feat: internal checkbox for prop gates
This commit is contained in:
parent
e8331b3d7a
commit
8168d60ed9
11
src/common/lang/record/map.ts
Normal file
11
src/common/lang/record/map.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Like Array.map but for records
|
||||||
|
export const mapRecord = <T extends keyof object, A, B>(
|
||||||
|
record: Record<T, A>,
|
||||||
|
mapper: (a: A) => B
|
||||||
|
): Record<T, B> =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(record).map(([key, value]: [T, A]) => [
|
||||||
|
key,
|
||||||
|
mapper(value)
|
||||||
|
])
|
||||||
|
)
|
|
@ -8,12 +8,17 @@ 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 } from '../../simulation/classes/Gate'
|
||||||
|
import { mapRecord } from '../../../common/lang/record/map'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
export interface GatePropertyProps {
|
export interface GatePropertyProps {
|
||||||
raw: Property
|
raw: Property
|
||||||
gate: Gate
|
gate: Gate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyInput = <></>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a single props of the gate
|
* Renders a single props of the gate
|
||||||
*
|
*
|
||||||
|
@ -24,7 +29,18 @@ export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
||||||
const prop = gate.props[name]
|
const prop = gate.props[name]
|
||||||
const outputSnapshot = useObservable(() => prop, '')
|
const outputSnapshot = useObservable(() => prop, '')
|
||||||
|
|
||||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:`
|
// rerender when the internal checkbox changes
|
||||||
|
const internal = useObservable(
|
||||||
|
() =>
|
||||||
|
gate.props.internal.pipe(
|
||||||
|
map((value) => value && name !== 'internal')
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
|
||||||
|
internal ? '(default value)' : ''
|
||||||
|
}:`
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement
|
||||||
|
@ -45,33 +61,47 @@ export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = <></>
|
let input = (() => {
|
||||||
|
if (
|
||||||
|
raw.show &&
|
||||||
|
!raw.show(
|
||||||
|
mapRecord(gate.props, (subject) => subject.value),
|
||||||
|
gate
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return emptyInput
|
||||||
|
}
|
||||||
|
|
||||||
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
if (
|
||||||
input = (
|
raw.type === 'number' ||
|
||||||
<TextField
|
raw.type === 'text' ||
|
||||||
onChange={handleChange}
|
raw.type === 'string'
|
||||||
label={displayableName}
|
) {
|
||||||
value={outputSnapshot}
|
return (input = (
|
||||||
type={raw.type}
|
<TextField
|
||||||
multiline={raw.type === 'string'}
|
|
||||||
rowsMax={7}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (raw.type === 'boolean') {
|
|
||||||
input = (
|
|
||||||
<>
|
|
||||||
<span className="checkbox-label">{displayableName}</span>
|
|
||||||
<CheckBox
|
|
||||||
onClick={() => {
|
|
||||||
prop.next(!outputSnapshot)
|
|
||||||
}}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
checked={!!outputSnapshot}
|
label={displayableName}
|
||||||
/>{' '}
|
value={outputSnapshot}
|
||||||
</>
|
type={raw.type}
|
||||||
)
|
multiline={raw.type === 'string'}
|
||||||
}
|
rowsMax={7}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
} else if (raw.type === 'boolean') {
|
||||||
|
return (input = (
|
||||||
|
<>
|
||||||
|
<span className="checkbox-label">{displayableName}</span>
|
||||||
|
<CheckBox
|
||||||
|
onClick={() => {
|
||||||
|
prop.next(!outputSnapshot)
|
||||||
|
}}
|
||||||
|
onChange={handleChange}
|
||||||
|
checked={!!outputSnapshot}
|
||||||
|
/>{' '}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
return <div className="gate-prop-container">{input}</div>
|
return <div className="gate-prop-container">{input}</div>
|
||||||
}
|
}
|
||||||
|
@ -104,7 +134,7 @@ const GateProperties = () => {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="gate-properties-container"
|
id="gate-properties-container"
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,6 +4,6 @@ import { DefaultGateTemplate } from '../../simulation/constants'
|
||||||
|
|
||||||
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
||||||
return merge(DefaultGateTemplate, template, {
|
return merge(DefaultGateTemplate, template, {
|
||||||
arrayMerge: (a: unknown[], b: unknown[]) => b
|
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
|
||||||
}) as GateTemplate
|
}) as GateTemplate
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { GateTemplate, PinCount } 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 } 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'
|
||||||
|
@ -15,7 +15,7 @@ import { saveStore } from '../../saving/stores/saveStore'
|
||||||
import { Wire } from './Wire'
|
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 } from 'rxjs/operators'
|
import { tap, observeOn } from 'rxjs/operators'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface for the pins of a gate
|
* The interface for the pins of a gate
|
||||||
|
@ -205,7 +205,7 @@ export class Gate {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new SimulationError(
|
throw new SimulationError(
|
||||||
`Cannot run ic ${
|
`Cannot run ic ${
|
||||||
this.template.metadata.name
|
this.template.metadata.name
|
||||||
} - save not found`
|
} - save not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ export class Gate {
|
||||||
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
|
inputs.length
|
||||||
} !== ${this._pins.inputs.length}`
|
} !== ${this._pins.inputs.length}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ export class Gate {
|
||||||
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
|
outputs.length
|
||||||
} !== ${this._pins.outputs.length}`
|
} !== ${this._pins.outputs.length}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,18 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
tags: ['base'],
|
tags: ['base'],
|
||||||
properties: {
|
properties: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
data: []
|
data: [
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
base: false,
|
||||||
|
name: 'internal',
|
||||||
|
show: (_, gate) =>
|
||||||
|
gate.env === 'global' &&
|
||||||
|
!gate.template.properties.data.some(
|
||||||
|
(prop) => prop.needsUpdate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
innerText: {
|
innerText: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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
|
||||||
|
@ -12,6 +13,10 @@ export interface Property<
|
||||||
base: T
|
base: T
|
||||||
name: string
|
name: string
|
||||||
needsUpdate?: boolean
|
needsUpdate?: boolean
|
||||||
|
show?: (
|
||||||
|
obj: Record<string, string | boolean | number>,
|
||||||
|
gate: Gate
|
||||||
|
) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Material {
|
export interface Material {
|
||||||
|
|
Loading…
Reference in a new issue