From 8168d60ed9eaa42982c9dd6677a148916e1d7c42 Mon Sep 17 00:00:00 2001
From: Matei Adriel <rafaeladriel11@gmail.com>
Date: Mon, 13 Apr 2020 17:45:44 +0300
Subject: [PATCH] feat: internal checkbox for prop gates

---
 src/common/lang/record/map.ts                 | 11 +++
 .../components/GatePropertiesModal.tsx        | 84 +++++++++++++------
 .../logic-gates/helpers/completeTemplate.ts   |  2 +-
 src/modules/simulation/classes/Gate.ts        | 10 +--
 src/modules/simulation/constants.ts           | 13 ++-
 src/modules/simulation/types/GateTemplate.ts  |  5 ++
 6 files changed, 91 insertions(+), 34 deletions(-)
 create mode 100644 src/common/lang/record/map.ts

diff --git a/src/common/lang/record/map.ts b/src/common/lang/record/map.ts
new file mode 100644
index 0000000..fe8858f
--- /dev/null
+++ b/src/common/lang/record/map.ts
@@ -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)
+        ])
+    )
diff --git a/src/modules/logic-gates/components/GatePropertiesModal.tsx b/src/modules/logic-gates/components/GatePropertiesModal.tsx
index 1a167d0..e9c1d27 100644
--- a/src/modules/logic-gates/components/GatePropertiesModal.tsx
+++ b/src/modules/logic-gates/components/GatePropertiesModal.tsx
@@ -8,12 +8,17 @@ 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 { map } from 'rxjs/operators'
 
 export interface GatePropertyProps {
     raw: Property
     gate: Gate
 }
 
+const emptyInput = <></>
+
 /**
  * Renders a single props of the gate
  *
@@ -24,7 +29,18 @@ export const GatePropery = ({ raw, gate }: GatePropertyProps) => {
     const prop = gate.props[name]
     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 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') {
-        input = (
-            <TextField
-                onChange={handleChange}
-                label={displayableName}
-                value={outputSnapshot}
-                type={raw.type}
-                multiline={raw.type === 'string'}
-                rowsMax={7}
-            />
-        )
-    } else if (raw.type === 'boolean') {
-        input = (
-            <>
-                <span className="checkbox-label">{displayableName}</span>
-                <CheckBox
-                    onClick={() => {
-                        prop.next(!outputSnapshot)
-                    }}
+        if (
+            raw.type === 'number' ||
+            raw.type === 'text' ||
+            raw.type === 'string'
+        ) {
+            return (input = (
+                <TextField
                     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>
 }
@@ -104,7 +134,7 @@ const GateProperties = () => {
         >
             <div
                 id="gate-properties-container"
-                onClick={e => {
+                onClick={(e) => {
                     e.stopPropagation()
                 }}
             >
diff --git a/src/modules/logic-gates/helpers/completeTemplate.ts b/src/modules/logic-gates/helpers/completeTemplate.ts
index 98b43d8..b2f0e3a 100644
--- a/src/modules/logic-gates/helpers/completeTemplate.ts
+++ b/src/modules/logic-gates/helpers/completeTemplate.ts
@@ -4,6 +4,6 @@ import { DefaultGateTemplate } from '../../simulation/constants'
 
 export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
     return merge(DefaultGateTemplate, template, {
-        arrayMerge: (a: unknown[], b: unknown[]) => b
+        arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
     }) as GateTemplate
 }
diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts
index 4fc4067..a9561f0 100644
--- a/src/modules/simulation/classes/Gate.ts
+++ b/src/modules/simulation/classes/Gate.ts
@@ -4,7 +4,7 @@ import { GateTemplate, PinCount } from '../types/GateTemplate'
 import { idStore } from '../stores/idStore'
 import { Context, InitialisationContext } from '../../activation/types/Context'
 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 { getGateTimePipes } from '../helpers/getGateTimePipes'
 import { ImageStore } from '../../simulationRenderer/stores/imageStore'
@@ -15,7 +15,7 @@ import { saveStore } from '../../saving/stores/saveStore'
 import { Wire } from './Wire'
 import { cleanSimulation } from '../../simulation-actions/helpers/clean'
 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
@@ -205,7 +205,7 @@ export class Gate {
             if (!state) {
                 throw new SimulationError(
                     `Cannot run ic ${
-                        this.template.metadata.name
+                    this.template.metadata.name
                     } - save not found`
                 )
             }
@@ -233,7 +233,7 @@ export class Gate {
             if (inputs.length !== this._pins.inputs.length) {
                 throw new SimulationError(
                     `Input count needs to match with the container gate: ${
-                        inputs.length
+                    inputs.length
                     } !== ${this._pins.inputs.length}`
                 )
             }
@@ -241,7 +241,7 @@ export class Gate {
             if (outputs.length !== this._pins.outputs.length) {
                 throw new SimulationError(
                     `Output count needs to match with the container gate: ${
-                        outputs.length
+                    outputs.length
                     } !== ${this._pins.outputs.length}`
                 )
             }
diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts
index 83ce040..a19e712 100644
--- a/src/modules/simulation/constants.ts
+++ b/src/modules/simulation/constants.ts
@@ -53,7 +53,18 @@ export const DefaultGateTemplate: GateTemplate = {
     tags: ['base'],
     properties: {
         enabled: false,
-        data: []
+        data: [
+            {
+                type: 'boolean',
+                base: false,
+                name: 'internal',
+                show: (_, gate) =>
+                    gate.env === 'global' &&
+                    !gate.template.properties.data.some(
+                        (prop) => prop.needsUpdate
+                    )
+            }
+        ]
     },
     innerText: {
         enabled: false,
diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts
index 07e8ff3..3a38357 100644
--- a/src/modules/simulation/types/GateTemplate.ts
+++ b/src/modules/simulation/types/GateTemplate.ts
@@ -1,4 +1,5 @@
 import { vector2 } from '../../../common/math/types/vector2'
+import { Gate } from '../classes/Gate'
 
 export interface PinCount {
     variable: boolean
@@ -12,6 +13,10 @@ export interface Property<
     base: T
     name: string
     needsUpdate?: boolean
+    show?: (
+        obj: Record<string, string | boolean | number>,
+        gate: Gate
+    ) => boolean
 }
 
 export interface Material {