Error handling + fixed pin connecting
This commit is contained in:
parent
d38ce7cd1b
commit
fbcfb76305
40
package-lock.json
generated
40
package-lock.json
generated
|
@ -2194,6 +2194,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
|
||||
|
@ -3058,6 +3063,14 @@
|
|||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||
|
@ -7715,6 +7728,11 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz",
|
||||
|
@ -7761,6 +7779,28 @@
|
|||
"tiny-warning": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-toastify": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.3.2.tgz",
|
||||
"integrity": "sha512-YHTTey7JWqXVkkBIeJ34PAvQELmGfLEGCx9bu68aIZYd+kRU2u9k/nG3AydgbX/uevIb4QNpeeE98DjkooMs5w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.2",
|
||||
"classnames": "^2.2.6",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-transition-group": "^2.6.1"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||
"requires": {
|
||||
"dom-helpers": "^3.4.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-toastify": "^5.3.2",
|
||||
"rxjs": "^6.5.2",
|
||||
"rxjs-hooks": "^0.5.1"
|
||||
}
|
||||
|
|
|
@ -2,5 +2,8 @@ import React from 'react'
|
|||
import App from './modules/core/components/App'
|
||||
|
||||
import { render } from 'react-dom'
|
||||
import { handleErrors } from './modules/errors/helpers/handlErrors'
|
||||
|
||||
render(<App />, document.getElementById('app'))
|
||||
|
||||
handleErrors()
|
||||
|
|
|
@ -8,4 +8,5 @@ body {
|
|||
|
||||
canvas {
|
||||
background-color: #222222;
|
||||
z-index: -1;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
import React from 'react'
|
||||
import '../styles/reset'
|
||||
import './App.scss'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import Canvas from './Canvas'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
|
||||
const App = () => {
|
||||
return <Canvas />
|
||||
return (
|
||||
<>
|
||||
<ToastContainer
|
||||
position="top-left"
|
||||
autoClose={5000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
<Canvas />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
9
src/modules/errors/classes/SimulationError.ts
Normal file
9
src/modules/errors/classes/SimulationError.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export class SimulationError extends Error {
|
||||
public constructor(public mesagge: string = '') {
|
||||
super()
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `SimulationError: ${this.mesagge}`
|
||||
}
|
||||
}
|
14
src/modules/errors/helpers/handlErrors.ts
Normal file
14
src/modules/errors/helpers/handlErrors.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
|
||||
export const handleErrors = () => {
|
||||
window.onerror = (a, b, c, d, error) => {
|
||||
if (error) {
|
||||
const args = createToastArguments(error.toString())
|
||||
|
||||
toast.error(...args)
|
||||
}
|
||||
|
||||
console.log(a)
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||
import { Gate } from '../../simulation/classes/Gate'
|
||||
import { Gate, PinWrapper } from '../../simulation/classes/Gate'
|
||||
import {
|
||||
GateState,
|
||||
TransformState,
|
||||
RendererState,
|
||||
CameraState,
|
||||
SimulationState
|
||||
SimulationState,
|
||||
WireState,
|
||||
WireLimit
|
||||
} from '../types/SimulationSave'
|
||||
import { Transform } from '../../../common/math/classes/Transform'
|
||||
import { Camera } from '../../simulationRenderer/classes/Camera'
|
||||
import { Simulation } from '../../simulation/classes/Simulation'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
|
||||
export const getTransformState = (transform: Transform): TransformState => {
|
||||
return {
|
||||
|
@ -25,9 +28,26 @@ export const getCameraState = (camera: Camera): CameraState => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getWireLimit = (pin: PinWrapper): WireLimit => {
|
||||
return {
|
||||
id: pin.value.gate.id,
|
||||
index: pin.index
|
||||
}
|
||||
}
|
||||
|
||||
export const getWireState = (wire: Wire): WireState => {
|
||||
return {
|
||||
from: getWireLimit(wire.start),
|
||||
to: getWireLimit(wire.end),
|
||||
id: wire.id
|
||||
}
|
||||
}
|
||||
|
||||
export const getSimulationState = (simulation: Simulation): SimulationState => {
|
||||
return {
|
||||
gates: Array.from(simulation.gates).map(getGateState)
|
||||
gates: Array.from(simulation.gates).map(getGateState),
|
||||
wires: simulation.wires.map(getWireState),
|
||||
mode: simulation.mode
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { vector2 } from '../../../common/math/classes/Transform'
|
||||
|
||||
export type simulationMode = 'ic' | 'project'
|
||||
|
||||
export interface TransformState {
|
||||
position: vector2
|
||||
scale: vector2
|
||||
|
@ -16,8 +18,22 @@ export interface CameraState {
|
|||
transform: TransformState
|
||||
}
|
||||
|
||||
export interface WireLimit {
|
||||
id: number
|
||||
index: number
|
||||
}
|
||||
|
||||
export interface WireState {
|
||||
from: WireLimit
|
||||
to: WireLimit
|
||||
id: number
|
||||
}
|
||||
|
||||
export interface SimulationState {
|
||||
gates: GateState[]
|
||||
wires: WireState[]
|
||||
|
||||
mode: simulationMode
|
||||
}
|
||||
|
||||
export interface RendererState {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Pin } from './Pin'
|
|||
import merge from 'deepmerge'
|
||||
import { GateTemplate, PinCount } from '../types/GateTemplate'
|
||||
import { DefaultGateTemplate } from '../constants'
|
||||
import { idStore } from '../stores/idStore'
|
||||
|
||||
export interface GatePins {
|
||||
inputs: Pin[]
|
||||
|
@ -16,22 +17,30 @@ export interface PinWrapper {
|
|||
}
|
||||
|
||||
export class Gate {
|
||||
public static lastId = 0
|
||||
|
||||
public transform = new Transform()
|
||||
public id = Gate.lastId++
|
||||
public _pins: GatePins = {
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}
|
||||
|
||||
public id: number
|
||||
public template: GateTemplate
|
||||
|
||||
public constructor(template: DeepPartial<GateTemplate> = {}) {
|
||||
public constructor(template: DeepPartial<GateTemplate> = {}, id?: number) {
|
||||
this.template = merge(DefaultGateTemplate, template) as GateTemplate
|
||||
|
||||
this._pins.inputs = Gate.generatePins(this.template.pins.inputs, 1)
|
||||
this._pins.outputs = Gate.generatePins(this.template.pins.outputs, 2)
|
||||
this._pins.inputs = Gate.generatePins(
|
||||
this.template.pins.inputs,
|
||||
1,
|
||||
this
|
||||
)
|
||||
this._pins.outputs = Gate.generatePins(
|
||||
this.template.pins.outputs,
|
||||
2,
|
||||
this
|
||||
)
|
||||
|
||||
this.id = id !== undefined ? id : idStore.generate()
|
||||
}
|
||||
|
||||
private wrapPins(pins: Pin[]) {
|
||||
|
@ -58,7 +67,9 @@ export class Gate {
|
|||
return result
|
||||
}
|
||||
|
||||
private static generatePins(options: PinCount, type: number) {
|
||||
return [...Array(options.count)].fill(true).map(() => new Pin(type))
|
||||
private static generatePins(options: PinCount, type: number, gate: Gate) {
|
||||
return [...Array(options.count)]
|
||||
.fill(true)
|
||||
.map(() => new Pin(type, gate))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { SubscriptionData } from '../types/SubscriptionData'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { Gate } from './Gate'
|
||||
|
||||
/* Types:
|
||||
|
||||
|
@ -9,16 +10,16 @@ Second bit = output
|
|||
*/
|
||||
export class Pin {
|
||||
public state = new BehaviorSubject(false)
|
||||
public connectedTo = new Set<Pin>()
|
||||
public pairs = new Set<Pin>()
|
||||
|
||||
private pairs = new Set<Pin>()
|
||||
private subscriptions: SubscriptionData<Pin>[] = []
|
||||
|
||||
public constructor(public type = 0b01) {}
|
||||
public constructor(public type = 0b01, public gate: Gate) {}
|
||||
|
||||
public addPair(pin: Pin) {
|
||||
public addPair(pin: Pin, subscribe = false) {
|
||||
this.pairs.add(pin)
|
||||
|
||||
if (subscribe) {
|
||||
const rawSubscription = pin.state.subscribe(state => {
|
||||
this.state.next(state)
|
||||
})
|
||||
|
@ -28,6 +29,7 @@ export class Pin {
|
|||
subscription: rawSubscription
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public removePair(pin: Pin) {
|
||||
this.pairs.delete(pin)
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { Gate } from './Gate'
|
||||
import { GateStorage } from './GateStorage'
|
||||
import { LruCacheNode } from '@eix-js/utils'
|
||||
import { Wire } from './Wire'
|
||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||
|
||||
export class Simulation {
|
||||
public gates = new GateStorage()
|
||||
public wires: Wire[] = []
|
||||
|
||||
public constructor(public mode: simulationMode = 'project') {}
|
||||
|
||||
public push(...gates: Gate[]) {
|
||||
for (const gate of gates) {
|
||||
|
|
27
src/modules/simulation/classes/Wire.ts
Normal file
27
src/modules/simulation/classes/Wire.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { idStore } from '../stores/idStore'
|
||||
import { PinWrapper } from './Gate'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
|
||||
export class Wire {
|
||||
public id: number
|
||||
|
||||
public constructor(
|
||||
public start: PinWrapper,
|
||||
public end: PinWrapper,
|
||||
id?: number
|
||||
) {
|
||||
if (end.value.pairs.size !== 0) {
|
||||
throw new SimulationError('An input pin can only have 1 pair')
|
||||
}
|
||||
|
||||
end.value.addPair(start.value, true)
|
||||
start.value.addPair(end.value)
|
||||
|
||||
this.id = id !== undefined ? id : idStore.generate()
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.end.value.removePair(this.start.value)
|
||||
this.start.value.removePair(this.end.value)
|
||||
}
|
||||
}
|
12
src/modules/simulation/stores/idStore.ts
Normal file
12
src/modules/simulation/stores/idStore.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||
|
||||
const store = new LocalStore<number>('id')
|
||||
|
||||
export const idStore = {
|
||||
generate() {
|
||||
const current = store.get()
|
||||
store.set(current + 1)
|
||||
|
||||
return current + 1
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import { getPinPosition } from '../helpers/pinPosition'
|
|||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||
import { SelectedPins } from '../types/SelectedPins'
|
||||
import { getRendererState } from '../../saving/helpers/getState'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
|
||||
export class SimulationRenderer {
|
||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||
|
@ -104,11 +105,21 @@ export class SimulationRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.selectedPins.start && this.selectedPins.end) {
|
||||
console.log('Connecting!')
|
||||
console.log(getRendererState(this))
|
||||
if (
|
||||
this.selectedPins.start &&
|
||||
this.selectedPins.end &&
|
||||
this.selectedPins.end.wrapper.value.pairs.size === 0
|
||||
) {
|
||||
this.simulation.wires.push(
|
||||
new Wire(
|
||||
this.selectedPins.start.wrapper,
|
||||
this.selectedPins.end.wrapper
|
||||
)
|
||||
)
|
||||
this.selectedPins.start = null
|
||||
this.selectedPins.end = null
|
||||
|
||||
console.log(getRendererState(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,14 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
|||
connectionLength: 30,
|
||||
pinRadius: 10,
|
||||
pinStrokeColor: '#888888',
|
||||
pinStrokeWidth: 3
|
||||
pinStrokeWidth: 3,
|
||||
pinFill: {
|
||||
open: 'rgb(255,216,20)',
|
||||
closed: 'rgb(90,90,90)'
|
||||
}
|
||||
},
|
||||
wires: {
|
||||
temporaryWireColor: `rgba(128,128,128,0.5)`
|
||||
temporaryWireColor: `rgba(128,128,128,0.5)`,
|
||||
curvePointOffset: 100
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { Pin } from '../../simulation/classes/Pin'
|
||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
|
||||
export const pinFill = (pin: Pin) => {
|
||||
export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
||||
let color = 'rgba(0,0,0,0)'
|
||||
|
||||
if (pin.connectedTo.size) {
|
||||
if (pin.state) {
|
||||
color = 'yellow'
|
||||
if (pin.pairs.size) {
|
||||
if (pin.state.value) {
|
||||
color = renderer.options.gates.pinFill.open
|
||||
} else {
|
||||
color = 'grey'
|
||||
color = renderer.options.gates.pinFill.closed
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const renderPins = (
|
|||
ctx.lineWidth = pinStrokeWidth
|
||||
|
||||
for (const pin of gate.pins) {
|
||||
ctx.fillStyle = pinFill(pin.value)
|
||||
ctx.fillStyle = pinFill(renderer, pin.value)
|
||||
|
||||
// render little connection
|
||||
const start = calculatePinStart(
|
||||
|
|
|
@ -3,6 +3,7 @@ import { invert } from '../../vector2/helpers/basic'
|
|||
import { renderGate } from './renderGate'
|
||||
import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
|
||||
import { renderClickedPins } from './renderClickedPins'
|
||||
import { renderWires } from './renderWires'
|
||||
|
||||
export const renderSimulation = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
|
@ -12,7 +13,10 @@ export const renderSimulation = (
|
|||
|
||||
ctx.translate(...renderer.camera.transform.position)
|
||||
|
||||
// render gates
|
||||
for (const wire of renderer.simulation.wires) {
|
||||
renderWires(ctx, renderer, wire)
|
||||
}
|
||||
|
||||
for (const gate of renderer.simulation.gates) {
|
||||
renderGate(ctx, renderer, gate)
|
||||
}
|
||||
|
|
94
src/modules/simulationRenderer/helpers/renderWires.ts
Normal file
94
src/modules/simulationRenderer/helpers/renderWires.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
import { pinFill } from './pinFill'
|
||||
import { getPinPosition } from './pinPosition'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
import { wireRadius } from './wireRadius'
|
||||
import { clamp } from '../../simulation/helpers/clamp'
|
||||
|
||||
export const renderWires = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
renderer: SimulationRenderer,
|
||||
wire: Wire
|
||||
) => {
|
||||
const { start, end } = wire
|
||||
const startPosition = getPinPosition(
|
||||
renderer,
|
||||
start.value.gate.transform,
|
||||
start
|
||||
)
|
||||
const endPosition = getPinPosition(renderer, end.value.gate.transform, end)
|
||||
const length = renderer.options.wires.curvePointOffset
|
||||
const centerY = (startPosition[1] + endPosition[1]) / 2
|
||||
const controlPostions = [startPosition[0] + length, endPosition[0] - length]
|
||||
|
||||
ctx.strokeStyle = pinFill(renderer, start.value)
|
||||
ctx.lineWidth = wireRadius(renderer)
|
||||
ctx.lineCap = 'round'
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(...startPosition)
|
||||
|
||||
if (startPosition[0] < endPosition[0]) {
|
||||
ctx.bezierCurveTo(
|
||||
controlPostions[0],
|
||||
startPosition[1],
|
||||
controlPostions[1],
|
||||
endPosition[1],
|
||||
...endPosition
|
||||
)
|
||||
} else {
|
||||
const { abs, PI } = Math
|
||||
|
||||
const baseFactor = startPosition[1] < endPosition[1] ? 1 : -1
|
||||
const factors = [baseFactor, baseFactor]
|
||||
|
||||
const radiuses = [...Array(2)].fill(
|
||||
abs((centerY - startPosition[1]) / 2)
|
||||
)
|
||||
|
||||
const limit = 70
|
||||
if (radiuses[0] < limit) {
|
||||
factors[0] *= -1
|
||||
radiuses[0] = limit
|
||||
radiuses[1] = abs(
|
||||
(startPosition[1] +
|
||||
factors[0] * 2 * radiuses[0] -
|
||||
endPosition[1]) /
|
||||
2
|
||||
)
|
||||
// radiuses[0] =
|
||||
}
|
||||
|
||||
const centerPosition = [
|
||||
startPosition[1] + factors[0] * radiuses[0],
|
||||
endPosition[1] - factors[1] * radiuses[1]
|
||||
]
|
||||
|
||||
ctx.arc(
|
||||
controlPostions[0],
|
||||
centerPosition[0],
|
||||
radiuses[0],
|
||||
(-factors[0] * PI) / 2,
|
||||
(factors[0] * PI) / 2,
|
||||
factors[0] !== 1
|
||||
)
|
||||
|
||||
ctx.lineTo(
|
||||
controlPostions[1],
|
||||
endPosition[1] - factors[1] * 2 * radiuses[1]
|
||||
)
|
||||
|
||||
ctx.arc(
|
||||
controlPostions[1],
|
||||
centerPosition[1],
|
||||
radiuses[1],
|
||||
(-factors[1] * PI) / 2,
|
||||
(factors[1] * PI) / 2,
|
||||
factors[1] === 1
|
||||
)
|
||||
|
||||
ctx.lineTo(...endPosition)
|
||||
}
|
||||
|
||||
ctx.stroke()
|
||||
}
|
9
src/modules/simulationRenderer/helpers/wireRadius.ts
Normal file
9
src/modules/simulationRenderer/helpers/wireRadius.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||
|
||||
export const wireRadius = (renderer: SimulationRenderer) => {
|
||||
return (
|
||||
2 *
|
||||
(renderer.options.gates.pinRadius -
|
||||
renderer.options.gates.pinStrokeWidth)
|
||||
)
|
||||
}
|
|
@ -7,8 +7,13 @@ export interface SimulationRendererOptions {
|
|||
pinRadius: number
|
||||
pinStrokeColor: string
|
||||
pinStrokeWidth: number
|
||||
pinFill: {
|
||||
open: string
|
||||
closed: string
|
||||
}
|
||||
}
|
||||
wires: {
|
||||
temporaryWireColor: string
|
||||
curvePointOffset: number
|
||||
}
|
||||
}
|
||||
|
|
56
src/modules/storage/classes/LocalStore.ts
Normal file
56
src/modules/storage/classes/LocalStore.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { CacheInstancesByKey } from '@eix-js/utils'
|
||||
|
||||
@CacheInstancesByKey(Infinity)
|
||||
export class LocalStore<T> {
|
||||
public constructor(public name: string) {
|
||||
if (!localStorage.getItem(name)) {
|
||||
localStorage.setItem(name, '{}')
|
||||
}
|
||||
}
|
||||
|
||||
public getAll(): Record<string, T> {
|
||||
const raw = localStorage.getItem(this.name)
|
||||
|
||||
if (!raw)
|
||||
throw new Error(
|
||||
`An error occured when accesing ${
|
||||
this.name
|
||||
} in the local storage!`
|
||||
)
|
||||
else {
|
||||
return JSON.parse(raw)
|
||||
}
|
||||
}
|
||||
|
||||
public ls(): string[] {
|
||||
return Object.keys(this.getAll())
|
||||
}
|
||||
|
||||
public *[Symbol.iterator](): Iterable<T> {
|
||||
for (const item of this.ls()) {
|
||||
return this.get(item)
|
||||
}
|
||||
}
|
||||
|
||||
public get(key = 'index') {
|
||||
return this.getAll()[key]
|
||||
}
|
||||
|
||||
public set(key: string | T = 'index', value?: T) {
|
||||
if (typeof key !== 'string' || value === undefined) {
|
||||
localStorage.setItem(
|
||||
this.name,
|
||||
JSON.stringify({
|
||||
index: key
|
||||
})
|
||||
)
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
this.name,
|
||||
JSON.stringify({
|
||||
[key]: value
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
15
src/modules/toasts/helpers/createToastArguments.ts
Normal file
15
src/modules/toasts/helpers/createToastArguments.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ToastOptions } from 'react-toastify'
|
||||
|
||||
export const createToastArguments = (
|
||||
message: string
|
||||
): [string, ToastOptions] => [
|
||||
message,
|
||||
{
|
||||
position: 'top-left',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true
|
||||
}
|
||||
]
|
|
@ -17,9 +17,7 @@ const babelRule = {
|
|||
use: 'babel-loader'
|
||||
}
|
||||
|
||||
const sassRule = {
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
const cssAndSass = [
|
||||
isProduction
|
||||
? MiniCssExtractPlugin.loader
|
||||
: {
|
||||
|
@ -28,7 +26,18 @@ const sassRule = {
|
|||
singleton: true
|
||||
}
|
||||
},
|
||||
{ loader: 'css-loader' },
|
||||
'css-loader'
|
||||
]
|
||||
|
||||
const cssRule = {
|
||||
test: /\.css$/,
|
||||
use: cssAndSass
|
||||
}
|
||||
|
||||
const sassRule = {
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
...cssAndSass,
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
|
@ -47,7 +56,7 @@ const baseConfig = {
|
|||
publicPath: '/'
|
||||
},
|
||||
module: {
|
||||
rules: [babelRule, sassRule]
|
||||
rules: [babelRule, sassRule, cssRule]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx', '.scss']
|
||||
|
|
Loading…
Reference in a new issue