mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-30 02:32:58 +05:30
Add typings for Box and BoxesField classes, split BSoD view into controller and pure view, add storybook, fix support link styles
This commit is contained in:
parent
32ebba63a1
commit
228bc048af
6
packages/app/components/ui/bsod/BSoD.story.tsx
Normal file
6
packages/app/components/ui/bsod/BSoD.story.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
|
||||||
|
import BSoD from './BSoD';
|
||||||
|
|
||||||
|
storiesOf('UI', module).add('BSoD', () => <BSoD />);
|
@ -1,72 +1,56 @@
|
|||||||
import React from 'react';
|
import React, { ComponentType } from 'react';
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import logger from 'app/services/logger';
|
|
||||||
import appInfo from 'app/components/auth/appInfo/AppInfo.intl.json';
|
import appInfo from 'app/components/auth/appInfo/AppInfo.intl.json';
|
||||||
|
|
||||||
import styles from './styles.scss';
|
|
||||||
import BoxesField from './BoxesField';
|
import BoxesField from './BoxesField';
|
||||||
|
|
||||||
|
import styles from './styles.scss';
|
||||||
import messages from './BSoD.intl.json';
|
import messages from './BSoD.intl.json';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
lastEventId?: string | void;
|
lastEventId?: string | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: probably it is better to render this view from the App view
|
interface Props {
|
||||||
// to remove dependencies from store and IntlProvider
|
lastEventId?: string;
|
||||||
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 (
|
|
||||||
<div className={styles.body}>
|
|
||||||
<canvas
|
|
||||||
className={styles.canvas}
|
|
||||||
ref={(el: HTMLCanvasElement | null) => 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={emailUrl} className={styles.support}>
|
|
||||||
support@ely.by
|
|
||||||
</a>
|
|
||||||
<div className={styles.easterEgg}>
|
|
||||||
<Message {...messages.alsoYouCanInteractWithBackground} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: probably it's better to render this view from the App view
|
||||||
|
// to remove dependencies from the store and IntlProvider
|
||||||
|
const BSoD: ComponentType<Props> = ({ lastEventId }) => {
|
||||||
|
let emailUrl = 'mailto:support@ely.by';
|
||||||
|
|
||||||
|
if (lastEventId) {
|
||||||
|
emailUrl += `?subject=Bug report for #${lastEventId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.body}>
|
||||||
|
<canvas
|
||||||
|
className={styles.canvas}
|
||||||
|
ref={(el: HTMLCanvasElement | null) => 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={emailUrl} className={styles.support}>
|
||||||
|
support@ely.by
|
||||||
|
</a>
|
||||||
|
<div className={styles.easterEgg}>
|
||||||
|
<Message {...messages.alsoYouCanInteractWithBackground} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default BSoD;
|
export default BSoD;
|
||||||
|
29
packages/app/components/ui/bsod/BSoDContainer.tsx
Normal file
29
packages/app/components/ui/bsod/BSoDContainer.tsx
Normal file
@ -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<string>();
|
||||||
|
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 <BSoD lastEventId={lastEventId} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BSoDContainer;
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
119
packages/app/components/ui/bsod/Box.ts
Normal file
119
packages/app/components/ui/bsod/Box.ts
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,34 @@
|
|||||||
|
import Point from './Point';
|
||||||
import Box from './Box';
|
import Box from './Box';
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
countBoxes: number;
|
||||||
|
boxMinSize: number;
|
||||||
|
boxMaxSize: number;
|
||||||
|
backgroundColor: string;
|
||||||
|
lightColor: string;
|
||||||
|
shadowColor: string;
|
||||||
|
boxColors: ReadonlyArray<string>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Основано на http://codepen.io/mladen___/pen/gbvqBo
|
* Based on http://codepen.io/mladen___/pen/gbvqBo
|
||||||
*/
|
*/
|
||||||
export default class BoxesField {
|
export default class BoxesField {
|
||||||
|
private readonly elem: HTMLCanvasElement;
|
||||||
|
private readonly ctx: CanvasRenderingContext2D;
|
||||||
|
private readonly params: Params;
|
||||||
|
|
||||||
|
private light: Point;
|
||||||
|
private boxes: Array<Box>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLCanvasElement} elem - canvas DOM node
|
* @param {HTMLCanvasElement} elem - canvas DOM node
|
||||||
* @param {object} params
|
* @param {object} params
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
elem,
|
elem: HTMLCanvasElement,
|
||||||
params = {
|
params: Params = {
|
||||||
countBoxes: 14,
|
countBoxes: 14,
|
||||||
boxMinSize: 20,
|
boxMinSize: 20,
|
||||||
boxMaxSize: 75,
|
boxMaxSize: 75,
|
||||||
@ -31,7 +49,7 @@ export default class BoxesField {
|
|||||||
const ctx = elem.getContext('2d');
|
const ctx = elem.getContext('2d');
|
||||||
|
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('Can not get canvas 2d context');
|
throw new Error('Cannot get canvas 2d context');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
@ -46,35 +64,34 @@ export default class BoxesField {
|
|||||||
this.drawLoop();
|
this.drawLoop();
|
||||||
this.bindWindowListeners();
|
this.bindWindowListeners();
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Box[]}
|
|
||||||
*/
|
|
||||||
this.boxes = [];
|
this.boxes = [];
|
||||||
|
|
||||||
while (this.boxes.length < this.params.countBoxes) {
|
while (this.boxes.length < this.params.countBoxes) {
|
||||||
this.boxes.push(
|
this.boxes.push(
|
||||||
new Box({
|
new Box(
|
||||||
size: Math.floor(
|
Math.floor(
|
||||||
Math.random() * (this.params.boxMaxSize - this.params.boxMinSize) +
|
Math.random() * (this.params.boxMaxSize - this.params.boxMinSize) +
|
||||||
this.params.boxMinSize,
|
this.params.boxMinSize,
|
||||||
),
|
),
|
||||||
startX: Math.floor(Math.random() * elem.width + 1),
|
{
|
||||||
startY: Math.floor(Math.random() * elem.height + 1),
|
x: Math.floor(Math.random() * elem.width + 1),
|
||||||
startRotate: Math.random() * Math.PI,
|
y: Math.floor(Math.random() * elem.height + 1),
|
||||||
color: this.getRandomColor(),
|
},
|
||||||
shadowColor: this.params.shadowColor,
|
Math.random() * Math.PI,
|
||||||
}),
|
this.getRandomColor(),
|
||||||
|
this.params.shadowColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resize() {
|
resize(): void {
|
||||||
const { width, height } = this.elem.getBoundingClientRect();
|
const { width, height } = this.elem.getBoundingClientRect();
|
||||||
this.elem.width = width;
|
this.elem.width = width;
|
||||||
this.elem.height = height;
|
this.elem.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawLight(light) {
|
drawLight(light: Point): void {
|
||||||
const greaterSize =
|
const greaterSize =
|
||||||
window.screen.width > window.screen.height
|
window.screen.width > window.screen.height
|
||||||
? window.screen.width
|
? window.screen.width
|
||||||
@ -98,7 +115,7 @@ export default class BoxesField {
|
|||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawLoop() {
|
drawLoop(): void {
|
||||||
this.ctx.clearRect(0, 0, this.elem.width, this.elem.height);
|
this.ctx.clearRect(0, 0, this.elem.width, this.elem.height);
|
||||||
this.drawLight(this.light);
|
this.drawLight(this.light);
|
||||||
|
|
||||||
@ -120,14 +137,20 @@ export default class BoxesField {
|
|||||||
const box = this.boxes[i];
|
const box = this.boxes[i];
|
||||||
box.draw(this.ctx);
|
box.draw(this.ctx);
|
||||||
|
|
||||||
// Если квадратик вылетел за пределы экрана
|
// When box leaves window boundaries
|
||||||
if (box.y - box.halfSize > this.elem.height) {
|
let shouldUpdateBox = false;
|
||||||
box.y -= this.elem.height + 100;
|
|
||||||
this.updateBox(box);
|
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) {
|
if (box.position.x - box.halfSize > this.elem.width) {
|
||||||
box.x -= this.elem.width + 100;
|
box.position.x -= this.elem.width + 100;
|
||||||
|
shouldUpdateBox = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdateBox) {
|
||||||
this.updateBox(box);
|
this.updateBox(box);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,14 +166,11 @@ export default class BoxesField {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
updateBox(box: Box): void {
|
||||||
* @param {Box} box
|
|
||||||
*/
|
|
||||||
updateBox(box) {
|
|
||||||
box.color = this.getRandomColor();
|
box.color = this.getRandomColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomColor() {
|
getRandomColor(): string {
|
||||||
return this.params.boxColors[
|
return this.params.boxColors[
|
||||||
Math.floor(Math.random() * this.params.boxColors.length)
|
Math.floor(Math.random() * this.params.boxColors.length)
|
||||||
];
|
];
|
4
packages/app/components/ui/bsod/Point.ts
Normal file
4
packages/app/components/ui/bsod/Point.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
export const BSOD = 'BSOD';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function bsod() {
|
|
||||||
return {
|
|
||||||
type: BSOD,
|
|
||||||
};
|
|
||||||
}
|
|
9
packages/app/components/ui/bsod/actions.ts
Normal file
9
packages/app/components/ui/bsod/actions.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Action } from 'redux';
|
||||||
|
|
||||||
|
export const BSOD = 'BSOD';
|
||||||
|
|
||||||
|
export function bsod(): Action {
|
||||||
|
return {
|
||||||
|
type: BSOD,
|
||||||
|
};
|
||||||
|
}
|
@ -5,7 +5,7 @@ import { Store } from 'app/reducers';
|
|||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
|
|
||||||
import { bsod } from './actions';
|
import { bsod } from './actions';
|
||||||
import BSoD from './BSoD';
|
import BSoDContainer from './BSoDContainer';
|
||||||
|
|
||||||
let injectedStore: Store;
|
let injectedStore: Store;
|
||||||
let injectedHistory: History<any>;
|
let injectedHistory: History<any>;
|
||||||
@ -20,7 +20,7 @@ export default function dispatchBsod(
|
|||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<ContextProvider store={store} history={history}>
|
<ContextProvider store={store} history={history}>
|
||||||
<BSoD />
|
<BSoDContainer />
|
||||||
</ContextProvider>,
|
</ContextProvider>,
|
||||||
document.getElementById('app'),
|
document.getElementById('app'),
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@import '~app/components/ui/colors.scss';
|
@import '~app/components/ui/colors.scss';
|
||||||
|
|
||||||
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono',
|
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono', monospace;
|
||||||
monospace;
|
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -46,9 +45,16 @@ $font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono',
|
|||||||
|
|
||||||
.support {
|
.support {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
line-height: 18px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin: 3px 0 44px;
|
margin: 5px 0 44px;
|
||||||
display: block;
|
display: inline-block;
|
||||||
|
border-bottom-color: #39777f;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-bottom-color: #eee;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.easterEgg {
|
.easterEgg {
|
||||||
|
Loading…
Reference in New Issue
Block a user