feat: configurable ics:)
This commit is contained in:
parent
7df80284f9
commit
a9c9ba0b5f
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"prettier.eslintIntegration": true,
|
||||
"explorer.autoReveal": false
|
||||
"explorer.autoReveal": false,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ module.exports = {
|
|||
'@babel/preset-typescript'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }]
|
||||
|
|
37
package-lock.json
generated
37
package-lock.json
generated
|
@ -672,6 +672,22 @@
|
|||
"@babel/plugin-syntax-optional-catch-binding": "^7.2.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-optional-chaining": {
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz",
|
||||
"integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.8.3",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
|
||||
"integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-unicode-property-regex": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz",
|
||||
|
@ -746,6 +762,21 @@
|
|||
"@babel/helper-plugin-utils": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-optional-chaining": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
|
||||
"integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
|
||||
"integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-typescript": {
|
||||
"version": "7.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz",
|
||||
|
@ -10461,9 +10492,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
|
||||
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"typescript": "^3.5.2",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.36.1",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.7.2",
|
||||
|
@ -46,6 +46,7 @@
|
|||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@eix-js/utils": "0.0.6",
|
||||
"@material-ui/core": "^4.2.1",
|
||||
"deepmerge": "^4.0.0",
|
||||
|
|
1
src/common/lang/record/types/ValueOf.ts
Normal file
1
src/common/lang/record/types/ValueOf.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export type ValueOf<T> = T[keyof T]
|
17
src/common/lazy/classes/Lazy.ts
Normal file
17
src/common/lazy/classes/Lazy.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
export class Lazy<T> {
|
||||
private value: T | null = null
|
||||
|
||||
/**
|
||||
* SImple encoding of lazy values
|
||||
* @param getter a function o get the lazy value
|
||||
*/
|
||||
public constructor(private getter: () => T) {}
|
||||
|
||||
public get(value) {
|
||||
if (this.value === null) {
|
||||
this.value = this.getter()
|
||||
}
|
||||
|
||||
return this.value
|
||||
}
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
import { SimulationState } from '../../saving/types/SimulationSave'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { GateTemplate } from '../../simulation/types/GateTemplate'
|
||||
import {
|
||||
GateTemplate,
|
||||
Property,
|
||||
PropGroup,
|
||||
isGroup
|
||||
} from '../../simulation/types/GateTemplate'
|
||||
import {
|
||||
simulationInputCount,
|
||||
simulationOutputCount
|
||||
|
@ -13,6 +18,8 @@ import { fromSimulationState } from '../../saving/helpers/fromState'
|
|||
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
||||
import { getSimulationState } from '../../saving/helpers/getState'
|
||||
import { categories } from '../../saving/data/categories'
|
||||
import { getTemplateSafely } from '../../logic-gates/helpers/getTemplateSafely'
|
||||
import { reservedPropNames } from '../../simulation/constants'
|
||||
|
||||
/**
|
||||
* Compiles a simulation into a logicGate
|
||||
|
@ -35,6 +42,8 @@ export const compileIc = (state: SimulationState) => {
|
|||
const inputCount = simulationInputCount(cleanState.gates)
|
||||
const outputCount = simulationOutputCount(cleanState.gates)
|
||||
|
||||
const props = cleanState.gates.filter(({ props }) => props.external)
|
||||
|
||||
const result: DeepPartial<GateTemplate> = {
|
||||
metadata: {
|
||||
name
|
||||
|
@ -52,6 +61,24 @@ export const compileIc = (state: SimulationState) => {
|
|||
material: {
|
||||
type: 'image',
|
||||
fill: require('../../../assets/ic')
|
||||
},
|
||||
properties: {
|
||||
enabled: !!props.length,
|
||||
data: props.map((gate) => {
|
||||
const template = getTemplateSafely(gate.template)
|
||||
return {
|
||||
groupName: gate.props.label,
|
||||
props: template.properties.data.map((prop) => {
|
||||
if (isGroup(prop)) {
|
||||
return prop
|
||||
}
|
||||
return {
|
||||
...prop,
|
||||
base: gate.props[prop.name]
|
||||
}
|
||||
})
|
||||
} as PropGroup
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,21 +17,24 @@ $gate-props-margin: 1rem;
|
|||
}
|
||||
|
||||
div #gate-properties-container {
|
||||
@include flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: $grey;
|
||||
padding: $gate-props-margin * 4;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#gate-props-divider {
|
||||
margin-top: $gate-props-margin;
|
||||
margin-bottom: $gate-props-margin;
|
||||
padding: $gate-props-margin * 4;
|
||||
box-sizing: border-box;
|
||||
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div #gate-props-title {
|
||||
color: white;
|
||||
font-size: 3em;
|
||||
|
||||
margin-bottom: 2 * $gate-props-margin;
|
||||
}
|
||||
|
||||
div .gate-prop-container {
|
||||
|
@ -45,6 +48,12 @@ div .gate-prop-container {
|
|||
}
|
||||
}
|
||||
|
||||
div .gate-prop-group-container {
|
||||
@include flex;
|
||||
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
div #save-props {
|
||||
width: 50%;
|
||||
margin: $gate-props-margin * 2;
|
||||
|
|
|
@ -1,45 +1,83 @@
|
|||
import './GateProperties.scss'
|
||||
import React, { ChangeEvent, MouseEvent } from 'react'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||
import { Property } from '../../simulation/types/GateTemplate'
|
||||
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
|
||||
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'
|
||||
import { mapRecord } from '../../../common/lang/record/map'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { Gate, GateProps } from '../../simulation/classes/Gate'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
export interface GatePropertyProps {
|
||||
raw: Property
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import ExpansionPanel from '@material-ui/core/ExpansionPanel'
|
||||
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
|
||||
import Icon from '@material-ui/core/Icon'
|
||||
interface GatePropertyProps<T extends Property = Property> {
|
||||
raw: T
|
||||
gate: Gate
|
||||
props: GateProps
|
||||
}
|
||||
|
||||
const emptyInput = <></>
|
||||
|
||||
const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
|
||||
if (isGroup(raw)) {
|
||||
return (
|
||||
<ExpansionPanel>
|
||||
<ExpansionPanelSummary
|
||||
expandIcon={<Icon> expand_more</Icon>}
|
||||
aria-controls={raw.groupName}
|
||||
id={raw.groupName}
|
||||
>
|
||||
<Typography>{raw.groupName}</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<div className="gate-prop-group-container">
|
||||
{raw.props.map((propTemplate, index) => (
|
||||
<GateProperty
|
||||
key={index}
|
||||
raw={propTemplate}
|
||||
gate={gate}
|
||||
props={props[raw.groupName] as GateProps}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
)
|
||||
}
|
||||
|
||||
return <GateRawProperty raw={raw} gate={gate} props={props} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single props of the gate
|
||||
*
|
||||
* @param param0 The props passed to the component
|
||||
*/
|
||||
export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
||||
const GateRawProperty = ({
|
||||
props,
|
||||
raw,
|
||||
gate
|
||||
}: GatePropertyProps & { raw: RawProp }) => {
|
||||
const { name } = raw
|
||||
const prop = gate.props[name]
|
||||
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
|
||||
const outputSnapshot = useObservable(() => prop, '')
|
||||
|
||||
// rerender when the internal checkbox changes
|
||||
const internal = useObservable(
|
||||
// rerender when the external checkbox changes
|
||||
const external = useObservable(
|
||||
() =>
|
||||
gate.props.internal.pipe(
|
||||
map((value) => value && name !== 'internal')
|
||||
gate.props.external.pipe(
|
||||
map((value) => value && name !== 'external')
|
||||
),
|
||||
false
|
||||
)
|
||||
|
||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
|
||||
internal ? '(default value)' : ''
|
||||
external && name !== 'label' ? '(default value)' : ''
|
||||
}:`
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -55,19 +93,19 @@ export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
|||
if (raw.type !== 'boolean') {
|
||||
prop.next(value)
|
||||
}
|
||||
|
||||
if (raw.needsUpdate === true) {
|
||||
gate.update()
|
||||
}
|
||||
}
|
||||
|
||||
let input = (() => {
|
||||
if (
|
||||
raw.show &&
|
||||
!raw.show(
|
||||
mapRecord(gate.props, (subject) => subject.value),
|
||||
gate
|
||||
const displayExternal = () =>
|
||||
getRendererSafely().simulation.mode === 'ic' &&
|
||||
gate.env === 'global' &&
|
||||
!gate.template.properties.data.some(
|
||||
(prop) => (prop as RawProp).needsUpdate
|
||||
)
|
||||
|
||||
if (
|
||||
(raw.name === 'external' && !displayExternal()) ||
|
||||
(raw.name === 'label' && !external)
|
||||
) {
|
||||
return emptyInput
|
||||
}
|
||||
|
@ -147,12 +185,12 @@ const GateProperties = () => {
|
|||
}}
|
||||
>
|
||||
<div id="gate-props-title">Gate properties</div>
|
||||
<Divider id="gate-props-divider" />
|
||||
|
||||
{gate.template.properties.data.map((raw, index) => {
|
||||
{gate.template.properties.data.map((prop, index) => {
|
||||
return (
|
||||
<GatePropery
|
||||
raw={raw}
|
||||
<GateProperty
|
||||
props={gate.props}
|
||||
raw={prop}
|
||||
gate={gate}
|
||||
key={`${index}-${id.value}`}
|
||||
/>
|
||||
|
|
|
@ -8,11 +8,15 @@ export interface TransformState {
|
|||
rotation: number
|
||||
}
|
||||
|
||||
export type PropsSave = {
|
||||
[K in string]: string | number | boolean | PropsSave
|
||||
}
|
||||
|
||||
export interface GateState {
|
||||
transform: TransformState
|
||||
id: number
|
||||
template: string
|
||||
props: Record<string, string | number | boolean>
|
||||
props: PropsSave
|
||||
}
|
||||
|
||||
export interface CameraState {
|
||||
|
|
|
@ -2,26 +2,25 @@ import { Transform } from '../../../common/math/classes/Transform'
|
|||
import { BehaviorSubject, fromEvent } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { getWidth } from '../helpers/getWidth'
|
||||
import { sidebarWidth } from '../../core/constants'
|
||||
|
||||
const width = new BehaviorSubject(getWidth())
|
||||
const height = new BehaviorSubject(window.innerHeight)
|
||||
|
||||
const resize = fromEvent(window, 'resize')
|
||||
|
||||
resize.pipe(map(getWidth)).subscribe(val => width.next(val))
|
||||
resize.pipe(map(() => window.innerHeight)).subscribe(val => height.next(val))
|
||||
resize.pipe(map(getWidth)).subscribe((val) => width.next(val))
|
||||
resize.pipe(map(() => window.innerHeight)).subscribe((val) => height.next(val))
|
||||
|
||||
/**
|
||||
* The main screen transform
|
||||
*/
|
||||
const Screen = new Transform()
|
||||
|
||||
width.subscribe(currentWidth => {
|
||||
width.subscribe((currentWidth) => {
|
||||
Screen.width = currentWidth
|
||||
})
|
||||
|
||||
height.subscribe(currentHeight => {
|
||||
height.subscribe((currentHeight) => {
|
||||
Screen.height = currentHeight
|
||||
})
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import { Transform } from '../../../common/math/classes/Transform'
|
||||
import { Pin } from './Pin'
|
||||
import { GateTemplate, PinCount } from '../types/GateTemplate'
|
||||
import {
|
||||
GateTemplate,
|
||||
PinCount,
|
||||
isGroup,
|
||||
Property
|
||||
} from '../types/GateTemplate'
|
||||
import { idStore } from '../stores/idStore'
|
||||
import { Context, InitialisationContext } from '../../activation/types/Context'
|
||||
import { toFunction } from '../../activation/helpers/toFunction'
|
||||
import { Subscription, BehaviorSubject, asapScheduler, animationFrameScheduler } from 'rxjs'
|
||||
import {
|
||||
Subscription,
|
||||
BehaviorSubject,
|
||||
asapScheduler,
|
||||
animationFrameScheduler
|
||||
} from 'rxjs'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { getGateTimePipes } from '../helpers/getGateTimePipes'
|
||||
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
|
||||
|
@ -16,6 +26,8 @@ import { Wire } from './Wire'
|
|||
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
|
||||
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
|
||||
import { tap, observeOn } from 'rxjs/operators'
|
||||
import { PropsSave } from '../../saving/types/SimulationSave'
|
||||
import { ValueOf } from '../../../common/lang/record/types/ValueOf'
|
||||
|
||||
/**
|
||||
* The interface for the pins of a gate
|
||||
|
@ -47,6 +59,13 @@ export interface GateFunctions {
|
|||
onClick: GateFunction
|
||||
}
|
||||
|
||||
export type GateProps = {
|
||||
[K in keyof PropsSave]: BehaviorSubject<PropsSave[K]> | GateProps
|
||||
} & {
|
||||
external: BehaviorSubject<boolean>
|
||||
label: BehaviorSubject<string>
|
||||
}
|
||||
|
||||
export class Gate {
|
||||
/**
|
||||
* The transform of the gate
|
||||
|
@ -126,10 +145,7 @@ export class Gate {
|
|||
/**
|
||||
* The props used by the activation function (the same as memory but presists)
|
||||
*/
|
||||
public props: Record<
|
||||
string,
|
||||
BehaviorSubject<string | number | boolean>
|
||||
> = {}
|
||||
public props: GateProps = {} as GateProps
|
||||
|
||||
/**
|
||||
* The main logic gate class
|
||||
|
@ -140,10 +156,9 @@ export class Gate {
|
|||
public constructor(
|
||||
template: DeepPartial<GateTemplate> = {},
|
||||
id?: number,
|
||||
props: Record<string, string | number | boolean> = {}
|
||||
props: PropsSave = {}
|
||||
) {
|
||||
this.template = completeTemplate(template)
|
||||
|
||||
this.transform.scale = this.template.shape.scale
|
||||
|
||||
if (this.template.material.type === 'color') {
|
||||
|
@ -204,9 +219,7 @@ export class Gate {
|
|||
|
||||
if (!state) {
|
||||
throw new SimulationError(
|
||||
`Cannot run ic ${
|
||||
this.template.metadata.name
|
||||
} - save not found`
|
||||
`Cannot run ic ${this.template.metadata.name} - save not found`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -219,30 +232,26 @@ export class Gate {
|
|||
const gates = Array.from(this.ghostSimulation.gates)
|
||||
|
||||
const inputs = gates
|
||||
.filter(gate => gate.template.integration.input)
|
||||
.filter((gate) => gate.template.integration.input)
|
||||
.sort(sortByPosition)
|
||||
.map(gate => gate.wrapPins(gate._pins.outputs))
|
||||
.map((gate) => gate.wrapPins(gate._pins.outputs))
|
||||
.flat()
|
||||
|
||||
const outputs = gates
|
||||
.filter(gate => gate.template.integration.output)
|
||||
.filter((gate) => gate.template.integration.output)
|
||||
.sort(sortByPosition)
|
||||
.map(gate => gate.wrapPins(gate._pins.inputs))
|
||||
.map((gate) => gate.wrapPins(gate._pins.inputs))
|
||||
.flat()
|
||||
|
||||
if (inputs.length !== this._pins.inputs.length) {
|
||||
throw new SimulationError(
|
||||
`Input count needs to match with the container gate: ${
|
||||
inputs.length
|
||||
} !== ${this._pins.inputs.length}`
|
||||
`Input count needs to match with the container gate: ${inputs.length} !== ${this._pins.inputs.length}`
|
||||
)
|
||||
}
|
||||
|
||||
if (outputs.length !== this._pins.outputs.length) {
|
||||
throw new SimulationError(
|
||||
`Output count needs to match with the container gate: ${
|
||||
outputs.length
|
||||
} !== ${this._pins.outputs.length}`
|
||||
`Output count needs to match with the container gate: ${outputs.length} !== ${this._pins.outputs.length}`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -267,28 +276,95 @@ export class Gate {
|
|||
this.assignProps(props)
|
||||
}
|
||||
|
||||
private updateNestedProp(
|
||||
path: string[],
|
||||
value: ValueOf<PropsSave>,
|
||||
gate: Gate = this
|
||||
) {
|
||||
if (!path.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (path.length === 1) {
|
||||
const subject = gate.props[path[0]]
|
||||
|
||||
if (subject instanceof BehaviorSubject) {
|
||||
subject.next(value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const nextGates = [...gate.ghostSimulation.gates].filter(
|
||||
(gate) => gate.props?.label?.value === path[0]
|
||||
)
|
||||
|
||||
for (const nextGate of nextGates) {
|
||||
this.updateNestedProp(path.slice(1), value, nextGate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the props passed to the gate and mere them with the base ones
|
||||
*/
|
||||
private assignProps(props: Record<string, string | boolean | number>) {
|
||||
private assignProps(
|
||||
source: PropsSave,
|
||||
props: Property[] = this.template.properties.data,
|
||||
target: GateProps = this.props,
|
||||
path: string[] = []
|
||||
) {
|
||||
let shouldUpdate = false
|
||||
|
||||
if (this.template.properties.enabled) {
|
||||
for (const { base, name, needsUpdate } of this.template.properties
|
||||
.data) {
|
||||
this.props[name] = new BehaviorSubject(
|
||||
props.hasOwnProperty(name) ? props[name] : base
|
||||
for (const prop of props) {
|
||||
if (isGroup(prop)) {
|
||||
const { groupName } = prop
|
||||
target[groupName] = {} as GateProps
|
||||
const needsUpdate = this.assignProps(
|
||||
typeof source[groupName] === 'object'
|
||||
? (source[groupName] as PropsSave)
|
||||
: {},
|
||||
prop.props,
|
||||
target[groupName] as GateProps,
|
||||
[...path, groupName]
|
||||
)
|
||||
|
||||
if (!shouldUpdate && needsUpdate) {
|
||||
if (needsUpdate) {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const { name, base, needsUpdate } = prop
|
||||
|
||||
const subject = new BehaviorSubject(
|
||||
source.hasOwnProperty(name) ? source[name] : base
|
||||
)
|
||||
|
||||
target[name] = subject
|
||||
|
||||
this.subscriptions.push(
|
||||
subject.subscribe((value) => {
|
||||
if (needsUpdate && path.length === 0) {
|
||||
return this.update()
|
||||
}
|
||||
|
||||
if (path.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.updateNestedProp([...path, name], value)
|
||||
})
|
||||
)
|
||||
|
||||
if (needsUpdate) {
|
||||
shouldUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
this.update()
|
||||
}
|
||||
return shouldUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -315,11 +391,15 @@ export class Gate {
|
|||
/**
|
||||
* Used to get the props as an object
|
||||
*/
|
||||
public getProps() {
|
||||
const props: Record<string, string | boolean | number> = {}
|
||||
public getProps(target = this.props) {
|
||||
const props: PropsSave = {}
|
||||
|
||||
for (const key in this.props) {
|
||||
props[key] = this.props[key].value
|
||||
for (const [key, value] of Object.entries(target)) {
|
||||
if (value instanceof BehaviorSubject) {
|
||||
props[key] = value.value
|
||||
} else {
|
||||
props[key] = this.getProps(value)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
|
@ -361,7 +441,7 @@ export class Gate {
|
|||
*/
|
||||
public getContext(): Context {
|
||||
const maxLength = Math.max(
|
||||
...this._pins.inputs.map(pin => pin.state.value.length)
|
||||
...this._pins.inputs.map((pin) => pin.state.value.length)
|
||||
)
|
||||
|
||||
const toLength = (
|
||||
|
@ -412,7 +492,10 @@ export class Gate {
|
|||
return this.props[name].value
|
||||
},
|
||||
setProperty: (name: string, value: string | number | boolean) => {
|
||||
this.props[name].next(value)
|
||||
const subject = this.props[name]
|
||||
if (subject instanceof BehaviorSubject) {
|
||||
subject.next(value)
|
||||
}
|
||||
},
|
||||
innerText: (value: string) => {
|
||||
this.text.inner.next(value)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GateTemplate } from './types/GateTemplate'
|
||||
import { GateTemplate, Property, RawProp } from './types/GateTemplate'
|
||||
import { categories } from '../saving/data/categories'
|
||||
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
|
||||
|
||||
export const DefaultGateTemplate: GateTemplate = {
|
||||
metadata: {
|
||||
|
@ -49,7 +50,6 @@ export const DefaultGateTemplate: GateTemplate = {
|
|||
input: false,
|
||||
output: false
|
||||
},
|
||||
info: [],
|
||||
tags: ['base'],
|
||||
properties: {
|
||||
enabled: false,
|
||||
|
@ -57,18 +57,12 @@ export const DefaultGateTemplate: GateTemplate = {
|
|||
{
|
||||
type: 'boolean',
|
||||
base: false,
|
||||
name: 'internal',
|
||||
show: (_, gate) =>
|
||||
gate.env === 'global' &&
|
||||
!gate.template.properties.data.some(
|
||||
(prop) => prop.needsUpdate
|
||||
)
|
||||
name: 'external'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
base: 'my-logic-gate',
|
||||
name: 'label',
|
||||
show: ({ internal }: { internal: boolean }) => internal
|
||||
name: 'label'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -78,3 +72,10 @@ export const DefaultGateTemplate: GateTemplate = {
|
|||
},
|
||||
category: categories.basic
|
||||
}
|
||||
|
||||
/**
|
||||
* Prop names which need to not be overriten
|
||||
*/
|
||||
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
|
||||
({ name }: RawProp) => name
|
||||
)
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
import { Gate } from '../classes/Gate'
|
||||
|
||||
export interface PinCount {
|
||||
variable: boolean
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface Property<
|
||||
export type PropGroup<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> {
|
||||
> = {
|
||||
groupName: string
|
||||
props: Property<T>[]
|
||||
}
|
||||
|
||||
export const isGroup = (prop: Property): prop is PropGroup =>
|
||||
(prop as PropGroup).groupName !== undefined
|
||||
|
||||
export type RawProp<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> = {
|
||||
type: 'number' | 'string' | 'text' | 'boolean'
|
||||
base: T
|
||||
name: string
|
||||
needsUpdate?: boolean
|
||||
show?: (
|
||||
obj: Record<string, string | boolean | number>,
|
||||
gate: Gate
|
||||
) => boolean
|
||||
}
|
||||
export type Property<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> = PropGroup<T> | RawProp<T>
|
||||
|
||||
export interface Material {
|
||||
type: 'color' | 'image'
|
||||
|
@ -74,7 +82,6 @@ export interface GateTemplate {
|
|||
input: boolean
|
||||
output: boolean
|
||||
}
|
||||
info: string[]
|
||||
tags: GateTag[]
|
||||
properties: {
|
||||
enabled: boolean
|
||||
|
|
|
@ -80,7 +80,7 @@ export class SimulationRenderer {
|
|||
|
||||
public updateWheelListener(ref: RefObject<HTMLCanvasElement>) {
|
||||
if (ref.current) {
|
||||
ref.current.addEventListener('wheel', event => {
|
||||
ref.current.addEventListener('wheel', (event) => {
|
||||
if (!modalIsOpen() && location.pathname === '/') {
|
||||
event.preventDefault()
|
||||
|
||||
|
@ -119,7 +119,7 @@ export class SimulationRenderer {
|
|||
* @throws SimulationError if the id doesnt have a data prop
|
||||
*/
|
||||
public getSelected(): Gate[] {
|
||||
return setToArray(this.allSelectedIds()).map(id => {
|
||||
return setToArray(this.allSelectedIds()).map((id) => {
|
||||
const gate = this.simulation.gates.get(id)
|
||||
|
||||
if (!gate) {
|
||||
|
|
|
@ -4,15 +4,11 @@
|
|||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"experimentalDecorators": true,
|
||||
"target": "esnext",
|
||||
"target": "ESNext",
|
||||
"downlevelIteration": true,
|
||||
"strictNullChecks": true,
|
||||
"module": "esnext"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in a new issue