mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-02-01 08:20:01 +05:30
Merge branch 'bsod_page' into develop
This commit is contained in:
commit
59b1539478
5
src/components/ui/bsod/BSoD.intl.json
Normal file
5
src/components/ui/bsod/BSoD.intl.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"criticalErrorHappened": "There was a critical error due to which the application can not continue its normal operation.",
|
||||
"reloadPageOrContactUs": "Please reload this page and try again. If problem occurs again, please report it to the developers by sending email to",
|
||||
"alsoYouCanInteractWithBackground": "You can also play around with the background – it's interactable ;)"
|
||||
}
|
@ -1,7 +1,255 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function BSoD() {
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { IntlProvider } from 'components/i18n';
|
||||
import appInfo from 'components/auth/appInfo/AppInfo.intl.json';
|
||||
import messages from './BSoD.intl.json';
|
||||
import { rAF as requestAnimationFrame } from 'functions';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
// TODO: probably it is better to render this view from the App view
|
||||
// to remove dependencies from store and IntlProvider
|
||||
export default function BSoD({store}) {
|
||||
return (
|
||||
<img src="https://i.ytimg.com/vi/_aT9r3ZFErY/maxresdefault.jpg" width="100%" height="100%" />
|
||||
<IntlProvider store={store}>
|
||||
<div className={styles.body}>
|
||||
<canvas className={styles.canvas} ref={(el) => new BoxesField(el)} />
|
||||
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.title}>
|
||||
<Message {...appInfo.appName} />
|
||||
</div>
|
||||
<div className={styles.lineWithMargin}>
|
||||
<Message {...messages.criticalErrorHappened} />
|
||||
</div>
|
||||
<div className={styles.line}>
|
||||
<Message {...messages.reloadPageOrContactUs} />
|
||||
</div>
|
||||
<a href="mailto:support@ely.by" className={styles.support}>
|
||||
support@ely.by
|
||||
</a>
|
||||
<div className={styles.easterEgg}>
|
||||
<Message {...messages.alsoYouCanInteractWithBackground}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
class Box {
|
||||
|
||||
constructor({size, startX, startY, startRotate, color, shadowColor}) {
|
||||
this.color = color;
|
||||
this.shadowColor = shadowColor;
|
||||
this.setSize(size);
|
||||
this.x = startX;
|
||||
this.y = startY;
|
||||
this.angle = startRotate;
|
||||
this.shadowLength = 2000; // TODO: should be calculated
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._initialSize;
|
||||
}
|
||||
|
||||
get dots() {
|
||||
const full = (Math.PI * 2) / 4;
|
||||
|
||||
const p1 = {
|
||||
x: this.x + this.halfSize * Math.sin(this.angle),
|
||||
y: this.y + this.halfSize * Math.cos(this.angle)
|
||||
};
|
||||
|
||||
const p2 = {
|
||||
x: this.x + this.halfSize * Math.sin(this.angle + full),
|
||||
y: this.y + this.halfSize * Math.cos(this.angle + full)
|
||||
};
|
||||
|
||||
const p3 = {
|
||||
x: this.x + this.halfSize * Math.sin(this.angle + full * 2),
|
||||
y: this.y + this.halfSize * Math.cos(this.angle + full * 2)
|
||||
};
|
||||
|
||||
const p4 = {
|
||||
x: this.x + this.halfSize * Math.sin(this.angle + full * 3),
|
||||
y: this.y + this.halfSize * Math.cos(this.angle + full * 3)
|
||||
};
|
||||
|
||||
return { p1, p2, p3, p4 };
|
||||
}
|
||||
|
||||
rotate() {
|
||||
const speed = (60 - this.halfSize) / 20;
|
||||
this.angle += speed * 0.002;
|
||||
this.x += speed;
|
||||
this.y += speed;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
const dots = this.dots;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(dots.p1.x, dots.p1.y);
|
||||
ctx.lineTo(dots.p2.x, dots.p2.y);
|
||||
ctx.lineTo(dots.p3.x, dots.p3.y);
|
||||
ctx.lineTo(dots.p4.x, dots.p4.y);
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
drawShadow(ctx, light) {
|
||||
const dots = this.dots;
|
||||
const angles = [];
|
||||
const points = [];
|
||||
|
||||
for (const i in dots) {
|
||||
const dot = dots[i];
|
||||
const angle = Math.atan2(light.y - dot.y, light.x - dot.x);
|
||||
const endX = dot.x + this.shadowLength * Math.sin(-angle - Math.PI / 2);
|
||||
const endY = dot.y + this.shadowLength * Math.cos(-angle - Math.PI / 2);
|
||||
angles.push(angle);
|
||||
points.push({
|
||||
endX,
|
||||
endY,
|
||||
startX: dot.x,
|
||||
startY: dot.y
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = points.length - 1; i >= 0; i--) {
|
||||
const n = i === 3 ? 0 : i + 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[i].startX, points[i].startY);
|
||||
ctx.lineTo(points[n].startX, points[n].startY);
|
||||
ctx.lineTo(points[n].endX, points[n].endY);
|
||||
ctx.lineTo(points[i].endX, points[i].endY);
|
||||
ctx.fillStyle = this.shadowColor;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
setSize(size) {
|
||||
this._initialSize = size;
|
||||
this.halfSize = Math.floor(size / 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Основано на http://codepen.io/mladen___/pen/gbvqBo
|
||||
*/
|
||||
class BoxesField {
|
||||
|
||||
/**
|
||||
* @param {Node} elem - canvas DOM node
|
||||
* @param {object} params
|
||||
*/
|
||||
constructor(elem, params = {
|
||||
countBoxes: 14,
|
||||
boxMinSize: 20,
|
||||
boxMaxSize: 75,
|
||||
backgroundColor: '#233d49',
|
||||
lightColor: '#28555b',
|
||||
shadowColor: '#274451',
|
||||
boxColors: ['#207e5c', '#5b9aa9', '#e66c69', '#6b5b8c', '#8b5d79', '#dd8650']
|
||||
}) {
|
||||
this.elem = elem;
|
||||
this.ctx = elem.getContext('2d');
|
||||
this.params = params;
|
||||
|
||||
this.light = {
|
||||
x: 160,
|
||||
y: 200
|
||||
};
|
||||
|
||||
this.resize();
|
||||
this.drawLoop();
|
||||
this.bindWindowListeners();
|
||||
|
||||
/**
|
||||
* @type {Box[]}
|
||||
*/
|
||||
this.boxes = [];
|
||||
while (this.boxes.length < this.params.countBoxes) {
|
||||
this.boxes.push(new Box({
|
||||
size: Math.floor((Math.random() * (this.params.boxMaxSize - this.params.boxMinSize)) + this.params.boxMinSize),
|
||||
startX: Math.floor((Math.random() * elem.width) + 1),
|
||||
startY: Math.floor((Math.random() * elem.height) + 1),
|
||||
startRotate: Math.random() * Math.PI,
|
||||
color: this.getRandomColor(),
|
||||
shadowColor: this.params.shadowColor
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
resize() {
|
||||
const { width, height } = this.elem.getBoundingClientRect();
|
||||
this.elem.width = width;
|
||||
this.elem.height = height;
|
||||
}
|
||||
|
||||
drawLight(light) {
|
||||
const greaterSize = window.screen.width > window.screen.height ? window.screen.width : window.screen.height;
|
||||
// еее, теорема пифагора и описывание окружности вокруг квадрата, не зря в универ ходил!!!
|
||||
const lightRadius = greaterSize * Math.sqrt(2);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(light.x, light.y, lightRadius, 0, 2 * Math.PI);
|
||||
const gradient = this.ctx.createRadialGradient(light.x, light.y, 0, light.x, light.y, lightRadius);
|
||||
gradient.addColorStop(0, this.params.lightColor);
|
||||
gradient.addColorStop(1, this.params.backgroundColor);
|
||||
this.ctx.fillStyle = gradient;
|
||||
this.ctx.fill();
|
||||
}
|
||||
|
||||
drawLoop() {
|
||||
this.ctx.clearRect(0, 0, this.elem.width, this.elem.height);
|
||||
this.drawLight(this.light);
|
||||
|
||||
for (let i in this.boxes) {
|
||||
const box = this.boxes[i];
|
||||
box.rotate();
|
||||
box.drawShadow(this.ctx, this.light);
|
||||
}
|
||||
|
||||
for (let i in this.boxes) {
|
||||
const box = this.boxes[i];
|
||||
box.draw(this.ctx);
|
||||
|
||||
// Если квадратик вылетел за пределы экрана
|
||||
if (box.y - box.halfSize > this.elem.height) {
|
||||
box.y -= this.elem.height + 100;
|
||||
this.updateBox(box);
|
||||
}
|
||||
|
||||
if (box.x - box.halfSize > this.elem.width) {
|
||||
box.x -= this.elem.width + 100;
|
||||
this.updateBox(box);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(this.drawLoop.bind(this));
|
||||
}
|
||||
|
||||
bindWindowListeners() {
|
||||
window.addEventListener('resize', this.resize.bind(this));
|
||||
window.addEventListener('mousemove', (event) => {
|
||||
this.light.x = event.clientX;
|
||||
this.light.y = event.clientY;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Box} box
|
||||
*/
|
||||
updateBox(box) {
|
||||
box.color = this.getRandomColor();
|
||||
}
|
||||
|
||||
getRandomColor() {
|
||||
return this.params.boxColors[Math.floor((Math.random() * this.params.boxColors.length))];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export default function dispatchBsod(store = injectedStore) {
|
||||
onBsod && onBsod();
|
||||
|
||||
ReactDOM.render(
|
||||
<BSoD />,
|
||||
<BSoD store={store} />,
|
||||
document.getElementById('app')
|
||||
);
|
||||
}
|
||||
|
56
src/components/ui/bsod/styles.scss
Normal file
56
src/components/ui/bsod/styles.scss
Normal file
@ -0,0 +1,56 @@
|
||||
@import '~components/ui/colors.scss';
|
||||
|
||||
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono', monospace;
|
||||
|
||||
.body {
|
||||
height: 100%;
|
||||
background-color: $dark_blue;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-family: $font-family-monospaced;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
margin: 85px auto 0;
|
||||
max-width: 500px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin: 0 auto;
|
||||
font-size: 16px;
|
||||
color: #EBE8E1;
|
||||
}
|
||||
|
||||
.lineWithMargin {
|
||||
composes: line;
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.support {
|
||||
font-size: 18px;
|
||||
color: #FFF;
|
||||
margin: 3px 0 44px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.easterEgg {
|
||||
font-size: 14px;
|
||||
color: #EBE8E1;
|
||||
}
|
@ -133,6 +133,9 @@
|
||||
"components.profile.preferencesDescription": "Тут вы можаце змяніць асноўныя параметры вашага акаўнта. Звярніце ўвагу, што для ўсіх дзеянняў неабходна пацверджанне з дапамогай уводу пароля.",
|
||||
"components.profile.projectRules": "правілах праекта",
|
||||
"components.profile.twoFactorAuth": "Двухфактарная аўтэнтыфікацыя",
|
||||
"components.ui.bsod.alsoYouCanInteractWithBackground": "Таксама вы можаце пагуляцца з фонам - ён інтэрактыўны ;)",
|
||||
"components.ui.bsod.criticalErrorHappened": "Здарылася крытычная памылка, з-за якой сэрвіс не можа працягваць сваю звычайную працу.",
|
||||
"components.ui.bsod.reloadPageOrContactUs": "Калі ласка, перезагрузіце старонку, каб паўтарыць спробу. Калі праблема ўзнікае зноў, паведаміце аб гэтым распрацоўшчыкам, даслаў пісьмо на адрас",
|
||||
"components.userbar.login": "Уваход",
|
||||
"components.userbar.register": "Рэгістрацыя",
|
||||
"pages.404.homePage": "галоўную старонку",
|
||||
|
@ -133,6 +133,9 @@
|
||||
"components.profile.preferencesDescription": "Here you can change the key preferences of your account. Please note that all actions must be confirmed by entering a password.",
|
||||
"components.profile.projectRules": "project rules",
|
||||
"components.profile.twoFactorAuth": "Two factor auth",
|
||||
"components.ui.bsod.alsoYouCanInteractWithBackground": "You can also play around with the background – it's interactable ;)",
|
||||
"components.ui.bsod.criticalErrorHappened": "There was a critical error due to which the application can not continue its normal operation.",
|
||||
"components.ui.bsod.reloadPageOrContactUs": "Please reload this page and try again. If problem occurs again, please report it to the developers by sending email to",
|
||||
"components.userbar.login": "Sign in",
|
||||
"components.userbar.register": "Join",
|
||||
"pages.404.homePage": "main page",
|
||||
|
@ -133,6 +133,9 @@
|
||||
"components.profile.preferencesDescription": "Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.",
|
||||
"components.profile.projectRules": "правилами проекта",
|
||||
"components.profile.twoFactorAuth": "Двухфакторная аутентификация",
|
||||
"components.ui.bsod.alsoYouCanInteractWithBackground": "А ещё вы можете потыкать на фон - он интерактивный ;)",
|
||||
"components.ui.bsod.criticalErrorHappened": "Случилась критическая ошибка, из-за которой приложение не может продолжить свою нормальную работу.",
|
||||
"components.ui.bsod.reloadPageOrContactUs": "Перезагрузите страницу, чтобы повторить попытку. Если проблема возникает вновь, сообщите об этом разработчикам, отправив письмо на адрес",
|
||||
"components.userbar.login": "Вход",
|
||||
"components.userbar.register": "Регистрация",
|
||||
"pages.404.homePage": "главную страницу",
|
||||
|
@ -133,6 +133,9 @@
|
||||
"components.profile.preferencesDescription": "Тут ви можете змінити ключові параметри вашого облікового запису. Зверніть увагу, що для всіх дій необхідно підтвердження за допомогою введення пароля.",
|
||||
"components.profile.projectRules": "правилами проекта",
|
||||
"components.profile.twoFactorAuth": "Двофакторна аутентифікація",
|
||||
"components.ui.bsod.alsoYouCanInteractWithBackground": "А ещё вы можете потыкать на фон - он интерактивный ;)",
|
||||
"components.ui.bsod.criticalErrorHappened": "Случилась критическая ошибка, из-за которой приложение не может продолжить свою нормальную работу.",
|
||||
"components.ui.bsod.reloadPageOrContactUs": "Перезагрузите страницу, чтобы повторить попытку. Если проблема возникает вновь, сообщите об этом разработчикам, отправив письмо на адрес",
|
||||
"components.userbar.login": "Вхід",
|
||||
"components.userbar.register": "Реєстрація",
|
||||
"pages.404.homePage": "",
|
||||
|
17
src/index.js
17
src/index.js
@ -6,8 +6,6 @@ import ReactDOM from 'react-dom';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { Router, browserHistory } from 'react-router';
|
||||
|
||||
import webFont from 'webfontloader';
|
||||
|
||||
import { factory as userFactory } from 'components/user/factory';
|
||||
import { IntlProvider } from 'components/i18n';
|
||||
import routesFactory from 'routes';
|
||||
@ -15,6 +13,7 @@ import storeFactory from 'storeFactory';
|
||||
import bsodFactory from 'components/ui/bsod/factory';
|
||||
import loader from 'services/loader';
|
||||
import logger from 'services/logger';
|
||||
import font from 'services/font';
|
||||
|
||||
logger.init({
|
||||
sentryCdn: window.SENTRY_CDN
|
||||
@ -24,21 +23,9 @@ const store = storeFactory();
|
||||
|
||||
bsodFactory(store, stopLoading);
|
||||
|
||||
const fontLoadingPromise = new Promise((resolve) =>
|
||||
webFont.load({
|
||||
classes: false,
|
||||
active: resolve,
|
||||
inactive: resolve, // TODO: may be we should track such cases
|
||||
timeout: 2000,
|
||||
custom: {
|
||||
families: ['Roboto', 'Roboto Condensed']
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Promise.all([
|
||||
userFactory(store),
|
||||
fontLoadingPromise
|
||||
font.load(['Roboto', 'Roboto Condensed'])
|
||||
])
|
||||
.then(() => {
|
||||
ReactDOM.render(
|
||||
|
32
src/services/font.js
Normal file
32
src/services/font.js
Normal file
@ -0,0 +1,32 @@
|
||||
import webFont from 'webfontloader';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @param {array} families
|
||||
* @param {object} options
|
||||
* @param {bool} [options.external=false] - whether the font should be loaded from external source (e.g. google)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
load(families = [], options = {}) {
|
||||
let config = {
|
||||
custom: {families}
|
||||
};
|
||||
|
||||
if (options.external) {
|
||||
config = {
|
||||
google: {families}
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve) =>
|
||||
webFont.load({
|
||||
classes: false,
|
||||
active: resolve,
|
||||
inactive: resolve, // TODO: may be we should track such cases
|
||||
timeout: 2000,
|
||||
...config
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user