Add support for constants
This commit is contained in:
parent
7d9d2a2d78
commit
96e2184a24
6
build.js
6
build.js
|
@ -43,7 +43,11 @@ const ctx = await esbuild.context({
|
|||
})
|
||||
|
||||
if (serve) {
|
||||
const { port, host } = await ctx.serve({ servedir: 'dist' })
|
||||
await ctx.watch()
|
||||
const { port, host } = await ctx.serve({
|
||||
servedir: 'dist',
|
||||
fallback: 'dist/index.html'
|
||||
})
|
||||
console.log(`Serving on ${host}:${port}`)
|
||||
} else {
|
||||
await ctx.rebuild()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "ESBUILD_SERVE=1 node ./build.js",
|
||||
"build": "node ./build.js",
|
||||
"check": "tsc"
|
||||
},
|
||||
|
|
52
src/index.ts
52
src/index.ts
|
@ -4,37 +4,39 @@ import { Splash } from './modules/splash/classes/Splash'
|
|||
* The function wich is run when the app is loaded
|
||||
*/
|
||||
async function main() {
|
||||
// Create splash screen variable
|
||||
let splash: Splash | undefined = undefined
|
||||
// Create splash screen variable
|
||||
let splash: Splash | undefined = undefined
|
||||
|
||||
try {
|
||||
// instantiate splash screen
|
||||
splash = new Splash()
|
||||
} catch {}
|
||||
try {
|
||||
// instantiate splash screen
|
||||
splash = new Splash()
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
// import main app
|
||||
const app = await import('./main')
|
||||
try {
|
||||
// import main app
|
||||
const app = await import('./main')
|
||||
|
||||
// wait for app to start
|
||||
await app.start()
|
||||
} catch (error) {
|
||||
// show the error to the client
|
||||
if (splash) splash.setError(error)
|
||||
// wait for app to start
|
||||
await app.start()
|
||||
} catch (error) {
|
||||
// show the error to the client
|
||||
if (splash) splash.setError(error)
|
||||
|
||||
// log the error to the console
|
||||
console.error(error.stack || error)
|
||||
return
|
||||
}
|
||||
// log the error to the console
|
||||
console.error(error.stack || error)
|
||||
return
|
||||
}
|
||||
|
||||
// hide splash screen if it exists
|
||||
if (splash) {
|
||||
splash.fade()
|
||||
}
|
||||
// hide splash screen if it exists
|
||||
if (splash) {
|
||||
splash.fade()
|
||||
}
|
||||
}
|
||||
|
||||
new EventSource('/esbuild').addEventListener('change', () => location.reload())
|
||||
|
||||
// Call entry
|
||||
main().catch(error => {
|
||||
// if the error handling error has an error, log that error
|
||||
console.error('Error loading app', error)
|
||||
main().catch((error) => {
|
||||
// if the error handling error has an error, log that error
|
||||
console.error('Error loading app', error)
|
||||
})
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation'
|
||||
import { SimulationEnv } from '../../simulation/classes/Simulation'
|
||||
import { PinState } from '../../simulation/classes/Pin'
|
||||
|
||||
export interface Context {
|
||||
getProperty: (name: string) => unknown
|
||||
setProperty: (name: string, value: unknown) => void
|
||||
get: (index: number) => PinState
|
||||
set: (index: number, state: PinState) => void
|
||||
getBinary: (index: number) => number
|
||||
setBinary: (index: number, value: number, bits: number) => void
|
||||
invertBinary: (value: number) => number
|
||||
color: (color: string) => void
|
||||
innerText: (value: string) => void
|
||||
update: () => void
|
||||
toLength: (value: number | PinState, length: number) => string
|
||||
maxLength: number
|
||||
enviroment: SimulationEnv
|
||||
colors: Record<string, string>
|
||||
memory: Record<string, unknown>
|
||||
getProperty: (name: string) => unknown
|
||||
setProperty: (name: string, value: unknown) => void
|
||||
get: (index: number) => PinState
|
||||
set: (index: number, state: PinState) => void
|
||||
getOutput: (index: number) => PinState
|
||||
getBinary: (index: number) => number
|
||||
printBinary: (value: number, bits?: number) => string
|
||||
printHex: (value: number, length?: number) => string
|
||||
setBinary: (index: number, value: number, bits?: number) => void
|
||||
getOutputBinary: (index: number) => number
|
||||
invertBinary: (value: number) => number
|
||||
color: (color: string) => void
|
||||
innerText: (value: string) => void
|
||||
update: () => void
|
||||
toLength: (value: number | PinState, length: number) => string
|
||||
maxLength: number
|
||||
enviroment: SimulationEnv
|
||||
colors: Record<string, string>
|
||||
memory: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface InitialisationContext {
|
||||
memory: Record<string, unknown>
|
||||
memory: Record<string, unknown>
|
||||
}
|
||||
|
|
|
@ -7,60 +7,64 @@
|
|||
$gate-props-margin: 1rem;
|
||||
|
||||
#gate-properties-modal {
|
||||
@include modal-container();
|
||||
@include modal-container();
|
||||
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.visible#gate-properties-modal {
|
||||
@include visible();
|
||||
@include visible();
|
||||
}
|
||||
|
||||
div #gate-properties-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: $grey;
|
||||
border-radius: 1em;
|
||||
background-color: $grey;
|
||||
border-radius: 1em;
|
||||
|
||||
padding: $gate-props-margin * 4;
|
||||
box-sizing: border-box;
|
||||
padding: $gate-props-margin * 4;
|
||||
box-sizing: border-box;
|
||||
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div #gate-props-title {
|
||||
color: white;
|
||||
font-size: 3em;
|
||||
color: white;
|
||||
font-size: 3em;
|
||||
|
||||
margin-bottom: 2 * $gate-props-margin;
|
||||
margin-bottom: 2 * $gate-props-margin;
|
||||
}
|
||||
|
||||
div .gate-prop-container {
|
||||
@include flex();
|
||||
@include flex();
|
||||
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
|
||||
&.visible {
|
||||
margin: 1rem;
|
||||
}
|
||||
&.visible {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
&>* {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
div .gate-prop-group-container {
|
||||
@include flex;
|
||||
@include flex;
|
||||
|
||||
margin-left: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
div #save-props {
|
||||
width: 50%;
|
||||
margin: $gate-props-margin * 2;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
margin: $gate-props-margin * 2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import './GateProperties.scss'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { ChangeEvent } from 'react'
|
||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
|
||||
import { useObservable } from 'rxjs-hooks'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import CheckBox from '@material-ui/core/Checkbox'
|
||||
import { open, id } from '../subjects/LogicGatePropsSubjects'
|
||||
|
@ -16,41 +15,41 @@ 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
|
||||
props: GateProps
|
||||
raw: T
|
||||
gate: Gate
|
||||
props: GateProps
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
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} />
|
||||
return <GateRawProperty raw={raw} gate={gate} props={props} />
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,99 +58,90 @@ const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
|
|||
* @param param0 The props passed to the component
|
||||
*/
|
||||
const GateRawProperty = ({
|
||||
props,
|
||||
raw,
|
||||
gate
|
||||
props,
|
||||
raw,
|
||||
gate
|
||||
}: GatePropertyProps & { raw: RawProp }) => {
|
||||
const { name } = raw
|
||||
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
|
||||
const outputSnapshot = useObservable(() => prop, '')
|
||||
const { name } = raw
|
||||
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
|
||||
const outputSnapshot = useObservable(() => prop, '')
|
||||
|
||||
// rerender when the external checkbox changes
|
||||
const external = useObservable(
|
||||
() =>
|
||||
gate.props.external.pipe(
|
||||
map((value) => value && name !== 'external')
|
||||
),
|
||||
false
|
||||
)
|
||||
// rerender when the external checkbox changes
|
||||
const external = useObservable(
|
||||
() =>
|
||||
gate.props.external.pipe(map((value) => value && name !== 'external')),
|
||||
false
|
||||
)
|
||||
|
||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
|
||||
external && name !== 'label' ? '(default value)' : ''
|
||||
}:`
|
||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}${
|
||||
external && name !== 'label' ? ' (default value)' : ''
|
||||
}${raw.description == undefined ? '' : ` ${raw.description}`}:`
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
let value: boolean | string | number = target.value
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
let value: boolean | string | number = target.value
|
||||
|
||||
if (raw.type === 'boolean') {
|
||||
value = target.checked
|
||||
} else if (raw.type === 'number') {
|
||||
value = Number(target.value)
|
||||
}
|
||||
|
||||
if (raw.type !== 'boolean') {
|
||||
prop.next(value)
|
||||
}
|
||||
if (raw.type === 'boolean') {
|
||||
value = target.checked
|
||||
} else if (raw.type === 'number') {
|
||||
value = Number(target.value)
|
||||
}
|
||||
|
||||
let input = (() => {
|
||||
const root = gate.props[name] === prop
|
||||
const renderer = getRendererSafely()
|
||||
const displayExternal = () =>
|
||||
renderer.simulation.mode === 'ic' &&
|
||||
root &&
|
||||
!gate.template.properties.data.some(
|
||||
(prop) => (prop as RawProp).needsUpdate
|
||||
)
|
||||
if (raw.type !== 'boolean') {
|
||||
prop.next(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(raw.name === 'external' && !displayExternal()) ||
|
||||
(raw.name === 'label' && (!external || !root))
|
||||
) {
|
||||
return emptyInput
|
||||
}
|
||||
let input = (() => {
|
||||
const root = gate.props[name] === prop
|
||||
const renderer = getRendererSafely()
|
||||
const displayExternal = () =>
|
||||
renderer.simulation.mode === 'ic' &&
|
||||
root &&
|
||||
!gate.template.properties.data.some(
|
||||
(prop) => (prop as RawProp).needsUpdate
|
||||
)
|
||||
|
||||
if (
|
||||
raw.type === 'number' ||
|
||||
raw.type === 'text' ||
|
||||
raw.type === 'string'
|
||||
) {
|
||||
return (
|
||||
<TextField
|
||||
onChange={handleChange}
|
||||
label={displayableName}
|
||||
value={outputSnapshot}
|
||||
type={raw.type}
|
||||
multiline={raw.type === 'string'}
|
||||
rowsMax={7}
|
||||
/>
|
||||
)
|
||||
} else if (raw.type === 'boolean') {
|
||||
return (
|
||||
<>
|
||||
<span className="checkbox-label">{displayableName}</span>
|
||||
<CheckBox
|
||||
onClick={() => {
|
||||
prop.next(!outputSnapshot)
|
||||
}}
|
||||
onChange={handleChange}
|
||||
checked={!!outputSnapshot}
|
||||
/>{' '}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})()
|
||||
if (
|
||||
(raw.name === 'external' && !displayExternal()) ||
|
||||
(raw.name === 'label' && (!external || !root))
|
||||
) {
|
||||
return emptyInput
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`gate-prop-container ${
|
||||
input !== emptyInput ? 'visible' : ''
|
||||
}`}
|
||||
>
|
||||
{input}
|
||||
</div>
|
||||
)
|
||||
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
||||
return (
|
||||
<TextField
|
||||
onChange={handleChange}
|
||||
label={displayableName}
|
||||
value={outputSnapshot}
|
||||
type={raw.type}
|
||||
multiline={raw.type === 'string'}
|
||||
/>
|
||||
)
|
||||
} else if (raw.type === 'boolean') {
|
||||
return (
|
||||
<>
|
||||
<span className="checkbox-label">{displayableName}</span>
|
||||
<CheckBox
|
||||
onClick={() => {
|
||||
prop.next(!outputSnapshot)
|
||||
}}
|
||||
onChange={handleChange}
|
||||
checked={!!outputSnapshot}
|
||||
/>{' '}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`gate-prop-container ${input !== emptyInput ? 'visible' : ''}`}
|
||||
>
|
||||
{input}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,47 +150,47 @@ const GateRawProperty = ({
|
|||
* @param props The react props of the component
|
||||
*/
|
||||
const GateProperties = () => {
|
||||
const openSnapshot = useObservable(() => open, false)
|
||||
const renderer = getRendererSafely()
|
||||
const openSnapshot = useObservable(() => open, false)
|
||||
const renderer = getRendererSafely()
|
||||
|
||||
const node = renderer.simulation.gates.get(id.value)
|
||||
const node = renderer.simulation.gates.get(id.value)
|
||||
|
||||
if (!(node && node.data && node.data.template.properties.enabled)) {
|
||||
if (!(node && node.data && node.data.template.properties.enabled)) {
|
||||
open.next(false)
|
||||
return <></>
|
||||
}
|
||||
|
||||
const gate = node.data
|
||||
|
||||
return (
|
||||
<div
|
||||
id="gate-properties-modal"
|
||||
className={openSnapshot ? 'visible' : ''}
|
||||
onClick={() => {
|
||||
open.next(false)
|
||||
return <></>
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="gate-properties-container"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<div id="gate-props-title">Gate properties</div>
|
||||
|
||||
const gate = node.data
|
||||
|
||||
return (
|
||||
<div
|
||||
id="gate-properties-modal"
|
||||
className={openSnapshot ? 'visible' : ''}
|
||||
onClick={() => {
|
||||
open.next(false)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="gate-properties-container"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<div id="gate-props-title">Gate properties</div>
|
||||
|
||||
{gate.template.properties.data.map((prop, index) => {
|
||||
return (
|
||||
<GateProperty
|
||||
props={gate.props}
|
||||
raw={prop}
|
||||
gate={gate}
|
||||
key={`${index}-${id.value}`}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
{gate.template.properties.data.map((prop, index) => {
|
||||
return (
|
||||
<GateProperty
|
||||
props={gate.props}
|
||||
raw={prop}
|
||||
gate={gate}
|
||||
key={`${index}-${id.value}`}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GateProperties
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GateTemplate } from '../../simulation/types/GateTemplate'
|
|||
import { DefaultGateTemplate } from '../../simulation/constants'
|
||||
|
||||
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
||||
return merge(DefaultGateTemplate, template, {
|
||||
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
|
||||
}) as GateTemplate
|
||||
return merge(DefaultGateTemplate, template, {
|
||||
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
|
||||
}) as GateTemplate
|
||||
}
|
||||
|
|
|
@ -20,43 +20,45 @@ import comparatorTemplate from './templates/comparator'
|
|||
import bitMergerTemplate from './templates/bitMerger'
|
||||
import bitSplitterTemplate from './templates/bitSplitter'
|
||||
import incrementorTemplate from './templates/incrementor'
|
||||
import constantTemplate from './templates/constant'
|
||||
|
||||
export const defaultSimulationName = 'default'
|
||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||
andTemplate,
|
||||
buttonTemplate,
|
||||
lightTemplate,
|
||||
nandTemplate,
|
||||
norTemplate,
|
||||
notTemplate,
|
||||
orTemplate,
|
||||
parallelDelayerTemplate,
|
||||
rgbLightTemplate,
|
||||
sequentialDelayerTemplate,
|
||||
xnorTemplate,
|
||||
xorTemplate,
|
||||
halfAdderTemplate,
|
||||
fullAdderTemplate,
|
||||
_4bitEncoderTemplate,
|
||||
_4bitDecoderTemplate,
|
||||
comparatorTemplate,
|
||||
bitMergerTemplate,
|
||||
bitSplitterTemplate,
|
||||
incrementorTemplate
|
||||
andTemplate,
|
||||
buttonTemplate,
|
||||
lightTemplate,
|
||||
nandTemplate,
|
||||
norTemplate,
|
||||
notTemplate,
|
||||
orTemplate,
|
||||
parallelDelayerTemplate,
|
||||
rgbLightTemplate,
|
||||
sequentialDelayerTemplate,
|
||||
xnorTemplate,
|
||||
xorTemplate,
|
||||
halfAdderTemplate,
|
||||
fullAdderTemplate,
|
||||
_4bitEncoderTemplate,
|
||||
_4bitDecoderTemplate,
|
||||
comparatorTemplate,
|
||||
bitMergerTemplate,
|
||||
bitSplitterTemplate,
|
||||
incrementorTemplate,
|
||||
constantTemplate
|
||||
]
|
||||
|
||||
export const baseSave: RendererState = {
|
||||
camera: {
|
||||
transform: {
|
||||
position: [0, 0],
|
||||
scale: [1, 1],
|
||||
rotation: 0
|
||||
}
|
||||
},
|
||||
simulation: {
|
||||
gates: [],
|
||||
mode: 'project',
|
||||
wires: [],
|
||||
name: 'default'
|
||||
camera: {
|
||||
transform: {
|
||||
position: [0, 0],
|
||||
scale: [1, 1],
|
||||
rotation: 0
|
||||
}
|
||||
},
|
||||
simulation: {
|
||||
gates: [],
|
||||
mode: 'project',
|
||||
wires: [],
|
||||
name: 'default'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
export const categories = {
|
||||
basic: 0,
|
||||
math: 1,
|
||||
time: 2,
|
||||
compressing: 3,
|
||||
io: 4,
|
||||
advancedIo: 5,
|
||||
ic: 6
|
||||
basic: 0,
|
||||
math: 1,
|
||||
time: 2,
|
||||
compressing: 3,
|
||||
io: 4,
|
||||
advancedIo: 5,
|
||||
ic: 6
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ import { SimulationError } from '../../errors/classes/SimulationError'
|
|||
* @throws SimulationError if something is wrong with the template
|
||||
*/
|
||||
export const initBaseTemplates = () => {
|
||||
for (const template of baseTemplates) {
|
||||
if (template.metadata && template.metadata.name) {
|
||||
templateStore.set(template.metadata.name, template)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Template ${JSON.stringify(template)} cannot be stored.`
|
||||
)
|
||||
}
|
||||
for (const template of baseTemplates) {
|
||||
if (template.metadata && template.metadata.name) {
|
||||
templateStore.set(template.metadata.name, template)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Template ${JSON.stringify(template)} cannot be stored.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,44 +4,44 @@ import { PartialTemplate } from '../types/PartialTemplate'
|
|||
* The template of the comment gate
|
||||
*/
|
||||
const commentTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'comment'
|
||||
metadata: {
|
||||
name: 'comment'
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 0
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 0
|
||||
},
|
||||
outputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
material: {
|
||||
fill: '#007A72'
|
||||
},
|
||||
shape: {
|
||||
scale: [300, 100]
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
context.innerText(context.getProperty('content'))
|
||||
`
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
|
||||
properties: {
|
||||
enabled: true,
|
||||
data: [
|
||||
{
|
||||
needsUpdate: true,
|
||||
base: 'Your comment here',
|
||||
name: 'content',
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
innerText: {
|
||||
enabled: true,
|
||||
color: '#ADFFFA'
|
||||
outputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
material: {
|
||||
fill: '#007A72'
|
||||
},
|
||||
shape: {
|
||||
scale: [300, 100]
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
context.innerText(context.getProperty('content'))
|
||||
`
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
|
||||
properties: {
|
||||
enabled: true,
|
||||
data: [
|
||||
{
|
||||
needsUpdate: true,
|
||||
base: 'Your comment here',
|
||||
name: 'content',
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
innerText: {
|
||||
enabled: true,
|
||||
color: '#ADFFFA'
|
||||
}
|
||||
}
|
||||
|
||||
export default commentTemplate
|
||||
|
|
60
src/modules/saving/templates/constant.ts
Normal file
60
src/modules/saving/templates/constant.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { PartialTemplate } from '../types/PartialTemplate'
|
||||
import { categories } from '../data/categories'
|
||||
|
||||
/**
|
||||
* The template of the button gate
|
||||
*/
|
||||
const constTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'constant'
|
||||
},
|
||||
material: {
|
||||
fill: '#673AB7',
|
||||
stroke: {
|
||||
normal: '#EDC6FB'
|
||||
}
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
const state = context.getProperty('value')
|
||||
const bits = context.getProperty('output bits')
|
||||
const length = state.toString(2).length
|
||||
const text = length > 10
|
||||
? "0x" + context.printHex(state, Math.ceil(length/4))
|
||||
: context.printBinary(state, length)
|
||||
|
||||
context.setBinary(0, state, bits === 0 ? length : bits)
|
||||
context.innerText(text)
|
||||
`
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
integration: {
|
||||
input: true
|
||||
},
|
||||
info: [],
|
||||
properties: {
|
||||
enabled: true,
|
||||
data: [
|
||||
{
|
||||
base: 0,
|
||||
name: 'output bits',
|
||||
description: '(0 for auto)',
|
||||
type: 'number',
|
||||
needsUpdate: true
|
||||
},
|
||||
{
|
||||
base: 0,
|
||||
name: 'value',
|
||||
type: 'number',
|
||||
needsUpdate: true
|
||||
}
|
||||
]
|
||||
},
|
||||
category: categories.io
|
||||
}
|
||||
|
||||
export default constTemplate
|
|
@ -5,40 +5,40 @@ import { categories } from '../data/categories'
|
|||
* The template of the light gate
|
||||
*/
|
||||
const lightTemplate: PartialTemplate = {
|
||||
metadata: {
|
||||
name: 'light bulb'
|
||||
metadata: {
|
||||
name: 'light bulb'
|
||||
},
|
||||
shape: {
|
||||
radius: 50
|
||||
},
|
||||
material: {
|
||||
fill: '#1C1C1C',
|
||||
stroke: {
|
||||
normal: '#3C3C3C'
|
||||
},
|
||||
shape: {
|
||||
radius: 50
|
||||
},
|
||||
material: {
|
||||
fill: '#1C1C1C',
|
||||
stroke: {
|
||||
normal: '#3C3C3C'
|
||||
},
|
||||
colors: {
|
||||
active: '#C6FF00'
|
||||
}
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
colors: {
|
||||
active: '#C6FF00'
|
||||
}
|
||||
},
|
||||
code: {
|
||||
activation: `
|
||||
const { main, active } = context.colors
|
||||
|
||||
const bits = context.get(0)
|
||||
|
||||
context.color(parseInt(context.get(0),2) ? active : main)
|
||||
`
|
||||
},
|
||||
integration: {
|
||||
output: true
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||
pins: {
|
||||
outputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
category: categories.io
|
||||
},
|
||||
integration: {
|
||||
output: true
|
||||
},
|
||||
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||
pins: {
|
||||
outputs: {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
category: categories.io
|
||||
}
|
||||
|
||||
export default lightTemplate
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,80 +3,80 @@ import { categories } from '../saving/data/categories'
|
|||
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
|
||||
|
||||
export const DefaultGateTemplate: GateTemplate = {
|
||||
metadata: {
|
||||
name: 'Default template'
|
||||
metadata: {
|
||||
name: 'Default template'
|
||||
},
|
||||
material: {
|
||||
type: 'color',
|
||||
fill: 'blue',
|
||||
stroke: {
|
||||
active: '#76FF02',
|
||||
normal: '#3FC4FF'
|
||||
},
|
||||
material: {
|
||||
type: 'color',
|
||||
fill: 'blue',
|
||||
stroke: {
|
||||
active: '#76FF02',
|
||||
normal: '#3FC4FF'
|
||||
},
|
||||
colors: {}
|
||||
colors: {}
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 1,
|
||||
variable: false
|
||||
},
|
||||
pins: {
|
||||
inputs: {
|
||||
count: 1,
|
||||
variable: false
|
||||
},
|
||||
outputs: {
|
||||
count: 1,
|
||||
variable: false
|
||||
}
|
||||
outputs: {
|
||||
count: 1,
|
||||
variable: false
|
||||
}
|
||||
},
|
||||
shape: {
|
||||
radius: 10,
|
||||
rounded: true,
|
||||
scale: [100, 100]
|
||||
},
|
||||
code: {
|
||||
async: false,
|
||||
activation: '',
|
||||
onClick: '',
|
||||
initialisation: ''
|
||||
},
|
||||
simulation: {
|
||||
debounce: {
|
||||
enabled: true,
|
||||
time: 1000 / 60
|
||||
},
|
||||
shape: {
|
||||
radius: 10,
|
||||
rounded: true,
|
||||
scale: [100, 100]
|
||||
},
|
||||
code: {
|
||||
async: false,
|
||||
activation: '',
|
||||
onClick: '',
|
||||
initialisation: ''
|
||||
},
|
||||
simulation: {
|
||||
debounce: {
|
||||
enabled: true,
|
||||
time: 1000 / 60
|
||||
},
|
||||
throttle: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
integration: {
|
||||
allowed: true,
|
||||
input: false,
|
||||
output: false
|
||||
},
|
||||
tags: ['base'],
|
||||
properties: {
|
||||
enabled: false,
|
||||
data: [
|
||||
{
|
||||
type: 'boolean',
|
||||
base: false,
|
||||
name: 'external'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
base: 'my-logic-gate',
|
||||
name: 'label'
|
||||
}
|
||||
]
|
||||
},
|
||||
innerText: {
|
||||
enabled: false,
|
||||
color: 'white'
|
||||
},
|
||||
category: categories.basic,
|
||||
info: []
|
||||
throttle: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
integration: {
|
||||
allowed: true,
|
||||
input: false,
|
||||
output: false
|
||||
},
|
||||
tags: ['base'],
|
||||
properties: {
|
||||
enabled: false,
|
||||
data: [
|
||||
{
|
||||
type: 'boolean',
|
||||
base: false,
|
||||
name: 'external'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
base: 'my-logic-gate',
|
||||
name: 'label'
|
||||
}
|
||||
]
|
||||
},
|
||||
innerText: {
|
||||
enabled: false,
|
||||
color: 'white'
|
||||
},
|
||||
category: categories.basic,
|
||||
info: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Prop names which need to not be overriten
|
||||
*/
|
||||
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
|
||||
({ name }: RawProp) => name
|
||||
({ name }: RawProp) => name
|
||||
)
|
||||
|
|
|
@ -1,96 +1,97 @@
|
|||
import { vector2 } from '../../../common/math/types/vector2'
|
||||
|
||||
export interface PinCount {
|
||||
variable: boolean
|
||||
count: number
|
||||
variable: boolean
|
||||
count: number
|
||||
}
|
||||
|
||||
export type PropGroup<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> = {
|
||||
groupName: string
|
||||
props: Property<T>[]
|
||||
groupName: string
|
||||
props: Property<T>[]
|
||||
}
|
||||
|
||||
export const isGroup = (prop: Property): prop is PropGroup =>
|
||||
(prop as PropGroup).groupName !== undefined
|
||||
(prop as PropGroup).groupName !== undefined
|
||||
|
||||
export type RawProp<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> = {
|
||||
type: 'number' | 'string' | 'text' | 'boolean'
|
||||
base: T
|
||||
name: string
|
||||
needsUpdate?: boolean
|
||||
type: 'number' | 'string' | 'text' | 'boolean'
|
||||
base: T
|
||||
name: string
|
||||
description?: string
|
||||
needsUpdate?: boolean
|
||||
}
|
||||
export type Property<
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
T extends boolean | number | string = boolean | number | string
|
||||
> = PropGroup<T> | RawProp<T>
|
||||
|
||||
export interface Material {
|
||||
type: 'color' | 'image'
|
||||
fill: string
|
||||
stroke: {
|
||||
active: string
|
||||
normal: string
|
||||
}
|
||||
colors: Record<string, string>
|
||||
type: 'color' | 'image'
|
||||
fill: string
|
||||
stroke: {
|
||||
active: string
|
||||
normal: string
|
||||
}
|
||||
colors: Record<string, string>
|
||||
}
|
||||
|
||||
export interface Shape {
|
||||
rounded: boolean
|
||||
radius: number
|
||||
scale: vector2
|
||||
rounded: boolean
|
||||
radius: number
|
||||
scale: vector2
|
||||
}
|
||||
|
||||
export type Enabled<T> =
|
||||
| {
|
||||
enabled: false
|
||||
}
|
||||
| ({
|
||||
enabled: true
|
||||
} & T)
|
||||
| {
|
||||
enabled: false
|
||||
}
|
||||
| ({
|
||||
enabled: true
|
||||
} & T)
|
||||
|
||||
export type TimePipe = Enabled<{
|
||||
time: number
|
||||
time: number
|
||||
}>
|
||||
|
||||
export type GateTag = 'base' | 'imported' | 'integrated'
|
||||
|
||||
export interface GateTemplate {
|
||||
material: Material
|
||||
shape: Shape
|
||||
pins: {
|
||||
inputs: PinCount
|
||||
outputs: PinCount
|
||||
}
|
||||
metadata: {
|
||||
name: string
|
||||
}
|
||||
code: {
|
||||
async: boolean
|
||||
initialisation: string
|
||||
activation: string
|
||||
onClick: string
|
||||
}
|
||||
simulation: {
|
||||
throttle: TimePipe
|
||||
debounce: TimePipe
|
||||
}
|
||||
integration: {
|
||||
allowed: boolean
|
||||
input: boolean
|
||||
output: boolean
|
||||
}
|
||||
tags: GateTag[]
|
||||
properties: {
|
||||
enabled: boolean
|
||||
data: Property[]
|
||||
}
|
||||
innerText: {
|
||||
color: string
|
||||
enabled: boolean
|
||||
}
|
||||
category: number // for better sorting
|
||||
info: string[]
|
||||
material: Material
|
||||
shape: Shape
|
||||
pins: {
|
||||
inputs: PinCount
|
||||
outputs: PinCount
|
||||
}
|
||||
metadata: {
|
||||
name: string
|
||||
}
|
||||
code: {
|
||||
async: boolean
|
||||
initialisation: string
|
||||
activation: string
|
||||
onClick: string
|
||||
}
|
||||
simulation: {
|
||||
throttle: TimePipe
|
||||
debounce: TimePipe
|
||||
}
|
||||
integration: {
|
||||
allowed: boolean
|
||||
input: boolean
|
||||
output: boolean
|
||||
}
|
||||
tags: GateTag[]
|
||||
properties: {
|
||||
enabled: boolean
|
||||
data: Property[]
|
||||
}
|
||||
innerText: {
|
||||
color: string
|
||||
enabled: boolean
|
||||
}
|
||||
category: number // for better sorting
|
||||
info: string[]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const textSettings = {
|
||||
font: '30px Roboto',
|
||||
offset: 35,
|
||||
fill: `rgba(256,256,256,0.75)`
|
||||
font: '30px monospace',
|
||||
offset: 35,
|
||||
fill: `rgba(256,256,256,0.75)`
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
|||
.map((key) =>
|
||||
chunked
|
||||
.flat()
|
||||
.filter((v, index) => index % 3 === key)
|
||||
.filter((_v, index) => index % 3 === key)
|
||||
.reduce((acc, curr) => acc + curr, 0)
|
||||
)
|
||||
.map((value) => Math.floor(value / digits.length))
|
||||
|
|
|
@ -10,64 +10,85 @@ import { idIsSelected } from './idIsSelected'
|
|||
import { textSettings } from '../data/textSettings'
|
||||
|
||||
export const renderGate = (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
renderer: SimulationRenderer,
|
||||
gate: Gate
|
||||
ctx: CanvasRenderingContext2D,
|
||||
renderer: SimulationRenderer,
|
||||
gate: Gate
|
||||
) => {
|
||||
const { active, normal } = gate.template.material.stroke
|
||||
const { active, normal } = gate.template.material.stroke
|
||||
|
||||
const selected =
|
||||
(renderer.mouseState >> 2 &&
|
||||
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
|
||||
idIsSelected(renderer, gate.id)
|
||||
const selected =
|
||||
(renderer.mouseState >> 2 &&
|
||||
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
|
||||
idIsSelected(renderer, gate.id)
|
||||
|
||||
renderPins(ctx, renderer, gate, selected)
|
||||
renderPins(ctx, renderer, gate, selected)
|
||||
|
||||
if (selected) {
|
||||
ctx.strokeStyle = active
|
||||
} else {
|
||||
ctx.strokeStyle = normal
|
||||
if (selected) {
|
||||
ctx.strokeStyle = active
|
||||
} else {
|
||||
ctx.strokeStyle = normal
|
||||
}
|
||||
|
||||
ctx.lineWidth = renderer.options.gates.gateStroke.width
|
||||
|
||||
ctx.save()
|
||||
|
||||
const relativeTransform = useTransform(ctx, gate.transform)
|
||||
const renderingParameters = [
|
||||
relativeTransform.x,
|
||||
relativeTransform.y,
|
||||
relativeTransform.width,
|
||||
relativeTransform.height,
|
||||
gate.template.shape.rounded ? gate.template.shape.radius : 0
|
||||
]
|
||||
|
||||
if (gate.template.material.type === 'image') {
|
||||
roundImage(
|
||||
ctx,
|
||||
ImageStore.get(gate.template.material.fill),
|
||||
...renderingParameters
|
||||
)
|
||||
}
|
||||
|
||||
roundRect(ctx, ...renderingParameters)
|
||||
|
||||
if (gate.template.material.type === 'color') {
|
||||
ctx.fillStyle = gate.template.material.fill
|
||||
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
ctx.stroke()
|
||||
|
||||
if (gate.template.tags.includes('integrated')) {
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.fillStyle = textSettings.fill
|
||||
ctx.fillText(
|
||||
gate.template.metadata.name,
|
||||
relativeTransform.center[0],
|
||||
relativeTransform.maxY + textSettings.offset
|
||||
)
|
||||
}
|
||||
|
||||
const text = gate.text.inner.value
|
||||
if (text !== null) {
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillStyle = textSettings.fill
|
||||
|
||||
let size = 30
|
||||
if (text.length >= 8) {
|
||||
size = 15
|
||||
} else if (text.length >= 6) {
|
||||
size = 20
|
||||
}
|
||||
|
||||
ctx.lineWidth = renderer.options.gates.gateStroke.width
|
||||
ctx.font = `${size}px monospace`
|
||||
ctx.fillText(
|
||||
text,
|
||||
relativeTransform.center[0],
|
||||
relativeTransform.center[1] + 2
|
||||
)
|
||||
}
|
||||
|
||||
ctx.save()
|
||||
|
||||
const relativeTransform = useTransform(ctx, gate.transform)
|
||||
const renderingParameters = [
|
||||
relativeTransform.x,
|
||||
relativeTransform.y,
|
||||
relativeTransform.width,
|
||||
relativeTransform.height,
|
||||
gate.template.shape.rounded ? gate.template.shape.radius : 0
|
||||
]
|
||||
|
||||
if (gate.template.material.type === 'image') {
|
||||
roundImage(
|
||||
ctx,
|
||||
ImageStore.get(gate.template.material.fill),
|
||||
...renderingParameters
|
||||
)
|
||||
}
|
||||
|
||||
roundRect(ctx, ...renderingParameters)
|
||||
|
||||
if (gate.template.material.type === 'color') {
|
||||
ctx.fillStyle = gate.template.material.fill
|
||||
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
ctx.stroke()
|
||||
|
||||
if (gate.template.tags.includes('integrated')) {
|
||||
ctx.fillStyle = textSettings.fill
|
||||
ctx.fillText(
|
||||
gate.template.metadata.name,
|
||||
relativeTransform.center[0],
|
||||
relativeTransform.maxY + textSettings.offset
|
||||
)
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
ctx.restore()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue