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,
|
"editor.formatOnSave": true,
|
||||||
"prettier.eslintIntegration": 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'
|
'@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 }]
|
||||||
|
|
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-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": {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
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 { 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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}`}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue