diff --git a/src/components/ui/bsod/BSoD.intl.json b/src/components/ui/bsod/BSoD.intl.json
new file mode 100644
index 0000000..f195005
--- /dev/null
+++ b/src/components/ui/bsod/BSoD.intl.json
@@ -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 ;)"
+}
diff --git a/src/components/ui/bsod/BSoD.jsx b/src/components/ui/bsod/BSoD.jsx
index 256625c..c8917e5 100644
--- a/src/components/ui/bsod/BSoD.jsx
+++ b/src/components/ui/bsod/BSoD.jsx
@@ -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 (
-
+
+
+
+
);
}
+
+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))];
+ }
+
+}
diff --git a/src/components/ui/bsod/dispatchBsod.js b/src/components/ui/bsod/dispatchBsod.js
index 0954035..ef97070 100644
--- a/src/components/ui/bsod/dispatchBsod.js
+++ b/src/components/ui/bsod/dispatchBsod.js
@@ -12,7 +12,7 @@ export default function dispatchBsod(store = injectedStore) {
onBsod && onBsod();
ReactDOM.render(
- ,
+ ,
document.getElementById('app')
);
}
diff --git a/src/components/ui/bsod/styles.scss b/src/components/ui/bsod/styles.scss
new file mode 100644
index 0000000..346733f
--- /dev/null
+++ b/src/components/ui/bsod/styles.scss
@@ -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;
+}
diff --git a/src/i18n/be.json b/src/i18n/be.json
index 7970aad..9e7168d 100644
--- a/src/i18n/be.json
+++ b/src/i18n/be.json
@@ -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": "галоўную старонку",
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 65d4322..c6f8aa4 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -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",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 6cd3047..79c231c 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -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": "главную страницу",
diff --git a/src/i18n/uk.json b/src/i18n/uk.json
index eeef6bb..bd6bfc8 100644
--- a/src/i18n/uk.json
+++ b/src/i18n/uk.json
@@ -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": "",
diff --git a/src/index.js b/src/index.js
index 827f62c..3166b6e 100644
--- a/src/index.js
+++ b/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(
diff --git a/src/services/font.js b/src/services/font.js
new file mode 100644
index 0000000..deb39b1
--- /dev/null
+++ b/src/services/font.js
@@ -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
+ })
+ );
+ }
+};