feat: configurable ics:)

This commit is contained in:
Matei Adriel 2020-04-13 23:39:00 +03:00
parent 7df80284f9
commit a9c9ba0b5f
16 changed files with 327 additions and 111 deletions

View file

@ -1,5 +1,6 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"prettier.eslintIntegration": true, "prettier.eslintIntegration": true,
"explorer.autoReveal": false "explorer.autoReveal": false,
"typescript.tsdk": "node_modules/typescript/lib"
} }

View file

@ -5,6 +5,7 @@ module.exports = {
'@babel/preset-typescript' '@babel/preset-typescript'
], ],
plugins: [ plugins: [
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-syntax-dynamic-import', '@babel/plugin-syntax-dynamic-import',
['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }] ['@babel/plugin-proposal-class-properties', { loose: true }]
@ -14,4 +15,4 @@ module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]] presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
} }
} }
} }

37
package-lock.json generated
View file

@ -672,6 +672,22 @@
"@babel/plugin-syntax-optional-catch-binding": "^7.2.0" "@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": { "@babel/plugin-proposal-unicode-property-regex": {
"version": "7.4.4", "version": "7.4.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", "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/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": { "@babel/plugin-syntax-typescript": {
"version": "7.3.3", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz",
@ -10461,9 +10492,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.5.3", "version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {

View file

@ -38,7 +38,7 @@
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"typescript": "^3.5.2", "typescript": "^3.8.3",
"webpack": "^4.36.1", "webpack": "^4.36.1",
"webpack-cli": "^3.3.6", "webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2", "webpack-dev-server": "^3.7.2",
@ -46,6 +46,7 @@
"webpack-node-externals": "^1.7.2" "webpack-node-externals": "^1.7.2"
}, },
"dependencies": { "dependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@eix-js/utils": "0.0.6", "@eix-js/utils": "0.0.6",
"@material-ui/core": "^4.2.1", "@material-ui/core": "^4.2.1",
"deepmerge": "^4.0.0", "deepmerge": "^4.0.0",

View file

@ -0,0 +1 @@
export type ValueOf<T> = T[keyof T]

View 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
}
}

View file

@ -1,6 +1,11 @@
import { SimulationState } from '../../saving/types/SimulationSave' import { SimulationState } from '../../saving/types/SimulationSave'
import { SimulationError } from '../../errors/classes/SimulationError' import { SimulationError } from '../../errors/classes/SimulationError'
import { GateTemplate } from '../../simulation/types/GateTemplate' import {
GateTemplate,
Property,
PropGroup,
isGroup
} from '../../simulation/types/GateTemplate'
import { import {
simulationInputCount, simulationInputCount,
simulationOutputCount simulationOutputCount
@ -13,6 +18,8 @@ import { fromSimulationState } from '../../saving/helpers/fromState'
import { cleanSimulation } from '../../simulation-actions/helpers/clean' import { cleanSimulation } from '../../simulation-actions/helpers/clean'
import { getSimulationState } from '../../saving/helpers/getState' import { getSimulationState } from '../../saving/helpers/getState'
import { categories } from '../../saving/data/categories' import { categories } from '../../saving/data/categories'
import { getTemplateSafely } from '../../logic-gates/helpers/getTemplateSafely'
import { reservedPropNames } from '../../simulation/constants'
/** /**
* Compiles a simulation into a logicGate * Compiles a simulation into a logicGate
@ -35,6 +42,8 @@ export const compileIc = (state: SimulationState) => {
const inputCount = simulationInputCount(cleanState.gates) const inputCount = simulationInputCount(cleanState.gates)
const outputCount = simulationOutputCount(cleanState.gates) const outputCount = simulationOutputCount(cleanState.gates)
const props = cleanState.gates.filter(({ props }) => props.external)
const result: DeepPartial<GateTemplate> = { const result: DeepPartial<GateTemplate> = {
metadata: { metadata: {
name name
@ -52,6 +61,24 @@ export const compileIc = (state: SimulationState) => {
material: { material: {
type: 'image', type: 'image',
fill: require('../../../assets/ic') 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
})
} }
} }

View file

@ -17,21 +17,24 @@ $gate-props-margin: 1rem;
} }
div #gate-properties-container { div #gate-properties-container {
@include flex; display: flex;
flex-direction: column;
background-color: $grey; background-color: $grey;
padding: $gate-props-margin * 4;
border-radius: 1em; border-radius: 1em;
}
#gate-props-divider { padding: $gate-props-margin * 4;
margin-top: $gate-props-margin; box-sizing: border-box;
margin-bottom: $gate-props-margin;
max-height: 80vh;
overflow: auto;
} }
div #gate-props-title { div #gate-props-title {
color: white; color: white;
font-size: 3em; font-size: 3em;
margin-bottom: 2 * $gate-props-margin;
} }
div .gate-prop-container { 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 { div #save-props {
width: 50%; width: 50%;
margin: $gate-props-margin * 2; margin: $gate-props-margin * 2;

View file

@ -1,45 +1,83 @@
import './GateProperties.scss' import './GateProperties.scss'
import React, { ChangeEvent, MouseEvent } from 'react' import React, { ChangeEvent } from 'react'
import { getRendererSafely } from '../helpers/getRendererSafely' 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 { useObservable } from 'rxjs-hooks'
import Divider from '@material-ui/core/Divider' import Divider from '@material-ui/core/Divider'
import TextField from '@material-ui/core/TextField' import TextField from '@material-ui/core/TextField'
import CheckBox from '@material-ui/core/Checkbox' import CheckBox from '@material-ui/core/Checkbox'
import { open, id } from '../subjects/LogicGatePropsSubjects' import { open, id } from '../subjects/LogicGatePropsSubjects'
import { Gate } from '../../simulation/classes/Gate' import { Gate, GateProps } from '../../simulation/classes/Gate'
import { mapRecord } from '../../../common/lang/record/map'
import { BehaviorSubject } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { BehaviorSubject } from 'rxjs'
export interface GatePropertyProps { import ExpansionPanel from '@material-ui/core/ExpansionPanel'
raw: Property 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 gate: Gate
props: GateProps
} }
const emptyInput = <></> 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 * Renders a single props of the gate
* *
* @param param0 The props passed to the component * @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 { name } = raw
const prop = gate.props[name] const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
const outputSnapshot = useObservable(() => prop, '') const outputSnapshot = useObservable(() => prop, '')
// rerender when the internal checkbox changes // rerender when the external checkbox changes
const internal = useObservable( const external = useObservable(
() => () =>
gate.props.internal.pipe( gate.props.external.pipe(
map((value) => value && name !== 'internal') map((value) => value && name !== 'external')
), ),
false false
) )
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${ const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
internal ? '(default value)' : '' external && name !== 'label' ? '(default value)' : ''
}:` }:`
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
@ -55,19 +93,19 @@ export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
if (raw.type !== 'boolean') { if (raw.type !== 'boolean') {
prop.next(value) prop.next(value)
} }
if (raw.needsUpdate === true) {
gate.update()
}
} }
let input = (() => { let input = (() => {
if ( const displayExternal = () =>
raw.show && getRendererSafely().simulation.mode === 'ic' &&
!raw.show( gate.env === 'global' &&
mapRecord(gate.props, (subject) => subject.value), !gate.template.properties.data.some(
gate (prop) => (prop as RawProp).needsUpdate
) )
if (
(raw.name === 'external' && !displayExternal()) ||
(raw.name === 'label' && !external)
) { ) {
return emptyInput return emptyInput
} }
@ -147,12 +185,12 @@ const GateProperties = () => {
}} }}
> >
<div id="gate-props-title">Gate properties</div> <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 ( return (
<GatePropery <GateProperty
raw={raw} props={gate.props}
raw={prop}
gate={gate} gate={gate}
key={`${index}-${id.value}`} key={`${index}-${id.value}`}
/> />

View file

@ -8,11 +8,15 @@ export interface TransformState {
rotation: number rotation: number
} }
export type PropsSave = {
[K in string]: string | number | boolean | PropsSave
}
export interface GateState { export interface GateState {
transform: TransformState transform: TransformState
id: number id: number
template: string template: string
props: Record<string, string | number | boolean> props: PropsSave
} }
export interface CameraState { export interface CameraState {

View file

@ -2,26 +2,25 @@ import { Transform } from '../../../common/math/classes/Transform'
import { BehaviorSubject, fromEvent } from 'rxjs' import { BehaviorSubject, fromEvent } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { getWidth } from '../helpers/getWidth' import { getWidth } from '../helpers/getWidth'
import { sidebarWidth } from '../../core/constants'
const width = new BehaviorSubject(getWidth()) const width = new BehaviorSubject(getWidth())
const height = new BehaviorSubject(window.innerHeight) const height = new BehaviorSubject(window.innerHeight)
const resize = fromEvent(window, 'resize') const resize = fromEvent(window, 'resize')
resize.pipe(map(getWidth)).subscribe(val => width.next(val)) resize.pipe(map(getWidth)).subscribe((val) => width.next(val))
resize.pipe(map(() => window.innerHeight)).subscribe(val => height.next(val)) resize.pipe(map(() => window.innerHeight)).subscribe((val) => height.next(val))
/** /**
* The main screen transform * The main screen transform
*/ */
const Screen = new Transform() const Screen = new Transform()
width.subscribe(currentWidth => { width.subscribe((currentWidth) => {
Screen.width = currentWidth Screen.width = currentWidth
}) })
height.subscribe(currentHeight => { height.subscribe((currentHeight) => {
Screen.height = currentHeight Screen.height = currentHeight
}) })

View file

@ -1,10 +1,20 @@
import { Transform } from '../../../common/math/classes/Transform' import { Transform } from '../../../common/math/classes/Transform'
import { Pin } from './Pin' import { Pin } from './Pin'
import { GateTemplate, PinCount } from '../types/GateTemplate' import {
GateTemplate,
PinCount,
isGroup,
Property
} from '../types/GateTemplate'
import { idStore } from '../stores/idStore' import { idStore } from '../stores/idStore'
import { Context, InitialisationContext } from '../../activation/types/Context' import { Context, InitialisationContext } from '../../activation/types/Context'
import { toFunction } from '../../activation/helpers/toFunction' 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 { SimulationError } from '../../errors/classes/SimulationError'
import { getGateTimePipes } from '../helpers/getGateTimePipes' import { getGateTimePipes } from '../helpers/getGateTimePipes'
import { ImageStore } from '../../simulationRenderer/stores/imageStore' import { ImageStore } from '../../simulationRenderer/stores/imageStore'
@ -16,6 +26,8 @@ import { Wire } from './Wire'
import { cleanSimulation } from '../../simulation-actions/helpers/clean' import { cleanSimulation } from '../../simulation-actions/helpers/clean'
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue' import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
import { tap, observeOn } from 'rxjs/operators' 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 * The interface for the pins of a gate
@ -47,6 +59,13 @@ export interface GateFunctions {
onClick: GateFunction onClick: GateFunction
} }
export type GateProps = {
[K in keyof PropsSave]: BehaviorSubject<PropsSave[K]> | GateProps
} & {
external: BehaviorSubject<boolean>
label: BehaviorSubject<string>
}
export class Gate { export class Gate {
/** /**
* The transform of the 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) * The props used by the activation function (the same as memory but presists)
*/ */
public props: Record< public props: GateProps = {} as GateProps
string,
BehaviorSubject<string | number | boolean>
> = {}
/** /**
* The main logic gate class * The main logic gate class
@ -140,10 +156,9 @@ export class Gate {
public constructor( public constructor(
template: DeepPartial<GateTemplate> = {}, template: DeepPartial<GateTemplate> = {},
id?: number, id?: number,
props: Record<string, string | number | boolean> = {} props: PropsSave = {}
) { ) {
this.template = completeTemplate(template) this.template = completeTemplate(template)
this.transform.scale = this.template.shape.scale this.transform.scale = this.template.shape.scale
if (this.template.material.type === 'color') { if (this.template.material.type === 'color') {
@ -204,9 +219,7 @@ export class Gate {
if (!state) { if (!state) {
throw new SimulationError( throw new SimulationError(
`Cannot run ic ${ `Cannot run ic ${this.template.metadata.name} - save not found`
this.template.metadata.name
} - save not found`
) )
} }
@ -219,30 +232,26 @@ export class Gate {
const gates = Array.from(this.ghostSimulation.gates) const gates = Array.from(this.ghostSimulation.gates)
const inputs = gates const inputs = gates
.filter(gate => gate.template.integration.input) .filter((gate) => gate.template.integration.input)
.sort(sortByPosition) .sort(sortByPosition)
.map(gate => gate.wrapPins(gate._pins.outputs)) .map((gate) => gate.wrapPins(gate._pins.outputs))
.flat() .flat()
const outputs = gates const outputs = gates
.filter(gate => gate.template.integration.output) .filter((gate) => gate.template.integration.output)
.sort(sortByPosition) .sort(sortByPosition)
.map(gate => gate.wrapPins(gate._pins.inputs)) .map((gate) => gate.wrapPins(gate._pins.inputs))
.flat() .flat()
if (inputs.length !== this._pins.inputs.length) { if (inputs.length !== this._pins.inputs.length) {
throw new SimulationError( throw new SimulationError(
`Input count needs to match with the container gate: ${ `Input count needs to match with the container gate: ${inputs.length} !== ${this._pins.inputs.length}`
inputs.length
} !== ${this._pins.inputs.length}`
) )
} }
if (outputs.length !== this._pins.outputs.length) { if (outputs.length !== this._pins.outputs.length) {
throw new SimulationError( throw new SimulationError(
`Output count needs to match with the container gate: ${ `Output count needs to match with the container gate: ${outputs.length} !== ${this._pins.outputs.length}`
outputs.length
} !== ${this._pins.outputs.length}`
) )
} }
@ -267,28 +276,95 @@ export class Gate {
this.assignProps(props) 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 * 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 let shouldUpdate = false
if (this.template.properties.enabled) { if (this.template.properties.enabled) {
for (const { base, name, needsUpdate } of this.template.properties for (const prop of props) {
.data) { if (isGroup(prop)) {
this.props[name] = new BehaviorSubject( const { groupName } = prop
props.hasOwnProperty(name) ? props[name] : base 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 (needsUpdate) {
shouldUpdate = true
}
continue
}
const { name, base, needsUpdate } = prop
const subject = new BehaviorSubject(
source.hasOwnProperty(name) ? source[name] : base
) )
if (!shouldUpdate && needsUpdate) { 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 shouldUpdate = true
} }
} }
} }
if (shouldUpdate) { return shouldUpdate
this.update()
}
} }
/** /**
@ -315,11 +391,15 @@ export class Gate {
/** /**
* Used to get the props as an object * Used to get the props as an object
*/ */
public getProps() { public getProps(target = this.props) {
const props: Record<string, string | boolean | number> = {} const props: PropsSave = {}
for (const key in this.props) { for (const [key, value] of Object.entries(target)) {
props[key] = this.props[key].value if (value instanceof BehaviorSubject) {
props[key] = value.value
} else {
props[key] = this.getProps(value)
}
} }
return props return props
@ -361,7 +441,7 @@ export class Gate {
*/ */
public getContext(): Context { public getContext(): Context {
const maxLength = Math.max( const maxLength = Math.max(
...this._pins.inputs.map(pin => pin.state.value.length) ...this._pins.inputs.map((pin) => pin.state.value.length)
) )
const toLength = ( const toLength = (
@ -412,7 +492,10 @@ export class Gate {
return this.props[name].value return this.props[name].value
}, },
setProperty: (name: string, value: string | number | boolean) => { 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) => { innerText: (value: string) => {
this.text.inner.next(value) this.text.inner.next(value)

View file

@ -1,5 +1,6 @@
import { GateTemplate } from './types/GateTemplate' import { GateTemplate, Property, RawProp } from './types/GateTemplate'
import { categories } from '../saving/data/categories' import { categories } from '../saving/data/categories'
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
export const DefaultGateTemplate: GateTemplate = { export const DefaultGateTemplate: GateTemplate = {
metadata: { metadata: {
@ -49,7 +50,6 @@ export const DefaultGateTemplate: GateTemplate = {
input: false, input: false,
output: false output: false
}, },
info: [],
tags: ['base'], tags: ['base'],
properties: { properties: {
enabled: false, enabled: false,
@ -57,18 +57,12 @@ export const DefaultGateTemplate: GateTemplate = {
{ {
type: 'boolean', type: 'boolean',
base: false, base: false,
name: 'internal', name: 'external'
show: (_, gate) =>
gate.env === 'global' &&
!gate.template.properties.data.some(
(prop) => prop.needsUpdate
)
}, },
{ {
type: 'string', type: 'string',
base: 'my-logic-gate', base: 'my-logic-gate',
name: 'label', name: 'label'
show: ({ internal }: { internal: boolean }) => internal
} }
] ]
}, },
@ -78,3 +72,10 @@ export const DefaultGateTemplate: GateTemplate = {
}, },
category: categories.basic category: categories.basic
} }
/**
* Prop names which need to not be overriten
*/
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
({ name }: RawProp) => name
)

View file

@ -1,23 +1,31 @@
import { vector2 } from '../../../common/math/types/vector2' import { vector2 } from '../../../common/math/types/vector2'
import { Gate } from '../classes/Gate'
export interface PinCount { export interface PinCount {
variable: boolean variable: boolean
count: number count: number
} }
export interface Property< export type PropGroup<
T extends boolean | number | string = boolean | number | string 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' type: 'number' | 'string' | 'text' | 'boolean'
base: T base: T
name: string name: string
needsUpdate?: boolean 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 { export interface Material {
type: 'color' | 'image' type: 'color' | 'image'
@ -74,7 +82,6 @@ export interface GateTemplate {
input: boolean input: boolean
output: boolean output: boolean
} }
info: string[]
tags: GateTag[] tags: GateTag[]
properties: { properties: {
enabled: boolean enabled: boolean

View file

@ -80,7 +80,7 @@ export class SimulationRenderer {
public updateWheelListener(ref: RefObject<HTMLCanvasElement>) { public updateWheelListener(ref: RefObject<HTMLCanvasElement>) {
if (ref.current) { if (ref.current) {
ref.current.addEventListener('wheel', event => { ref.current.addEventListener('wheel', (event) => {
if (!modalIsOpen() && location.pathname === '/') { if (!modalIsOpen() && location.pathname === '/') {
event.preventDefault() event.preventDefault()
@ -119,7 +119,7 @@ export class SimulationRenderer {
* @throws SimulationError if the id doesnt have a data prop * @throws SimulationError if the id doesnt have a data prop
*/ */
public getSelected(): Gate[] { public getSelected(): Gate[] {
return setToArray(this.allSelectedIds()).map(id => { return setToArray(this.allSelectedIds()).map((id) => {
const gate = this.simulation.gates.get(id) const gate = this.simulation.gates.get(id)
if (!gate) { if (!gate) {

View file

@ -4,15 +4,11 @@
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "preserve", "jsx": "preserve",
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "esnext", "target": "ESNext",
"downlevelIteration": true, "downlevelIteration": true,
"strictNullChecks": true, "strictNullChecks": true,
"module": "esnext" "module": "esnext"
}, },
"exclude": [ "exclude": ["node_modules"],
"node_modules" "include": ["src"]
], }
"include": [
"src"
]
}