From 285248435a190d53be6432c96dd9869aeb31d7d1 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Mon, 29 Jul 2019 22:51:17 +0300 Subject: [PATCH] gate props and fixed the pins not beenig set to false bug --- .vscode/template.code-snippets | 15 + src/modules/activation/types/Context.ts | 3 + src/modules/core/components/App.tsx | 7 +- src/modules/core/components/CustomRouter.tsx | 6 + src/modules/core/components/Root.tsx | 2 + src/modules/core/constants.ts | 6 + .../styles/global-styles/mui-overrides.scss | 2 +- .../components/GateProperties.scss | 53 +++ .../components/GatePropertiesModal.tsx | 138 ++++++++ .../subjects/LogicGatePropsSubjects.ts | 4 + src/modules/saving/constants.ts | 308 ++---------------- src/modules/saving/data/delayProperties.ts | 12 + src/modules/saving/helpers/fromState.ts | 3 +- src/modules/saving/templates/and.ts | 25 ++ src/modules/saving/templates/button.ts | 56 ++++ src/modules/saving/templates/light.ts | 40 +++ src/modules/saving/templates/nand.ts | 25 ++ src/modules/saving/templates/nor.ts | 25 ++ src/modules/saving/templates/not.ts | 20 ++ src/modules/saving/templates/or.ts | 25 ++ .../saving/templates/parallelDelayer.ts | 30 ++ src/modules/saving/templates/rgb.ts | 49 +++ .../saving/templates/sequentialDelayer.ts | 31 ++ src/modules/saving/templates/xnor.ts | 30 ++ src/modules/saving/templates/xor.ts | 30 ++ src/modules/saving/types/PartialTemplate.ts | 6 + src/modules/saving/types/SimulationSave.ts | 2 +- src/modules/simulation/classes/Gate.ts | 37 ++- src/modules/simulation/classes/Wire.ts | 1 + src/modules/simulation/constants.ts | 3 +- src/modules/simulation/types/GateTemplate.ts | 15 +- .../classes/SimulationRenderer.ts | 63 ++-- src/modules/simulationRenderer/constants.ts | 8 +- 33 files changed, 739 insertions(+), 341 deletions(-) create mode 100644 .vscode/template.code-snippets create mode 100644 src/modules/core/components/CustomRouter.tsx create mode 100644 src/modules/logic-gates/components/GateProperties.scss create mode 100644 src/modules/logic-gates/components/GatePropertiesModal.tsx create mode 100644 src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts create mode 100644 src/modules/saving/data/delayProperties.ts create mode 100644 src/modules/saving/templates/and.ts create mode 100644 src/modules/saving/templates/button.ts create mode 100644 src/modules/saving/templates/light.ts create mode 100644 src/modules/saving/templates/nand.ts create mode 100644 src/modules/saving/templates/nor.ts create mode 100644 src/modules/saving/templates/not.ts create mode 100644 src/modules/saving/templates/or.ts create mode 100644 src/modules/saving/templates/parallelDelayer.ts create mode 100644 src/modules/saving/templates/rgb.ts create mode 100644 src/modules/saving/templates/sequentialDelayer.ts create mode 100644 src/modules/saving/templates/xnor.ts create mode 100644 src/modules/saving/templates/xor.ts create mode 100644 src/modules/saving/types/PartialTemplate.ts diff --git a/.vscode/template.code-snippets b/.vscode/template.code-snippets new file mode 100644 index 0000000..bd851af --- /dev/null +++ b/.vscode/template.code-snippets @@ -0,0 +1,15 @@ +{ + "Gate template": { + "prefix": "template", + "body": [ + "import { PartialTemplate } from '../types/PartialTemplate'", + "", + "/**", + " * The template of the ${1:and} gate", + " */", + "const ${1}Template: PartialTemplate = ${2:[]}", + "", + "export default ${1}Template" + ] + } +} diff --git a/src/modules/activation/types/Context.ts b/src/modules/activation/types/Context.ts index f4576da..1283da2 100644 --- a/src/modules/activation/types/Context.ts +++ b/src/modules/activation/types/Context.ts @@ -4,9 +4,12 @@ import { fromSimulationState } from '../../saving/helpers/fromState' export interface Context { memory: Record + getProperty: (name: string) => unknown + setProperty: (name: string, value: unknown) => void get: (index: number) => boolean set: (index: number, state: boolean) => void color: (color: string) => void + update: () => void enviroment: SimulationEnv colors: Record } diff --git a/src/modules/core/components/App.tsx b/src/modules/core/components/App.tsx index 7d672f6..000ee42 100644 --- a/src/modules/core/components/App.tsx +++ b/src/modules/core/components/App.tsx @@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css' import { ToastContainer } from 'react-toastify' import { theme as muiTheme } from '../constants' -import { BrowserRouter as Router, Route, Link } from 'react-router-dom' +import { Route } from 'react-router-dom' import React, { useEffect } from 'react' import CssBaseline from '@material-ui/core/CssBaseline' @@ -15,6 +15,7 @@ import Head from './Head' import Root from './Root' import LogicGatePage from '../../logic-gates/components/LogicGatesPage' import { loadSubject } from '../subjects/loadedSubject' +import { CustomRouter } from './CustomRouter' const App = () => { useEffect(() => { @@ -27,12 +28,12 @@ const App = () => { - + - + { return ( <> + ) diff --git a/src/modules/core/constants.ts b/src/modules/core/constants.ts index 97ebc68..45b6b76 100644 --- a/src/modules/core/constants.ts +++ b/src/modules/core/constants.ts @@ -1,6 +1,7 @@ import { createMuiTheme } from '@material-ui/core/styles' import { red, deepPurple } from '@material-ui/core/colors' import { simulationMode } from '../saving/types/SimulationSave' +import { createBrowserHistory } from 'history' export const theme = createMuiTheme({ palette: { @@ -31,3 +32,8 @@ export const icons: IconInterface = { * The width of the sidebar */ export const sidebarWidth = 240 + +/** + * The history to be used by the router + */ +export const history = createBrowserHistory() diff --git a/src/modules/core/styles/global-styles/mui-overrides.scss b/src/modules/core/styles/global-styles/mui-overrides.scss index 92cfe73..73330c4 100644 --- a/src/modules/core/styles/global-styles/mui-overrides.scss +++ b/src/modules/core/styles/global-styles/mui-overrides.scss @@ -1,6 +1,6 @@ @import '../colors.scss'; -.MuiListItem-root.contained { +.contained { // i spent hours trying to find a better solution background-color: $primary !important; } diff --git a/src/modules/logic-gates/components/GateProperties.scss b/src/modules/logic-gates/components/GateProperties.scss new file mode 100644 index 0000000..87c2129 --- /dev/null +++ b/src/modules/logic-gates/components/GateProperties.scss @@ -0,0 +1,53 @@ +@import '../../core/styles/mixins/flex.scss'; +@import '../../core/styles/colors.scss'; +@import '../../core/styles/mixins/visibility.scss'; +@import '../../modals/styles/mixins/modal-container.scss'; +@import '../../modals/styles/mixins/modal-title.scss'; + +$gate-props-margin: 1rem; + +#gate-properties-modal { + @include modal-container(); + + justify-content: center; +} + +.visible#gate-properties-modal { + @include visible(); +} + +div #gate-properties-container { + @include flex; + + background-color: $grey; + padding: $gate-props-margin * 4; + border-radius: 1em; +} + +#gate-props-divider { + margin-top: $gate-props-margin; + margin-bottom: $gate-props-margin; +} + +div #gate-props-title { + color: white; + font-size: 3em; +} + +div .gate-prop-container { + @include flex(); + + flex-direction: row; + justify-content: start; +} + +div #save-props { + width: 50%; + margin: $gate-props-margin * 2; + margin-bottom: 0; +} + +.checkbox-label { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} diff --git a/src/modules/logic-gates/components/GatePropertiesModal.tsx b/src/modules/logic-gates/components/GatePropertiesModal.tsx new file mode 100644 index 0000000..cc250a4 --- /dev/null +++ b/src/modules/logic-gates/components/GatePropertiesModal.tsx @@ -0,0 +1,138 @@ +import './GateProperties.scss' +import React, { ChangeEvent } from 'react' +import { getRendererSafely } from '../helpers/getRendererSafely' +import { Property } from '../../simulation/types/GateTemplate' +import { BehaviorSubject } from 'rxjs' +import { useObservable } from 'rxjs-hooks' +import Divider from '@material-ui/core/Divider' +import TextField from '@material-ui/core/TextField' +import CheckBox from '@material-ui/core/Checkbox' +import { open, id } from '../subjects/LogicGatePropsSubjects' +import { Gate } from '../../simulation/classes/Gate' + +export interface GatePropertyProps { + raw: Property + name: string + output: BehaviorSubject + gate: Gate +} + +/** + * Renders a single props of the gate + * + * @param param0 The props passed to the component + */ +export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => { + const outputSnapshot = useObservable(() => output, '') + + const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:` + + const handleChange = (e: ChangeEvent) => { + const target = e.target as HTMLInputElement + let value: boolean | string | number = target.value + + if (raw.type === 'boolean') { + value = target.checked + } else if (raw.type === 'number') { + value = Number(target.value) + } + + gate.props[name] = value + + if (raw.type !== 'boolean') { + output.next(target.value) + } + + if (raw.needsUpdate === true) { + gate.update() + } + } + + let input = <> + if (raw.type === 'number' || raw.type === 'text') { + input = ( + + ) + } else if (raw.type === 'boolean') { + input = ( + <> + {displayableName} + { + output.next(!outputSnapshot) + }} + onChange={handleChange} + checked={!!outputSnapshot} + />{' '} + + ) + } + + return
{input}
+} + +/** + * The props page of any gate wich has props enabled + * + * @param props The react props of the component + */ +const GateProperties = () => { + const openSnapshot = useObservable(() => open, false) + const renderer = getRendererSafely() + + const node = renderer.simulation.gates.get(id.value) + + if (!(node && node.data && node.data.template.properties.enabled)) { + open.next(false) + return <> + } + + const gate = node.data + const gateProps = gate.props + + return ( +
{ + open.next(false) + }} + > +
{ + e.stopPropagation() + }} + > +
Gate properties
+ + + {gate.template.properties.data.map((raw, index) => { + const { name, base } = raw + + const prop = gateProps[name] + const output = new BehaviorSubject( + gateProps.hasOwnProperty(name) ? prop : base + ) + + return ( + + ) + })} +
+
+ ) +} + +export default GateProperties diff --git a/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts b/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts new file mode 100644 index 0000000..a75af4a --- /dev/null +++ b/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts @@ -0,0 +1,4 @@ +import { BehaviorSubject, Subject } from 'rxjs' + +export const open = new Subject() +export const id = new BehaviorSubject(0) diff --git a/src/modules/saving/constants.ts b/src/modules/saving/constants.ts index 4c5d5c5..92c452f 100644 --- a/src/modules/saving/constants.ts +++ b/src/modules/saving/constants.ts @@ -1,292 +1,32 @@ import { GateTemplate } from '../simulation/types/GateTemplate' import { RendererState } from './types/SimulationSave' +import andTemplate from './templates/and' +import buttonTemplate from './templates/button' +import lightTemplate from './templates/light' +import nandTemplate from './templates/nand' +import norTemplate from './templates/nor' +import notTemplate from './templates/not' +import orTemplate from './templates/or' +import parallelDelayerTemplate from './templates/parallelDelayer' +import rgbLightTemplate from './templates/rgb' +import sequentialDelayerTemplate from './templates/sequentialDelayer' +import xnorTemplate from './templates/xnor' +import xorTemplate from './templates/xor' export const defaultSimulationName = 'default' export const baseTemplates: DeepPartial[] = [ - { - metadata: { - name: 'and' - }, - material: { - type: 'image', - fill: require('../../assets/and') - }, - code: { - activation: `context.set(0, context.get(0) && context.get(1))` - }, - pins: { - inputs: { - count: 2 - } - }, - info: ['https://en.wikipedia.org/wiki/AND_gate'] - }, - { - metadata: { - name: 'nand' - }, - material: { - type: 'image', - fill: require('../../assets/nand') - }, - code: { - activation: `context.set(0, !context.get(0) || !context.get(1))` - }, - pins: { - inputs: { - count: 2 - } - }, - info: ['https://en.wikipedia.org/wiki/NAND_gate'] - }, - { - metadata: { - name: 'or' - }, - material: { - type: 'image', - fill: require('../../assets/or') - }, - code: { - activation: `context.set(0, context.get(0) || context.get(1))` - }, - pins: { - inputs: { - count: 2 - } - }, - info: ['https://en.wikipedia.org/wiki/OR_gate'] - }, - { - metadata: { - name: 'nor' - }, - material: { - type: 'image', - fill: require('../../assets/nor') - }, - code: { - activation: `context.set(0, !(context.get(0) || context.get(1)))` - }, - pins: { - inputs: { - count: 2 - } - }, - info: ['https://en.wikipedia.org/wiki/NOR_gate'] - }, - { - metadata: { - name: 'xor' - }, - material: { - type: 'image', - fill: require('../../assets/xor') - }, - code: { - activation: ` - const a = context.get(0) - const b = context.get(1) - const c = (a || b) && (!a || !b) - - context.set(0, c)` - }, - info: ['https://en.wikipedia.org/wiki/XOR_gate'], - pins: { - inputs: { - count: 2 - } - } - }, - { - metadata: { - name: 'xnor' - }, - material: { - type: 'image', - fill: require('../../assets/xnor') - }, - code: { - activation: ` - const a = context.get(0) - const b = context.get(1) - const c = (a && b) || !(a || b) - - context.set(0, c)` - }, - info: ['https://en.wikipedia.org/wiki/XNOR_gate'], - pins: { - inputs: { - count: 2 - } - } - }, - { - metadata: { - name: 'not' - }, - material: { - type: 'image', - fill: require('../../assets/not') - }, - code: { - activation: `context.set(0, !context.get(0))` - }, - info: ['https://en.wikipedia.org/wiki/Inverter_(logic_gate)'] - }, - { - metadata: { - name: 'button' - }, - material: { - fill: '#D32F2E', - stroke: { - normal: '#AB8C31', - active: '#7EF813' - }, - colors: { - pressed: '#7D1313' - } - }, - code: { - onClick: ` - const old = context.memory.state - const state = !old - - context.set(0, state) - context.color(old ? context.colors.main : context.colors.pressed) - - context.memory.state = state - ` - }, - pins: { - inputs: { - count: 0 - } - }, - integration: { - input: true - }, - info: ['https://en.wikipedia.org/wiki/Push-button'] - }, - { - metadata: { - name: 'light bulb' - }, - shape: { - radius: 50 - }, - material: { - fill: '#1C1C1C', - stroke: { - normal: '#3C3C3C' - }, - colors: { - active: '#C6FF00' - } - }, - code: { - activation: ` - const { main, active } = context.colors - - context.color(context.get(0) ? active : main) - ` - }, - integration: { - output: true - }, - info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'], - pins: { - outputs: { - count: 0 - } - } - }, - { - metadata: { - name: 'rgb light' - }, - material: { - fill: '#1C1C1C', - colors: { - 1: '#00f', - 2: `#0f0`, - 3: `#0ff`, - 4: `#f00`, - 5: `#f0f`, - 6: `#ff0`, - 7: `#fff` - } - }, - code: { - activation: ` - const color = (context.get(0) << 2) + (context.get(1) << 1) + context.get(2) - - if (color === 0){ - context.color(context.colors.main) - } - - else{ - context.color(context.colors[color]) - } - ` - }, - integration: { - output: true - }, - info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'], - pins: { - outputs: { - count: 0 - }, - inputs: { - count: 3 - } - } - }, - { - metadata: { - name: 'Sequential delayer' - }, - material: { - type: 'image', - fill: require('../../assets/sequential') - }, - code: { - activation: ` - const i = context.get(0) - return new Promise((res, rej) => { - setTimeout(() => { - res() - },1000) - }).then(() => { - context.set(0,i) - }) - `, - async: true - } - }, - { - metadata: { - name: 'Parallel delayer' - }, - material: { - type: 'image', - fill: require('../../assets/parallel') - }, - code: { - activation: ` - const i = context.get(0) - return new Promise((res, rej) => { - setTimeout(() => { - res() - },1000) - }).then(() => { - context.set(0,i) - }) - ` - } - } + andTemplate, + buttonTemplate, + lightTemplate, + nandTemplate, + norTemplate, + notTemplate, + orTemplate, + parallelDelayerTemplate, + rgbLightTemplate, + sequentialDelayerTemplate, + xnorTemplate, + xorTemplate ] export const baseSave: RendererState = { diff --git a/src/modules/saving/data/delayProperties.ts b/src/modules/saving/data/delayProperties.ts new file mode 100644 index 0000000..e56f931 --- /dev/null +++ b/src/modules/saving/data/delayProperties.ts @@ -0,0 +1,12 @@ +import { GateTemplate } from '../../simulation/types/GateTemplate' + +export const delayProperties: GateTemplate['properties'] = { + enabled: true, + data: [ + { + base: 1000, + type: 'number', + name: 'delay' + } + ] +} diff --git a/src/modules/saving/helpers/fromState.ts b/src/modules/saving/helpers/fromState.ts index b935767..77d5532 100644 --- a/src/modules/saving/helpers/fromState.ts +++ b/src/modules/saving/helpers/fromState.ts @@ -35,7 +35,8 @@ export const fromSimulationState = ( for (const gateState of state.gates) { const gate = new Gate( templateStore.get(gateState.template), - gateState.id + gateState.id, + gateState.props ) gate.transform = fromTransformState(gateState.transform) diff --git a/src/modules/saving/templates/and.ts b/src/modules/saving/templates/and.ts new file mode 100644 index 0000000..ba8636d --- /dev/null +++ b/src/modules/saving/templates/and.ts @@ -0,0 +1,25 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the and gate + */ +const andTemplate: PartialTemplate = { + metadata: { + name: 'and' + }, + material: { + type: 'image', + fill: require('../../../assets/and') + }, + code: { + activation: `context.set(0, context.get(0) && context.get(1))` + }, + pins: { + inputs: { + count: 2 + } + }, + info: ['https://en.wikipedia.org/wiki/AND_gate'] +} + +export default andTemplate diff --git a/src/modules/saving/templates/button.ts b/src/modules/saving/templates/button.ts new file mode 100644 index 0000000..a8e1e8d --- /dev/null +++ b/src/modules/saving/templates/button.ts @@ -0,0 +1,56 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the button gate + */ +const buttonTemplate: PartialTemplate = { + metadata: { + name: 'button' + }, + material: { + fill: '#D32F2E', + stroke: { + normal: '#AB8C31', + active: '#7EF813' + }, + colors: { + pressed: '#7D1313' + } + }, + code: { + onClick: ` + const active = context.getProperty('active') + context.setProperty('active',!active) + + context.update() + `, + activation: ` + const state = context.getProperty('active') + + context.set(0, state) + context.color(!state ? context.colors.main : context.colors.pressed) + ` + }, + pins: { + inputs: { + count: 0 + } + }, + integration: { + input: true + }, + info: ['https://en.wikipedia.org/wiki/Push-button'], + properties: { + enabled: true, + data: [ + { + base: false, + name: 'active', + type: 'boolean', + needsUpdate: true + } + ] + } +} + +export default buttonTemplate diff --git a/src/modules/saving/templates/light.ts b/src/modules/saving/templates/light.ts new file mode 100644 index 0000000..c01a357 --- /dev/null +++ b/src/modules/saving/templates/light.ts @@ -0,0 +1,40 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the light gate + */ +const lightTemplate: PartialTemplate = { + metadata: { + name: 'light bulb' + }, + shape: { + radius: 50 + }, + material: { + fill: '#1C1C1C', + stroke: { + normal: '#3C3C3C' + }, + colors: { + active: '#C6FF00' + } + }, + code: { + activation: ` + const { main, active } = context.colors + + context.color(context.get(0) ? active : main) + ` + }, + integration: { + output: true + }, + info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'], + pins: { + outputs: { + count: 0 + } + } +} + +export default lightTemplate diff --git a/src/modules/saving/templates/nand.ts b/src/modules/saving/templates/nand.ts new file mode 100644 index 0000000..08cdf36 --- /dev/null +++ b/src/modules/saving/templates/nand.ts @@ -0,0 +1,25 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the nand gate + */ +const nandTemplate: PartialTemplate = { + metadata: { + name: 'nand' + }, + material: { + type: 'image', + fill: require('../../../assets/nand') + }, + code: { + activation: `context.set(0, !context.get(0) || !context.get(1))` + }, + pins: { + inputs: { + count: 2 + } + }, + info: ['https://en.wikipedia.org/wiki/NAND_gate'] +} + +export default nandTemplate diff --git a/src/modules/saving/templates/nor.ts b/src/modules/saving/templates/nor.ts new file mode 100644 index 0000000..3a57108 --- /dev/null +++ b/src/modules/saving/templates/nor.ts @@ -0,0 +1,25 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the nor gate + */ +const norTemplate: PartialTemplate = { + metadata: { + name: 'nor' + }, + material: { + type: 'image', + fill: require('../../../assets/nor') + }, + code: { + activation: `context.set(0, !(context.get(0) || context.get(1)))` + }, + pins: { + inputs: { + count: 2 + } + }, + info: ['https://en.wikipedia.org/wiki/NOR_gate'] +} + +export default norTemplate diff --git a/src/modules/saving/templates/not.ts b/src/modules/saving/templates/not.ts new file mode 100644 index 0000000..485ddce --- /dev/null +++ b/src/modules/saving/templates/not.ts @@ -0,0 +1,20 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the not gate + */ +const notTemplate: PartialTemplate = { + metadata: { + name: 'not' + }, + material: { + type: 'image', + fill: require('../../../assets/not') + }, + code: { + activation: `context.set(0, !context.get(0))` + }, + info: ['https://en.wikipedia.org/wiki/Inverter_(logic_gate)'] +} + +export default notTemplate diff --git a/src/modules/saving/templates/or.ts b/src/modules/saving/templates/or.ts new file mode 100644 index 0000000..dc9c80a --- /dev/null +++ b/src/modules/saving/templates/or.ts @@ -0,0 +1,25 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the or gate + */ +const orTemplate: PartialTemplate = { + metadata: { + name: 'or' + }, + material: { + type: 'image', + fill: require('../../../assets/or') + }, + code: { + activation: `context.set(0, context.get(0) || context.get(1))` + }, + pins: { + inputs: { + count: 2 + } + }, + info: ['https://en.wikipedia.org/wiki/OR_gate'] +} + +export default orTemplate diff --git a/src/modules/saving/templates/parallelDelayer.ts b/src/modules/saving/templates/parallelDelayer.ts new file mode 100644 index 0000000..6e50c83 --- /dev/null +++ b/src/modules/saving/templates/parallelDelayer.ts @@ -0,0 +1,30 @@ +import { PartialTemplate } from '../types/PartialTemplate' +import { delayProperties } from '../data/delayProperties' + +/** + * The template of the parallelDelayer gate + */ +const parallelDelayerTemplate: PartialTemplate = { + metadata: { + name: 'Parallel delayer' + }, + material: { + type: 'image', + fill: require('../../../assets/parallel') + }, + code: { + activation: ` + const i = context.get(0) + return new Promise((res, rej) => { + setTimeout(() => { + res() + }, context.getProperty('delay')) + }).then(() => { + context.set(0,i) + }) + ` + }, + properties: delayProperties +} + +export default parallelDelayerTemplate diff --git a/src/modules/saving/templates/rgb.ts b/src/modules/saving/templates/rgb.ts new file mode 100644 index 0000000..8fe0c05 --- /dev/null +++ b/src/modules/saving/templates/rgb.ts @@ -0,0 +1,49 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the rgbLight gate + */ +const rgbLightTemplate: PartialTemplate = { + metadata: { + name: 'rgb light' + }, + material: { + fill: '#1C1C1C', + colors: { + 1: '#00f', + 2: `#0f0`, + 3: `#0ff`, + 4: `#f00`, + 5: `#f0f`, + 6: `#ff0`, + 7: `#fff` + } + }, + code: { + activation: ` + const color = (context.get(0) << 2) + (context.get(1) << 1) + context.get(2) + + if (color === 0){ + context.color(context.colors.main) + } + + else{ + context.color(context.colors[color]) + } + ` + }, + integration: { + output: true + }, + info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'], + pins: { + outputs: { + count: 0 + }, + inputs: { + count: 3 + } + } +} + +export default rgbLightTemplate diff --git a/src/modules/saving/templates/sequentialDelayer.ts b/src/modules/saving/templates/sequentialDelayer.ts new file mode 100644 index 0000000..d6519e1 --- /dev/null +++ b/src/modules/saving/templates/sequentialDelayer.ts @@ -0,0 +1,31 @@ +import { PartialTemplate } from '../types/PartialTemplate' +import { delayProperties } from '../data/delayProperties' + +/** + * The template of the sequentialDelayer gate + */ +const sequentialDelayerTemplate: PartialTemplate = { + metadata: { + name: 'Sequential delayer' + }, + material: { + type: 'image', + fill: require('../../../assets/sequential') + }, + code: { + activation: ` + const i = context.get(0) + return new Promise((res, rej) => { + setTimeout(() => { + res() + },context.getProperty('delay')) + }).then(() => { + context.set(0,i) + }) + `, + async: true + }, + properties: delayProperties +} + +export default sequentialDelayerTemplate diff --git a/src/modules/saving/templates/xnor.ts b/src/modules/saving/templates/xnor.ts new file mode 100644 index 0000000..978bc5a --- /dev/null +++ b/src/modules/saving/templates/xnor.ts @@ -0,0 +1,30 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the xnor gate + */ +const xnorTemplate: PartialTemplate = { + metadata: { + name: 'xnor' + }, + material: { + type: 'image', + fill: require('../../../assets/xnor') + }, + code: { + activation: ` + const a = context.get(0) + const b = context.get(1) + const c = (a && b) || !(a || b) + + context.set(0, c)` + }, + info: ['https://en.wikipedia.org/wiki/XNOR_gate'], + pins: { + inputs: { + count: 2 + } + } +} + +export default xnorTemplate diff --git a/src/modules/saving/templates/xor.ts b/src/modules/saving/templates/xor.ts new file mode 100644 index 0000000..5d96ecb --- /dev/null +++ b/src/modules/saving/templates/xor.ts @@ -0,0 +1,30 @@ +import { PartialTemplate } from '../types/PartialTemplate' + +/** + * The template of the xor gate + */ +const xorTemplate: PartialTemplate = { + metadata: { + name: 'xor' + }, + material: { + type: 'image', + fill: require('../../../assets/xor') + }, + code: { + activation: ` + const a = context.get(0) + const b = context.get(1) + const c = (a || b) && (!a || !b) + + context.set(0, c)` + }, + info: ['https://en.wikipedia.org/wiki/XOR_gate'], + pins: { + inputs: { + count: 2 + } + } +} + +export default xorTemplate diff --git a/src/modules/saving/types/PartialTemplate.ts b/src/modules/saving/types/PartialTemplate.ts new file mode 100644 index 0000000..803fae5 --- /dev/null +++ b/src/modules/saving/types/PartialTemplate.ts @@ -0,0 +1,6 @@ +import { GateTemplate } from '../../simulation/types/GateTemplate' + +/** + * Deep partial of a gate template + */ +export type PartialTemplate = DeepPartial diff --git a/src/modules/saving/types/SimulationSave.ts b/src/modules/saving/types/SimulationSave.ts index 9a502c2..1d58e7a 100644 --- a/src/modules/saving/types/SimulationSave.ts +++ b/src/modules/saving/types/SimulationSave.ts @@ -12,7 +12,7 @@ export interface GateState { transform: TransformState id: number template: string - props: Record + props: Record } export interface CameraState { diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts index 734489e..fe2b284 100644 --- a/src/modules/simulation/classes/Gate.ts +++ b/src/modules/simulation/classes/Gate.ts @@ -118,7 +118,7 @@ export class Gate { /** * The props used by the activation function (the same as memory but presists) */ - public props: Record = {} + public props: Record = {} /** * The main logic gate class @@ -129,7 +129,7 @@ export class Gate { public constructor( template: DeepPartial = {}, id?: number, - props: Record = {} + props: Record = {} ) { this.template = completeTemplate(template) @@ -259,17 +259,25 @@ export class Gate { /** * Assign the props passed to the gate and mere them with the base ones */ - private assignProps(props: Record) { + private assignProps(props: Record) { + let shouldUpdate = false + if (this.template.properties.enabled) { - for (const prop in this.template.properties) { - if (prop !== 'enabled') { - this.props[prop] = - props[prop] !== undefined - ? props[prop] - : this.template.properties[prop].base + for (const { base, name, needsUpdate } of this.template.properties + .data) { + this.props[name] = props.hasOwnProperty(name) + ? props[name] + : base + + if (!shouldUpdate && needsUpdate) { + shouldUpdate = true } } } + + if (shouldUpdate) { + this.update() + } } /** @@ -335,13 +343,22 @@ export class Gate { set: (index: number, state: boolean = false) => { return this._pins.outputs[index].state.next(state) }, - memory: this.memory, color: (color: string) => { if (this.template.material.type === 'color') { this.template.material.fill = color } }, + getProperty: (name: string) => { + return this.props[name] + }, + setProperty: (name: string, value: string | number | boolean) => { + this.props[name] = value + }, + update: () => { + this.update() + }, enviroment: this.env, + memory: this.memory, colors: { ...this.template.material.colors } diff --git a/src/modules/simulation/classes/Wire.ts b/src/modules/simulation/classes/Wire.ts index 65ac442..d4e0d9a 100644 --- a/src/modules/simulation/classes/Wire.ts +++ b/src/modules/simulation/classes/Wire.ts @@ -30,6 +30,7 @@ export class Wire { public dispose() { this.end.value.removePair(this.start.value) this.start.value.removePair(this.end.value) + this.end.value.state.next(false) this.active = false } diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts index 39062ab..77b66be 100644 --- a/src/modules/simulation/constants.ts +++ b/src/modules/simulation/constants.ts @@ -51,6 +51,7 @@ export const DefaultGateTemplate: GateTemplate = { info: [], tags: ['base'], properties: { - enabled: false + enabled: false, + data: [] } } diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts index 24ceec1..ab089da 100644 --- a/src/modules/simulation/types/GateTemplate.ts +++ b/src/modules/simulation/types/GateTemplate.ts @@ -1,15 +1,17 @@ import { vector2 } from '../../../common/math/classes/Transform' -import { InputHTMLAttributes } from 'react' export interface PinCount { variable: boolean count: number } -export interface Property { - type: HTMLInputElement['type'] - encode: (value: string) => T +export interface Property< + T extends boolean | number | string = boolean | number | string +> { + type: 'number' | 'string' | 'text' | 'boolean' base: T + name: string + needsUpdate?: boolean } export interface Material { @@ -69,5 +71,8 @@ export interface GateTemplate { } info: string[] tags: GateTag[] - properties: Enabled, Property>> + properties: { + enabled: boolean + data: Property[] + } } diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts index 68c0d0a..332b95c 100644 --- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts +++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts @@ -35,6 +35,10 @@ import { gatesInSelection } from '../helpers/gatesInSelection' import { selectionType } from '../types/selectionType' import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected' import { GateInitter } from '../types/GateInitter' +import { + open as propsAreOpen, + id as propsModalId +} from '../../logic-gates/subjects/LogicGatePropsSubjects' export class SimulationRenderer { public mouseDownOutput = new Subject() @@ -89,29 +93,34 @@ export class SimulationRenderer { // because if we have 2 overlapping gates, // we want to select the one on top for (let index = gates.length - 1; index >= 0; index--) { - const { transform, id, pins } = gates[index] + const { transform, id, pins, template } = gates[index] - if ( - event.button === mouseButtons.drag && - pointInSquare(worldPosition, transform) - ) { - gates[index].onClick() + if (pointInSquare(worldPosition, transform)) { + if (event.button === mouseButtons.drag) { + gates[index].onClick() - this.mouseState |= 1 + this.mouseState |= 1 - if (!idIsSelected(this, id)) { - this.clearSelection() - addIdToSelection(this, 'temporary', id) - } + if (!idIsSelected(this, id)) { + this.clearSelection() + addIdToSelection(this, 'temporary', id) + } - const gateNode = this.simulation.gates.get(id) + const gateNode = this.simulation.gates.get(id) - if (gateNode) { - return this.simulation.gates.moveOnTop(gateNode) - } else { - throw new SimulationError( - `Cannot find gate with id ${id}` - ) + if (gateNode) { + return this.simulation.gates.moveOnTop(gateNode) + } else { + throw new SimulationError( + `Cannot find gate with id ${id}` + ) + } + } else if ( + event.button === mouseButtons.properties && + template.properties.enabled + ) { + propsModalId.next(id) + return propsAreOpen.next(true) } } @@ -313,21 +322,15 @@ export class SimulationRenderer { } public reloadSave() { - try { - dumpSimulation(this) + dumpSimulation(this) - const current = currentStore.get() - const save = saveStore.get(current) + const current = currentStore.get() + const save = saveStore.get(current) - if (!save) return - if (!(save.simulation || save.camera)) return + if (!save) return + if (!(save.simulation || save.camera)) return - this.loadSave(save) - } catch (e) { - throw new Error( - `An error occured while loading the save: ${e as Error}` - ) - } + this.loadSave(save) } /** diff --git a/src/modules/simulationRenderer/constants.ts b/src/modules/simulationRenderer/constants.ts index cf9b32f..342c716 100644 --- a/src/modules/simulationRenderer/constants.ts +++ b/src/modules/simulationRenderer/constants.ts @@ -37,15 +37,13 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = { export const imageQuality: vector2 = [100, 100] -export const mouseButtons: Record< - 'zoom' | 'pan' | 'drag' | 'select' | 'unselect', - mouseButton -> = { +export const mouseButtons = { zoom: 1, drag: 0, pan: 2, select: 0, - unselect: 0 + unselect: 0, + properties: 2 } export const shiftInput = new KeyboardInput('shift')