loading
This commit is contained in:
parent
639f4b5aa0
commit
fccc1922fb
36
package-lock.json
generated
36
package-lock.json
generated
|
@ -2550,8 +2550,7 @@
|
|||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||
},
|
||||
"buffer-indexof": {
|
||||
"version": "1.1.1",
|
||||
|
@ -3202,6 +3201,31 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"cross-env": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz",
|
||||
"integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^6.0.5",
|
||||
"is-windows": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
|
||||
|
@ -9469,8 +9493,7 @@
|
|||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"source-map-resolve": {
|
||||
"version": "0.5.2",
|
||||
|
@ -9489,7 +9512,6 @@
|
|||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
|
||||
"integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
|
@ -9929,7 +9951,6 @@
|
|||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.1.2.tgz",
|
||||
"integrity": "sha512-jvNoEQSPXJdssFwqPSgWjsOrb+ELoE+ILpHPKXC83tIxOlh2U75F1KuB2luLD/3a6/7K3Vw5pDn+hvu0C4AzSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
@ -9939,8 +9960,7 @@
|
|||
"commander": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
|
||||
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-regenerator-runtime": "^6.5.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^4.1.0",
|
||||
"html-webpack-inline-source-plugin": "0.0.10",
|
||||
|
@ -50,6 +51,7 @@
|
|||
"react-router-dom": "^5.0.1",
|
||||
"react-toastify": "^5.3.2",
|
||||
"rxjs": "^6.5.2",
|
||||
"rxjs-hooks": "^0.5.1"
|
||||
"rxjs-hooks": "^0.5.1",
|
||||
"terser": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
|
15
src/common/dom/helpers/querySelector.ts
Normal file
15
src/common/dom/helpers/querySelector.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* A type-safe querySelector function, which throws if the given element was not found
|
||||
*
|
||||
* @credit https://gitlab.com/wavedistrict/web-client/blob/master/src/common/dom/helpers/querySelector.ts
|
||||
*/
|
||||
export function querySelector<E extends Element>(
|
||||
selector: string,
|
||||
parent: Element = document.body
|
||||
) {
|
||||
const element = parent.querySelector(selector)
|
||||
if (!element) {
|
||||
throw `Could not find element with selector "${selector}"`
|
||||
}
|
||||
return element as E
|
||||
}
|
21
src/common/lang/errors/helpers/getSafeErrorStack.ts
Normal file
21
src/common/lang/errors/helpers/getSafeErrorStack.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
export const getSafeErrorStack = (error: any) => {
|
||||
const errorString: string = error.toString()
|
||||
const stackString: string = error.stack
|
||||
|
||||
if (stackString) {
|
||||
const safeStackString =
|
||||
stackString.replace(errorString + '\n', '') || stackString
|
||||
|
||||
const stackItems = safeStackString.split('\n')
|
||||
const safeStackItems = stackItems
|
||||
.map(item => item.replace(' at ', ''))
|
||||
.filter(item => item !== '')
|
||||
.map(item => ` at ${item}`)
|
||||
|
||||
const safeStack = safeStackItems.join('\n')
|
||||
|
||||
return `${errorString}\n${safeStack}`
|
||||
}
|
||||
|
||||
return errorString
|
||||
}
|
|
@ -28,5 +28,14 @@
|
|||
oncontextmenu="return false"
|
||||
>
|
||||
<div id="app"></div>
|
||||
<div class="Splash">
|
||||
<div class="loading">
|
||||
<div class="lds-ripple">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<noscript> JavaScript must be enabled to run this app. </noscript>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
27
src/index.ts
Normal file
27
src/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Splash } from './modules/splash/classes/Splash'
|
||||
|
||||
async function main() {
|
||||
let splash: Splash | undefined = undefined
|
||||
|
||||
try {
|
||||
splash = new Splash()
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
const app = await import('./main')
|
||||
|
||||
await app.start()
|
||||
} catch (error) {
|
||||
if (splash) splash.setError(error)
|
||||
console.error(error.stack || error)
|
||||
return
|
||||
}
|
||||
|
||||
if (splash) {
|
||||
splash.fade()
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Error loading app', error)
|
||||
})
|
18
src/main.tsx
18
src/main.tsx
|
@ -5,11 +5,19 @@ import { render } from 'react-dom'
|
|||
import { handleErrors } from './modules/errors/helpers/handleErrors'
|
||||
import { initKeyBindings } from './modules/keybindings/helpers/initialiseKeyBindings'
|
||||
import { initBaseTemplates } from './modules/saving/helpers/initBaseTemplates'
|
||||
import { loadSubject } from './modules/core/subjects/loadedSubject'
|
||||
import { take } from 'rxjs/operators'
|
||||
|
||||
console.clear()
|
||||
export const start = async () => {
|
||||
console.clear()
|
||||
|
||||
handleErrors()
|
||||
initKeyBindings()
|
||||
initBaseTemplates()
|
||||
const result = loadSubject.pipe(take(1)).toPromise()
|
||||
|
||||
render(<App />, document.getElementById('app'))
|
||||
handleErrors()
|
||||
initKeyBindings()
|
||||
initBaseTemplates()
|
||||
|
||||
render(<App />, document.getElementById('app'))
|
||||
|
||||
await result
|
||||
}
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
/**
|
||||
* Transforms js code into a function
|
||||
*
|
||||
* @param source tThe js code
|
||||
* @param args The name of arguments to pass to the function
|
||||
*/
|
||||
export const toFunction = <T extends unknown[]>(
|
||||
source: string,
|
||||
...args: string[]
|
||||
): ((...args: T) => void) => {
|
||||
return new Function(`return (${args.join(',')}) => {
|
||||
const raw = `return (${args.join(',')}) => {
|
||||
${source}
|
||||
}`)()
|
||||
}`
|
||||
|
||||
return new Function(raw)()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
|
|||
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
||||
import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation'
|
||||
import { rendererSubject } from '../subjects/rendererSubject'
|
||||
import { loadSubject } from '../subjects/loadedSubject'
|
||||
|
||||
class Canvas extends Component {
|
||||
private canvasRef: RefObject<HTMLCanvasElement> = createRef()
|
||||
|
@ -24,6 +25,8 @@ class Canvas extends Component {
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
loadSubject.next(true)
|
||||
|
||||
if (this.canvasRef.current) {
|
||||
this.renderingContext = this.canvasRef.current.getContext('2d')
|
||||
this.renderer.updateWheelListener()
|
||||
|
|
3
src/modules/core/subjects/loadedSubject.ts
Normal file
3
src/modules/core/subjects/loadedSubject.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { Subject } from 'rxjs'
|
||||
|
||||
export const loadSubject = new Subject<true>()
|
|
@ -5,8 +5,10 @@ import {
|
|||
simulationInputCount,
|
||||
simulationOutputCount
|
||||
} from './simulationIoCount'
|
||||
import { InitialisationContext } from '../../activation/types/Context'
|
||||
import { templateStore } from '../../saving/stores/templateStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
|
||||
/**
|
||||
* Compiles a simulation into a logicGate
|
||||
|
@ -18,6 +20,7 @@ export const compileIc = ({ mode, name, gates }: SimulationState) => {
|
|||
throw new SimulationError('Cannot compile project')
|
||||
}
|
||||
|
||||
const translation = CurrentLanguage.getTranslation()
|
||||
const inputCount = simulationInputCount(gates)
|
||||
const outputCount = simulationOutputCount(gates)
|
||||
|
||||
|
@ -37,4 +40,10 @@ export const compileIc = ({ mode, name, gates }: SimulationState) => {
|
|||
}
|
||||
|
||||
templateStore.set(name, result)
|
||||
toast(
|
||||
...createToastArguments(
|
||||
translation.messages.compiledIc(name),
|
||||
'markunread_mailbox'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export const EnglishTranslation: Translation = {
|
|||
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
||||
switchedToSimulation: name =>
|
||||
`Succesfully switched to simulation '${name}'`,
|
||||
savedSimulation: name => `Succesfully saved simulation '${name}'`
|
||||
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
||||
compiledIc: name => `Succesfully compiled circuit '${name}'`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ export const DutchTranslation: Translation = {
|
|||
createdSimulation: name => `Simulatie '${name}' succesvol gecreerd`,
|
||||
switchedToSimulation: name =>
|
||||
`Succesvol veranderd naar simulatie '${name}'`,
|
||||
savedSimulation: name => `Simulatie succesvol opgeslagen '${name}'`
|
||||
savedSimulation: name => `Simulatie succesvol opgeslagen '${name}'`,
|
||||
compiledIc: name => `Todo: ${name}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ export const RomanianTranslation: Translation = {
|
|||
`Simulația '${name}' a fost creeată cu succes`,
|
||||
switchedToSimulation: name =>
|
||||
`Simulația '${name}' a fost deschisă cu succes`,
|
||||
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`
|
||||
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`,
|
||||
compiledIc: name => `Simulația '${name}' a fost compilată cu succes`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,5 +28,6 @@ export interface Translation {
|
|||
createdSimulation: NameSentence
|
||||
switchedToSimulation: NameSentence
|
||||
savedSimulation: NameSentence
|
||||
compiledIc: NameSentence
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ $item-color: $grey;
|
|||
@include modal-container();
|
||||
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.visible#logic-gate-modal-container {
|
||||
|
@ -42,7 +44,7 @@ $item-color: $grey;
|
|||
}
|
||||
|
||||
#logic-gate-modal-container > .logic-gate-item > * {
|
||||
font-size: 3em;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
#logic-gate-modal-container > .logic-gate-item > .logic-gate-item-type {
|
||||
|
@ -60,3 +62,9 @@ $item-color: $grey;
|
|||
#logic-gate-modal-container > .logic-gate-item > .logic-gate-item-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#logic-gate-modal-container > .logic-gate-item:first-child {
|
||||
// height: 4em;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,10 @@ import { LogicGateList } from '../subjects/LogicGateList'
|
|||
import Icon from '@material-ui/core/Icon'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { addGate } from '../../simulation/helpers/addGate'
|
||||
import { rendererSubject } from '../../core/subjects/rendererSubject'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { templateStore } from '../../saving/stores/templateStore'
|
||||
import { randomItem } from '../../internalisation/helpers/randomItem'
|
||||
import { completeTemplate } from '../helpers/completeTemplate'
|
||||
import { gateIcons } from '../constants'
|
||||
import { getTemplateSafely } from '../helpers/getTemplateSafely'
|
||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||
|
||||
/**
|
||||
* Subject containing the open state of the modal
|
||||
|
@ -31,6 +29,7 @@ export const handleClose = () => {
|
|||
const LogicGateModal = () => {
|
||||
const openSnapshot = useObservable(() => open, false)
|
||||
const gates = useObservable(() => LogicGateList, [])
|
||||
const renderer = getRendererSafely()
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -38,36 +37,33 @@ const LogicGateModal = () => {
|
|||
id="logic-gate-modal-container"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{gates.map((gate, index) => {
|
||||
const renderer = rendererSubject.value
|
||||
|
||||
if (!renderer) {
|
||||
throw new SimulationError(`Renderer not found`)
|
||||
}
|
||||
|
||||
const template = completeTemplate(templateStore.get(gate) || {})
|
||||
|
||||
if (!template) {
|
||||
throw new SimulationError(
|
||||
`Template ${gate} cannot be found`
|
||||
<div className="logic-gate-item">---</div>
|
||||
{gates
|
||||
.map(getTemplateSafely)
|
||||
.filter(template => {
|
||||
return (
|
||||
renderer.simulation.mode === 'project' ||
|
||||
template.metadata.name !== renderer.simulation.name
|
||||
)
|
||||
}
|
||||
})
|
||||
.map((template, index) => {
|
||||
const { name } = template.metadata
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="logic-gate-item"
|
||||
onClick={e => {
|
||||
addGate(renderer.simulation, gate)
|
||||
onClick={() => {
|
||||
addGate(renderer.simulation, name)
|
||||
}}
|
||||
>
|
||||
<Icon className="lgi-icon logic-gate-item-type">
|
||||
{gateIcons[template.tags[0]]}
|
||||
</Icon>
|
||||
<Typography className="logic-gate-item-name">
|
||||
{gate}
|
||||
{name}
|
||||
</Typography>
|
||||
{template.info.length && (
|
||||
{template.info.length ? (
|
||||
<a
|
||||
target="_blank"
|
||||
className="logic-gate-item-info"
|
||||
|
@ -79,6 +75,8 @@ const LogicGateModal = () => {
|
|||
>
|
||||
<Icon className="lgi-icon">info</Icon>
|
||||
</a>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
17
src/modules/logic-gates/helpers/getRendererSafely.ts
Normal file
17
src/modules/logic-gates/helpers/getRendererSafely.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { rendererSubject } from '../../core/subjects/rendererSubject'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
|
||||
/**
|
||||
* Gets the current simulation renderer
|
||||
*
|
||||
* @throws SimulationError no renderer was found
|
||||
*/
|
||||
export const getRendererSafely = () => {
|
||||
const renderer = rendererSubject.value
|
||||
|
||||
if (!renderer) {
|
||||
throw new SimulationError(`Renderer not found`)
|
||||
}
|
||||
|
||||
return renderer
|
||||
}
|
20
src/modules/logic-gates/helpers/getTemplateSafely.ts
Normal file
20
src/modules/logic-gates/helpers/getTemplateSafely.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { templateStore } from '../../saving/stores/templateStore'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { completeTemplate } from './completeTemplate'
|
||||
|
||||
/**
|
||||
* Gets a gate template from localStorage
|
||||
*
|
||||
* @param name - The name of the template
|
||||
*
|
||||
* @throws SimulationError if the template cant be found
|
||||
*/
|
||||
export const getTemplateSafely = (name: string) => {
|
||||
const template = completeTemplate(templateStore.get(name) || {})
|
||||
|
||||
if (!template) {
|
||||
throw new SimulationError(`Template ${name} cannot be found`)
|
||||
}
|
||||
|
||||
return template
|
||||
}
|
|
@ -5,6 +5,7 @@ import { saveStore } from '../stores/saveStore'
|
|||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
import { compileIc } from '../../integrated-circuits/helpers/compileIc'
|
||||
|
||||
/**
|
||||
* Inits a simulation by:
|
||||
|
@ -31,5 +32,9 @@ export const initSimulation = (name: string, mode: simulationMode) => {
|
|||
)
|
||||
)
|
||||
|
||||
if (mode === 'ic') {
|
||||
compileIc(state.simulation)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
32
src/modules/splash/classes/Spinner.scss
Normal file
32
src/modules/splash/classes/Spinner.scss
Normal file
|
@ -0,0 +1,32 @@
|
|||
.lds-ripple {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.lds-ripple div {
|
||||
position: absolute;
|
||||
border: 4px solid #fff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
.lds-ripple div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
@keyframes lds-ripple {
|
||||
0% {
|
||||
top: 28px;
|
||||
left: 28px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
61
src/modules/splash/classes/Splash.scss
Normal file
61
src/modules/splash/classes/Splash.scss
Normal file
|
@ -0,0 +1,61 @@
|
|||
@import './Spinner.scss';
|
||||
@import '../../core/styles/colors.scss';
|
||||
|
||||
.Splash {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
background-color: $grey;
|
||||
padding: 32px;
|
||||
|
||||
overflow-y: auto;
|
||||
z-index: 999999999999999;
|
||||
}
|
||||
|
||||
.Splash > .loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Splash > .error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Splash > .error > .title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.Splash > .error > .details {
|
||||
padding: 16px;
|
||||
|
||||
background-color: shade(darker);
|
||||
|
||||
border-radius: 3px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.Splash > .error > .description {
|
||||
color: font-color(normal);
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.Splash.-hasError {
|
||||
> .error {
|
||||
display: block;
|
||||
}
|
||||
> .loading {
|
||||
display: none;
|
||||
}
|
||||
}
|
84
src/modules/splash/classes/Splash.ts
Normal file
84
src/modules/splash/classes/Splash.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { querySelector } from '../../../common/dom/helpers/querySelector'
|
||||
import { getSafeErrorStack } from '../../../common/lang/errors/helpers/getSafeErrorStack'
|
||||
import './Splash.scss'
|
||||
|
||||
export class Splash {
|
||||
private element = querySelector<HTMLDivElement>('.Splash')
|
||||
|
||||
public fade() {
|
||||
this.element.style.transition = '0.3s'
|
||||
this.element.style.opacity = '0'
|
||||
this.element.style.visibility = 'hidden'
|
||||
|
||||
setTimeout(() => {
|
||||
this.element.remove()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
private createErrorDOM() {
|
||||
const root = document.createElement('div')
|
||||
root.className = 'error'
|
||||
|
||||
const title = document.createElement('div')
|
||||
title.className = 'title'
|
||||
title.innerText = 'Oops! An error occurred.'
|
||||
|
||||
const details = document.createElement('pre')
|
||||
details.className = 'details'
|
||||
|
||||
const description = document.createElement('div')
|
||||
description.className = 'description'
|
||||
description.innerHTML = `
|
||||
<article class="Document">
|
||||
<p>Your browser might not be supported, or your data might be corrupt.
|
||||
Press "Clear data" below to reset the simulator and try again.</br>
|
||||
</br></br>
|
||||
We do not support the following browsers:</p>
|
||||
<ul>
|
||||
<li><span>Opera Mini</span></li>
|
||||
<li><span>Internet Explorer</span></li>
|
||||
</ul>
|
||||
</article>
|
||||
`
|
||||
|
||||
const actions = document.createElement('div')
|
||||
actions.className = 'actions'
|
||||
|
||||
root.appendChild(title)
|
||||
root.appendChild(details)
|
||||
root.appendChild(description)
|
||||
root.appendChild(actions)
|
||||
|
||||
this.element.appendChild(root)
|
||||
}
|
||||
|
||||
public setError(error: any) {
|
||||
this.createErrorDOM()
|
||||
|
||||
const details = querySelector<HTMLDivElement>(
|
||||
'.Splash > .error > .details'
|
||||
)
|
||||
const actions = querySelector<HTMLDivElement>(
|
||||
'.Splash > .error > .actions'
|
||||
)
|
||||
|
||||
details.innerText = getSafeErrorStack(error)
|
||||
actions.appendChild(this.getClearButton())
|
||||
|
||||
this.element.classList.add('-hasError')
|
||||
}
|
||||
|
||||
private getClearButton() {
|
||||
const clearButton = document.createElement('button')
|
||||
|
||||
clearButton.classList.add('PrimaryButton')
|
||||
clearButton.textContent = 'Clear data'
|
||||
|
||||
clearButton.addEventListener('click', () => {
|
||||
localStorage.clear()
|
||||
location.reload()
|
||||
})
|
||||
|
||||
return clearButton
|
||||
}
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "esnext",
|
||||
"strictNullChecks": true
|
||||
"strictNullChecks": true,
|
||||
"module": "esnext"
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
|
|
|
@ -54,7 +54,7 @@ const sassRule = {
|
|||
|
||||
const baseConfig = {
|
||||
mode: 'none',
|
||||
entry: ['babel-regenerator-runtime', resolve(sourceFolder, 'main')],
|
||||
entry: ['babel-regenerator-runtime', resolve(sourceFolder, 'index')],
|
||||
output: {
|
||||
filename: 'js/[name].js',
|
||||
path: buildFolder,
|
||||
|
|
Loading…
Reference in a new issue