feat: internal checkbox for prop gates

This commit is contained in:
Matei Adriel 2020-04-13 17:45:44 +03:00
parent e8331b3d7a
commit 8168d60ed9
6 changed files with 91 additions and 34 deletions

View 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)
])
)

View file

@ -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()
}} }}
> >

View file

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

View file

@ -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}`
) )
} }

View file

@ -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,

View file

@ -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 {