diff --git a/packages/app/components/ui/bsod/BSoD.story.tsx b/packages/app/components/ui/bsod/BSoD.story.tsx
new file mode 100644
index 0000000..332c1fa
--- /dev/null
+++ b/packages/app/components/ui/bsod/BSoD.story.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+
+import BSoD from './BSoD';
+
+storiesOf('UI', module).add('BSoD', () => );
diff --git a/packages/app/components/ui/bsod/BSoD.tsx b/packages/app/components/ui/bsod/BSoD.tsx
index 3cd2397..bd3bdca 100644
--- a/packages/app/components/ui/bsod/BSoD.tsx
+++ b/packages/app/components/ui/bsod/BSoD.tsx
@@ -1,72 +1,56 @@
-import React from 'react';
+import React, { ComponentType } from 'react';
import { FormattedMessage as Message } from 'react-intl';
-import logger from 'app/services/logger';
+
import appInfo from 'app/components/auth/appInfo/AppInfo.intl.json';
-import styles from './styles.scss';
import BoxesField from './BoxesField';
+
+import styles from './styles.scss';
import messages from './BSoD.intl.json';
interface State {
lastEventId?: string | void;
}
-// TODO: probably it is better to render this view from the App view
-// to remove dependencies from store and IntlProvider
-class BSoD extends React.Component<{}, State> {
- state: State = {};
-
- componentDidMount() {
- // poll for event id
- const timer = setInterval(() => {
- if (!logger.getLastEventId()) {
- return;
- }
-
- clearInterval(timer);
-
- this.setState({
- lastEventId: logger.getLastEventId(),
- });
- }, 500);
- }
-
- render() {
- const { lastEventId } = this.state;
-
- let emailUrl = 'mailto:support@ely.by';
-
- if (lastEventId) {
- emailUrl += `?subject=Bug report for #${lastEventId}`;
- }
-
- return (
-
-
- );
- }
+interface Props {
+ lastEventId?: string;
}
+// TODO: probably it's better to render this view from the App view
+// to remove dependencies from the store and IntlProvider
+const BSoD: ComponentType = ({ lastEventId }) => {
+ let emailUrl = 'mailto:support@ely.by';
+
+ if (lastEventId) {
+ emailUrl += `?subject=Bug report for #${lastEventId}`;
+ }
+
+ return (
+
+
+ );
+};
+
export default BSoD;
diff --git a/packages/app/components/ui/bsod/BSoDContainer.tsx b/packages/app/components/ui/bsod/BSoDContainer.tsx
new file mode 100644
index 0000000..422b895
--- /dev/null
+++ b/packages/app/components/ui/bsod/BSoDContainer.tsx
@@ -0,0 +1,29 @@
+import React, { ComponentType, useEffect, useState } from 'react';
+
+import logger from 'app/services/logger/logger';
+
+import BSoD from './BSoD';
+
+const BSoDContainer: ComponentType = () => {
+ const [lastEventId, setLastEventId] = useState();
+ useEffect(() => {
+ const timer = setInterval(() => {
+ // eslint-disable-next-line no-shadow
+ const lastEventId = logger.getLastEventId();
+
+ if (!lastEventId) {
+ return;
+ }
+
+ clearInterval(timer);
+ setLastEventId(lastEventId);
+ }, 500);
+
+ // Don't care about interval cleanup because there is no way from
+ // BSoD state and page can be only reloaded
+ }, []);
+
+ return ;
+};
+
+export default BSoDContainer;
diff --git a/packages/app/components/ui/bsod/Box.js b/packages/app/components/ui/bsod/Box.js
deleted file mode 100644
index ed04b6d..0000000
--- a/packages/app/components/ui/bsod/Box.js
+++ /dev/null
@@ -1,100 +0,0 @@
-export default class Box {
- constructor({ size, startX, startY, startRotate, color, shadowColor }) {
- this.color = color;
- this.shadowColor = shadowColor;
- this.halfSize = 0;
- 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;
- 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;
- const angles = [];
- const points = [];
-
- for (const i in dots) {
- if (!dots.hasOwnProperty(i)) {
- continue;
- }
-
- 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);
- }
-}
diff --git a/packages/app/components/ui/bsod/Box.ts b/packages/app/components/ui/bsod/Box.ts
new file mode 100644
index 0000000..5fa9726
--- /dev/null
+++ b/packages/app/components/ui/bsod/Box.ts
@@ -0,0 +1,119 @@
+import Point from './Point';
+
+const shadowLength = 2000; // TODO: should be calculated
+
+export default class Box {
+ public position: Point;
+ private angle: number;
+ public color: string;
+ private readonly shadowColor: string;
+
+ private _size: number;
+ private _halfSize: number;
+
+ constructor(
+ size: number,
+ position: Point,
+ startRotate: number,
+ color: string,
+ shadowColor: string,
+ ) {
+ this.size = size;
+ this.position = position;
+ this.color = color;
+ this.angle = startRotate;
+ this.shadowColor = shadowColor;
+ }
+
+ public get size(): number {
+ return this._size;
+ }
+
+ public set size(size: number) {
+ this._size = size;
+ this._halfSize = Math.floor(size / 2);
+ }
+
+ public get halfSize(): number {
+ return this._halfSize;
+ }
+
+ get points(): { p1: Point; p2: Point; p3: Point; p4: Point } {
+ const full = (Math.PI * 2) / 4;
+
+ const p1: Point = {
+ x: this.position.x + this._halfSize * Math.sin(this.angle),
+ y: this.position.y + this._halfSize * Math.cos(this.angle),
+ };
+
+ const p2: Point = {
+ x: this.position.x + this._halfSize * Math.sin(this.angle + full),
+ y: this.position.y + this._halfSize * Math.cos(this.angle + full),
+ };
+
+ const p3: Point = {
+ x: this.position.x + this._halfSize * Math.sin(this.angle + full * 2),
+ y: this.position.y + this._halfSize * Math.cos(this.angle + full * 2),
+ };
+
+ const p4: Point = {
+ x: this.position.x + this._halfSize * Math.sin(this.angle + full * 3),
+ y: this.position.y + this._halfSize * Math.cos(this.angle + full * 3),
+ };
+
+ return { p1, p2, p3, p4 };
+ }
+
+ rotate(): void {
+ const speed = (60 - this._halfSize) / 20;
+ this.angle += speed * 0.002;
+ this.position.x += speed;
+ this.position.y += speed;
+ }
+
+ draw(ctx: CanvasRenderingContext2D): void {
+ const { points } = this;
+ ctx.beginPath();
+ ctx.moveTo(points.p1.x, points.p1.y);
+ ctx.lineTo(points.p2.x, points.p2.y);
+ ctx.lineTo(points.p3.x, points.p3.y);
+ ctx.lineTo(points.p4.x, points.p4.y);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ }
+
+ drawShadow(ctx: CanvasRenderingContext2D, light: Point): void {
+ const boxPoints = this.points;
+ const points: Array<{
+ startX: number;
+ startY: number;
+ endX: number;
+ endY: number;
+ }> = [];
+
+ // eslint-disable-next-line guard-for-in
+ for (const i in boxPoints) {
+ const point = boxPoints[i];
+ const angle = Math.atan2(light.y - point.y, light.x - point.x);
+ const endX = point.x + shadowLength * Math.sin(-angle - Math.PI / 2);
+ const endY = point.y + shadowLength * Math.cos(-angle - Math.PI / 2);
+ points.push({
+ startX: point.x,
+ startY: point.y,
+ endX,
+ endY,
+ });
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/packages/app/components/ui/bsod/BoxesField.js b/packages/app/components/ui/bsod/BoxesField.ts
similarity index 67%
rename from packages/app/components/ui/bsod/BoxesField.js
rename to packages/app/components/ui/bsod/BoxesField.ts
index 7c279ca..c2870b3 100644
--- a/packages/app/components/ui/bsod/BoxesField.js
+++ b/packages/app/components/ui/bsod/BoxesField.ts
@@ -1,16 +1,34 @@
+import Point from './Point';
import Box from './Box';
+interface Params {
+ countBoxes: number;
+ boxMinSize: number;
+ boxMaxSize: number;
+ backgroundColor: string;
+ lightColor: string;
+ shadowColor: string;
+ boxColors: ReadonlyArray;
+}
+
/**
- * Основано на http://codepen.io/mladen___/pen/gbvqBo
+ * Based on http://codepen.io/mladen___/pen/gbvqBo
*/
export default class BoxesField {
+ private readonly elem: HTMLCanvasElement;
+ private readonly ctx: CanvasRenderingContext2D;
+ private readonly params: Params;
+
+ private light: Point;
+ private boxes: Array;
+
/**
* @param {HTMLCanvasElement} elem - canvas DOM node
* @param {object} params
*/
constructor(
- elem,
- params = {
+ elem: HTMLCanvasElement,
+ params: Params = {
countBoxes: 14,
boxMinSize: 20,
boxMaxSize: 75,
@@ -31,7 +49,7 @@ export default class BoxesField {
const ctx = elem.getContext('2d');
if (!ctx) {
- throw new Error('Can not get canvas 2d context');
+ throw new Error('Cannot get canvas 2d context');
}
this.ctx = ctx;
@@ -46,35 +64,34 @@ export default class BoxesField {
this.drawLoop();
this.bindWindowListeners();
- /**
- * @type {Box[]}
- */
this.boxes = [];
while (this.boxes.length < this.params.countBoxes) {
this.boxes.push(
- new Box({
- size: Math.floor(
+ new Box(
+ 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,
- }),
+ {
+ x: Math.floor(Math.random() * elem.width + 1),
+ y: Math.floor(Math.random() * elem.height + 1),
+ },
+ Math.random() * Math.PI,
+ this.getRandomColor(),
+ this.params.shadowColor,
+ ),
);
}
}
- resize() {
+ resize(): void {
const { width, height } = this.elem.getBoundingClientRect();
this.elem.width = width;
this.elem.height = height;
}
- drawLight(light) {
+ drawLight(light: Point): void {
const greaterSize =
window.screen.width > window.screen.height
? window.screen.width
@@ -98,7 +115,7 @@ export default class BoxesField {
this.ctx.fill();
}
- drawLoop() {
+ drawLoop(): void {
this.ctx.clearRect(0, 0, this.elem.width, this.elem.height);
this.drawLight(this.light);
@@ -120,14 +137,20 @@ export default class BoxesField {
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);
+ // When box leaves window boundaries
+ let shouldUpdateBox = false;
+
+ if (box.position.y - box.halfSize > this.elem.height) {
+ box.position.y -= this.elem.height + 100;
+ shouldUpdateBox = true;
}
- if (box.x - box.halfSize > this.elem.width) {
- box.x -= this.elem.width + 100;
+ if (box.position.x - box.halfSize > this.elem.width) {
+ box.position.x -= this.elem.width + 100;
+ shouldUpdateBox = true;
+ }
+
+ if (shouldUpdateBox) {
this.updateBox(box);
}
}
@@ -143,14 +166,11 @@ export default class BoxesField {
});
}
- /**
- * @param {Box} box
- */
- updateBox(box) {
+ updateBox(box: Box): void {
box.color = this.getRandomColor();
}
- getRandomColor() {
+ getRandomColor(): string {
return this.params.boxColors[
Math.floor(Math.random() * this.params.boxColors.length)
];
diff --git a/packages/app/components/ui/bsod/Point.ts b/packages/app/components/ui/bsod/Point.ts
new file mode 100644
index 0000000..6ebe393
--- /dev/null
+++ b/packages/app/components/ui/bsod/Point.ts
@@ -0,0 +1,4 @@
+export default interface Point {
+ x: number;
+ y: number;
+}
diff --git a/packages/app/components/ui/bsod/actions.js b/packages/app/components/ui/bsod/actions.js
deleted file mode 100644
index f4010f9..0000000
--- a/packages/app/components/ui/bsod/actions.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export const BSOD = 'BSOD';
-
-/**
- * @returns {object}
- */
-export function bsod() {
- return {
- type: BSOD,
- };
-}
diff --git a/packages/app/components/ui/bsod/actions.ts b/packages/app/components/ui/bsod/actions.ts
new file mode 100644
index 0000000..2128074
--- /dev/null
+++ b/packages/app/components/ui/bsod/actions.ts
@@ -0,0 +1,9 @@
+import { Action } from 'redux';
+
+export const BSOD = 'BSOD';
+
+export function bsod(): Action {
+ return {
+ type: BSOD,
+ };
+}
diff --git a/packages/app/components/ui/bsod/dispatchBsod.tsx b/packages/app/components/ui/bsod/dispatchBsod.tsx
index e76b293..0cb12c1 100644
--- a/packages/app/components/ui/bsod/dispatchBsod.tsx
+++ b/packages/app/components/ui/bsod/dispatchBsod.tsx
@@ -5,7 +5,7 @@ import { Store } from 'app/reducers';
import { History } from 'history';
import { bsod } from './actions';
-import BSoD from './BSoD';
+import BSoDContainer from './BSoDContainer';
let injectedStore: Store;
let injectedHistory: History;
@@ -20,7 +20,7 @@ export default function dispatchBsod(
ReactDOM.render(
-
+
,
document.getElementById('app'),
);
diff --git a/packages/app/components/ui/bsod/styles.scss b/packages/app/components/ui/bsod/styles.scss
index ca38ca0..d9ff3fb 100644
--- a/packages/app/components/ui/bsod/styles.scss
+++ b/packages/app/components/ui/bsod/styles.scss
@@ -1,7 +1,6 @@
@import '~app/components/ui/colors.scss';
-$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono',
- monospace;
+$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono', monospace;
.body {
height: 100%;
@@ -46,9 +45,16 @@ $font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono',
.support {
font-size: 18px;
+ line-height: 18px;
color: #fff;
- margin: 3px 0 44px;
- display: block;
+ margin: 5px 0 44px;
+ display: inline-block;
+ border-bottom-color: #39777f;
+
+ &:hover {
+ color: #fff;
+ border-bottom-color: #eee;
+ }
}
.easterEgg {