logic gate button
This commit is contained in:
parent
0c76f3cdf6
commit
e88244bc9d
|
@ -2,4 +2,5 @@ export interface Context {
|
||||||
memory: Record<string, unknown>
|
memory: Record<string, unknown>
|
||||||
get: (index: number) => boolean
|
get: (index: number) => boolean
|
||||||
set: (index: number, state: boolean) => void
|
set: (index: number, state: boolean) => void
|
||||||
|
color: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Theme from '@material-ui/styles/ThemeProvider'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
|
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
|
||||||
import Input from '../../input/components/Input'
|
import Input from '../../input/components/Input'
|
||||||
|
import LogicGateModal from '../../logic-gates/components/LogicGateModal'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -22,6 +23,7 @@ const App = () => {
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<CreateSimulation />
|
<CreateSimulation />
|
||||||
<Input />
|
<Input />
|
||||||
|
<LogicGateModal />
|
||||||
</Theme>
|
</Theme>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
|
|
5
src/modules/core/components/Language.scss
Normal file
5
src/modules/core/components/Language.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@import '../styles/colors.scss';
|
||||||
|
|
||||||
|
#language-button {
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
31
src/modules/core/components/Language.tsx
Normal file
31
src/modules/core/components/Language.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Icon from '@material-ui/core/Icon'
|
||||||
|
import List from '@material-ui/core/List'
|
||||||
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
|
import ListItemIconText from '@material-ui/core/ListItemText'
|
||||||
|
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||||
|
import { nextLanguage } from '../../internalisation/helpers/nextLanguage'
|
||||||
|
import './Language.scss'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The language component from the sidebar
|
||||||
|
*/
|
||||||
|
const Language = () => {
|
||||||
|
const translation = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<ListItem button onClick={nextLanguage} id="language-button">
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>language</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemIconText>
|
||||||
|
{translation.sidebar.language}: {translation.language}
|
||||||
|
</ListItemIconText>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Language
|
|
@ -1,32 +1,11 @@
|
||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import ListItem from '@material-ui/core/ListItem'
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
import Menu from '@material-ui/core/Menu'
|
|
||||||
import MenuItem from '@material-ui/core/MenuItem'
|
|
||||||
import Icon from '@material-ui/core/Icon'
|
import Icon from '@material-ui/core/Icon'
|
||||||
import Typography from '@material-ui/core/Typography'
|
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
import { useObservable } from 'rxjs-hooks'
|
|
||||||
import { switchTo } from '../../saving/helpers/switchTo'
|
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
|
||||||
import { templateStore } from '../../saving/stores/templateStore'
|
|
||||||
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||||
|
import { open } from '../../logic-gates/components/LogicGateModal'
|
||||||
/**
|
import { updateLogicGateList } from '../../logic-gates/subjects/LogicGateList'
|
||||||
* Subject to make React update the dom when new gates are stored
|
|
||||||
*/
|
|
||||||
const allGatesSubject = new BehaviorSubject<string[]>([])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers a dom update by pushing a new value to the
|
|
||||||
* useObservable hook inside the React component.
|
|
||||||
*
|
|
||||||
* It also has the side effect of sorting the template names.
|
|
||||||
*/
|
|
||||||
const updateTemplateList = () => {
|
|
||||||
allGatesSubject.next(templateStore.ls().sort())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component wich contains the sidebar 'Open simulation' button
|
* Component wich contains the sidebar 'Open simulation' button
|
||||||
|
@ -34,59 +13,21 @@ const updateTemplateList = () => {
|
||||||
* @throws SimulationError if the data about a simulation cant be found in localStorage
|
* @throws SimulationError if the data about a simulation cant be found in localStorage
|
||||||
*/
|
*/
|
||||||
const LogicGates = () => {
|
const LogicGates = () => {
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
|
||||||
const simulations = useObservable(() => allGatesSubject, [])
|
|
||||||
|
|
||||||
const translation = useTranslation()
|
const translation = useTranslation()
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ListItem
|
||||||
<ListItem
|
button
|
||||||
button
|
onClick={() => {
|
||||||
onClick={event => {
|
updateLogicGateList()
|
||||||
updateTemplateList()
|
open.next(true)
|
||||||
setAnchorEl(event.currentTarget)
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<ListItemIcon>
|
||||||
<ListItemIcon>
|
<Icon>memory</Icon>
|
||||||
<Icon>memory</Icon>
|
</ListItemIcon>
|
||||||
</ListItemIcon>
|
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
|
||||||
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
|
</ListItem>
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
keepMounted
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
onClose={handleClose}
|
|
||||||
>
|
|
||||||
{simulations.map((simulationName, index) => {
|
|
||||||
const simulationData = templateStore.get(simulationName)
|
|
||||||
|
|
||||||
if (!simulationData) {
|
|
||||||
throw new SimulationError(
|
|
||||||
`Cannot get data for simulation ${simulationName}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
key={index}
|
|
||||||
onClick={() => {
|
|
||||||
switchTo(simulationName)
|
|
||||||
handleClose()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography>{simulationName}</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Menu>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import OpenSimulation from './OpenSimulation'
|
||||||
import CreateSimulationButton from './CreateSimulationButton'
|
import CreateSimulationButton from './CreateSimulationButton'
|
||||||
import LogicGates from './LogicGates'
|
import LogicGates from './LogicGates'
|
||||||
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
||||||
|
import Language from './Language'
|
||||||
/**
|
/**
|
||||||
* The width of the sidebar
|
* The width of the sidebar
|
||||||
*/
|
*/
|
||||||
|
@ -39,6 +40,11 @@ const useStyles = makeStyles(
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
width: sidebarWidth,
|
width: sidebarWidth,
|
||||||
zIndex: sidebarZIndex
|
zIndex: sidebarZIndex
|
||||||
|
},
|
||||||
|
|
||||||
|
// This is the class for the main button list
|
||||||
|
list: {
|
||||||
|
flexGrow: 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -60,11 +66,13 @@ const Sidebar = () => {
|
||||||
paper: classes.drawerPaper
|
paper: classes.drawerPaper
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List component="nav">
|
<List component="nav" className={classes.list}>
|
||||||
<CreateSimulationButton />
|
<CreateSimulationButton />
|
||||||
<OpenSimulation />
|
<OpenSimulation />
|
||||||
<LogicGates />
|
<LogicGates />
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
<Language />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
$modal-bg-color: rgba(0, 0, 0, 0.7);
|
$modal-bg-color: rgba(0, 0, 0, 0.7);
|
||||||
$primary: #673ab7;
|
$primary: #673ab7;
|
||||||
|
$grey: #444444;
|
||||||
|
|
16
src/modules/core/styles/mixins/glow.scss
Normal file
16
src/modules/core/styles/mixins/glow.scss
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
@keyframes glow {
|
||||||
|
from {
|
||||||
|
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #e60073,
|
||||||
|
0 0 40px #e60073, 0 0 50px #e60073, 0 0 60px #e60073,
|
||||||
|
0 0 70px #e60073;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
text-shadow: 0 0 20px #fff, 0 0 30px #ff4da6, 0 0 40px #ff4da6,
|
||||||
|
0 0 50px #ff4da6, 0 0 60px #ff4da6, 0 0 70px #ff4da6,
|
||||||
|
0 0 80px #ff4da6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin glow {
|
||||||
|
animation: glow 1s ease-in-out infinite alternate;
|
||||||
|
}
|
|
@ -444,3 +444,8 @@ textarea {
|
||||||
color: #000;
|
color: #000;
|
||||||
padding: 0.2em 0;
|
padding: 0.2em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../../core/styles/colors.scss';
|
||||||
|
|
||||||
#create-options {
|
#create-options {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -7,7 +9,7 @@
|
||||||
.create-option {
|
.create-option {
|
||||||
@include flex();
|
@include flex();
|
||||||
|
|
||||||
background-color: #444444;
|
background-color: $grey;
|
||||||
height: 17em;
|
height: 17em;
|
||||||
width: 17em;
|
width: 17em;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import { supportedLanguages } from './types/supportedLanguages'
|
import { supportedLanguage } from './types/supportedLanguages'
|
||||||
import { Translation } from './types/TranslationInterface'
|
import { Translation } from './types/TranslationInterface'
|
||||||
import { EnglishTranslation } from './translations/english'
|
import { EnglishTranslation } from './translations/english'
|
||||||
import { RomanianTranslation } from './translations/romanian'
|
import { RomanianTranslation } from './translations/romanian'
|
||||||
|
import { DutchTranslation } from './translations/nederlands'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with all translations
|
* Object with all translations
|
||||||
*/
|
*/
|
||||||
export const translations: Record<supportedLanguages, Translation> = {
|
export const translations: Record<supportedLanguage, Translation> = {
|
||||||
english: EnglishTranslation,
|
english: EnglishTranslation,
|
||||||
['română']: RomanianTranslation
|
['română']: RomanianTranslation,
|
||||||
|
dutch: DutchTranslation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const allSupportedLanguages: supportedLanguage[] = [
|
||||||
|
'english',
|
||||||
|
'română',
|
||||||
|
'dutch'
|
||||||
|
]
|
||||||
|
|
17
src/modules/internalisation/helpers/nextLanguage.ts
Normal file
17
src/modules/internalisation/helpers/nextLanguage.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {
|
||||||
|
CurrentLanguage,
|
||||||
|
CurrentLanguageLocalStore
|
||||||
|
} from '../stores/currentLanguage'
|
||||||
|
import { allSupportedLanguages } from '../constants'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the language to the next one avabile
|
||||||
|
*/
|
||||||
|
export const nextLanguage = () => {
|
||||||
|
const current = CurrentLanguage.get()
|
||||||
|
const index = allSupportedLanguages.indexOf(current)
|
||||||
|
|
||||||
|
CurrentLanguage.set(
|
||||||
|
allSupportedLanguages[(index + 1) % allSupportedLanguages.length]
|
||||||
|
)
|
||||||
|
}
|
7
src/modules/internalisation/helpers/randomItem.ts
Normal file
7
src/modules/internalisation/helpers/randomItem.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Returns a random element of the aray
|
||||||
|
*
|
||||||
|
* @param arr - the array to choose from
|
||||||
|
*/
|
||||||
|
export const randomItem = <T>(arr: T[]): T =>
|
||||||
|
arr[Math.floor(Math.random() * arr.length)]
|
|
@ -1,4 +1,4 @@
|
||||||
import { supportedLanguages } from '../types/supportedLanguages'
|
import { supportedLanguage } from '../types/supportedLanguages'
|
||||||
import { LocalStore } from '../../storage/classes/LocalStore'
|
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||||
import currentLanguageSubject from './../subjects/currentLanguageSubject'
|
import currentLanguageSubject from './../subjects/currentLanguageSubject'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
|
@ -7,7 +7,7 @@ import { translations } from '../constants'
|
||||||
/**
|
/**
|
||||||
* Local store containing the current selected language
|
* Local store containing the current selected language
|
||||||
*/
|
*/
|
||||||
export const CurrentLanguageLocalStore = new LocalStore<supportedLanguages>(
|
export const CurrentLanguageLocalStore = new LocalStore<supportedLanguage>(
|
||||||
'language'
|
'language'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ currentLanguageSubject.next(CurrentLanguageLocalStore.get() || 'english')
|
||||||
* The preffered interface for interacting with CurrentLanguageLocalStore
|
* The preffered interface for interacting with CurrentLanguageLocalStore
|
||||||
*/
|
*/
|
||||||
const CurrentLanguage = {
|
const CurrentLanguage = {
|
||||||
set(name: supportedLanguages) {
|
set(name: supportedLanguage) {
|
||||||
CurrentLanguageLocalStore.set(name)
|
CurrentLanguageLocalStore.set(name)
|
||||||
currentLanguageSubject.next(name)
|
currentLanguageSubject.next(name)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject } from 'rxjs'
|
||||||
import { supportedLanguages } from '../types/supportedLanguages'
|
import { supportedLanguage } from '../types/supportedLanguages'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject with the current language
|
* Subject with the current language
|
||||||
*/
|
*/
|
||||||
export default new BehaviorSubject<supportedLanguages>('english')
|
export default new BehaviorSubject<supportedLanguage>('english')
|
||||||
|
|
|
@ -8,7 +8,8 @@ export const EnglishTranslation: Translation = {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
createSimulation: 'Create simulation',
|
createSimulation: 'Create simulation',
|
||||||
logicGates: 'Logic gates',
|
logicGates: 'Logic gates',
|
||||||
openSimulation: 'Open simulations'
|
openSimulation: 'Open simulations',
|
||||||
|
language: 'Language'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
mode: {
|
mode: {
|
||||||
|
|
33
src/modules/internalisation/translations/nederlands.ts
Normal file
33
src/modules/internalisation/translations/nederlands.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Translation } from '../types/TranslationInterface'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dutch translation
|
||||||
|
*/
|
||||||
|
export const DutchTranslation: Translation = {
|
||||||
|
language: 'dutch',
|
||||||
|
sidebar: {
|
||||||
|
createSimulation: 'Maak simulatie',
|
||||||
|
logicGates: 'Logische poorten',
|
||||||
|
openSimulation: 'Open simulatie',
|
||||||
|
language: 'Taal'
|
||||||
|
},
|
||||||
|
createSimulation: {
|
||||||
|
mode: {
|
||||||
|
question: 'Wat voor simulatie wil je maken?',
|
||||||
|
options: {
|
||||||
|
// ic: 'Geïntegreerde schakeling',
|
||||||
|
ic: 'IC',
|
||||||
|
project: 'Project'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
question: 'Hoe wil je je simulatie noemen?'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
createdSimulation: name => `Simulatie '${name}' succesvol gecreerd`,
|
||||||
|
switchedToSimulation: name =>
|
||||||
|
`Succesvol veranderd naar simulatie '${name}'`,
|
||||||
|
savedSimulation: name => `Simulatie succesvol opgeslagen '${name}'`
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,12 @@ export const RomanianTranslation: Translation = {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
createSimulation: 'Creează o simulație',
|
createSimulation: 'Creează o simulație',
|
||||||
openSimulation: 'Deschide o simulație',
|
openSimulation: 'Deschide o simulație',
|
||||||
logicGates: 'Porți logice'
|
logicGates: 'Porți logice',
|
||||||
|
language: 'Limba'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
mode: {
|
mode: {
|
||||||
question: 'Ce fel de simulație vrei să creiezi?',
|
question: 'Ce fel de simulație vrei să creezi?',
|
||||||
options: {
|
options: {
|
||||||
ic: 'Circuit integrat',
|
ic: 'Circuit integrat',
|
||||||
project: 'Proiect'
|
project: 'Proiect'
|
||||||
|
@ -24,7 +25,7 @@ export const RomanianTranslation: Translation = {
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name =>
|
createdSimulation: name =>
|
||||||
`Simulația '${name}' a fost creiată cu succes`,
|
`Simulația '${name}' a fost creeată cu succes`,
|
||||||
switchedToSimulation: name =>
|
switchedToSimulation: name =>
|
||||||
`Simulația '${name}' a fost deschisă cu succes`,
|
`Simulația '${name}' a fost deschisă cu succes`,
|
||||||
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`
|
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { supportedLanguages } from './supportedLanguages'
|
import { supportedLanguage } from './supportedLanguages'
|
||||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
|
||||||
export type SentenceFactory<T extends string[]> = (...names: T) => string
|
export type SentenceFactory<T extends string[]> = (...names: T) => string
|
||||||
|
@ -8,11 +8,12 @@ export type NameSentence = SentenceFactory<[string]>
|
||||||
* The interface all translations need to follow
|
* The interface all translations need to follow
|
||||||
*/
|
*/
|
||||||
export interface Translation {
|
export interface Translation {
|
||||||
language: supportedLanguages
|
language: supportedLanguage
|
||||||
sidebar: {
|
sidebar: {
|
||||||
createSimulation: string
|
createSimulation: string
|
||||||
openSimulation: string
|
openSimulation: string
|
||||||
logicGates: string
|
logicGates: string
|
||||||
|
language: string
|
||||||
}
|
}
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
mode: {
|
mode: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* Type containing the names of all supported languages
|
* Type containing the names of all supported languages
|
||||||
*/
|
*/
|
||||||
export type supportedLanguages = 'română' | 'english'
|
export type supportedLanguage = 'română' | 'english' | 'dutch'
|
||||||
|
|
62
src/modules/logic-gates/components/LogicGateModal.scss
Normal file
62
src/modules/logic-gates/components/LogicGateModal.scss
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
@import '../../core/styles/mixins/flex.scss';
|
||||||
|
@import '../../core/styles/mixins/visibility.scss';
|
||||||
|
@import '../../modals/styles/mixins/modal-container.scss';
|
||||||
|
@import '../../modals/styles/mixins/modal-title.scss';
|
||||||
|
@import '../../core/styles/colors.scss';
|
||||||
|
@import '../../core/styles/mixins/glow.scss';
|
||||||
|
|
||||||
|
// Opacities
|
||||||
|
$normal-opacity: 0.6;
|
||||||
|
$hover-opacity: 0.9;
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
$item-color: $grey;
|
||||||
|
|
||||||
|
#logic-gate-modal-container {
|
||||||
|
@include modal-container();
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible#logic-gate-modal-container {
|
||||||
|
@include visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item {
|
||||||
|
@include flex();
|
||||||
|
|
||||||
|
justify-content: left;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
width: 80%;
|
||||||
|
height: 4em;
|
||||||
|
|
||||||
|
border: 3px solid white;
|
||||||
|
margin: 0.5em;
|
||||||
|
|
||||||
|
background-color: rgba($item-color, $normal-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item:hover {
|
||||||
|
background-color: rgba($item-color, $hover-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item > * {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item > .logic-gate-item-type {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item > *:last-child {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lgi-icon:hover:not(.logic-gate-item-type) {
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logic-gate-modal-container > .logic-gate-item > .logic-gate-item-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
90
src/modules/logic-gates/components/LogicGateModal.tsx
Normal file
90
src/modules/logic-gates/components/LogicGateModal.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import './LogicGateModal.scss'
|
||||||
|
import React from 'react'
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { useObservable } from 'rxjs-hooks'
|
||||||
|
import { LogicGateList } from '../subjects/LogicGateList'
|
||||||
|
import Icon from '@material-ui/core/Icon'
|
||||||
|
import Typography from '@material-ui/core/Typography'
|
||||||
|
import { addGate } from '../../simulation/helpers/addGate'
|
||||||
|
import { rendererSubject } from '../../core/subjects/rendererSubject'
|
||||||
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
|
import { templateStore } from '../../saving/stores/templateStore'
|
||||||
|
import { randomItem } from '../../internalisation/helpers/randomItem'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subject containing the open state of the modal
|
||||||
|
*/
|
||||||
|
export const open = new BehaviorSubject(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper to close the modal
|
||||||
|
*/
|
||||||
|
export const handleClose = () => {
|
||||||
|
open.next(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component containing the info / actions about all logic gates
|
||||||
|
*/
|
||||||
|
const LogicGateModal = () => {
|
||||||
|
const openSnapshot = useObservable(() => open, false)
|
||||||
|
const gates = useObservable(() => LogicGateList, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={openSnapshot ? 'visible' : ''}
|
||||||
|
id="logic-gate-modal-container"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
{gates.map((gate, index) => {
|
||||||
|
const renderer = rendererSubject.value
|
||||||
|
|
||||||
|
if (!renderer) {
|
||||||
|
throw new SimulationError(`Renderer not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const template =
|
||||||
|
gate.source === 'base' ? templateStore.get(gate.name) : ''
|
||||||
|
|
||||||
|
if (gate.source === 'base' && !template) {
|
||||||
|
throw new SimulationError(
|
||||||
|
`Template ${gate.name} cannot be found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="logic-gate-item"
|
||||||
|
onClick={e => {
|
||||||
|
addGate(renderer.simulation, gate.name)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon className="lgi-icon logic-gate-item-type">
|
||||||
|
{gate.source === 'base' ? 'sd_storage' : 'memory'}
|
||||||
|
</Icon>
|
||||||
|
<Typography className="logic-gate-item-name">
|
||||||
|
{gate.name}
|
||||||
|
</Typography>
|
||||||
|
{template && template.info && template.info.length && (
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
className="logic-gate-item-info"
|
||||||
|
href={randomItem(template.info)}
|
||||||
|
>
|
||||||
|
<Icon className="lgi-icon">info</Icon>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{gate.source === 'ic' && (
|
||||||
|
<Icon className="lgi-icon logic-gate-item-delete">
|
||||||
|
delete
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogicGateModal
|
26
src/modules/logic-gates/helpers/getAllIcs.ts
Normal file
26
src/modules/logic-gates/helpers/getAllIcs.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the names of all integrated circuits
|
||||||
|
*
|
||||||
|
* @throws SimulationError if a save cannot be found in localsStorage
|
||||||
|
*/
|
||||||
|
export const getAllics = () => {
|
||||||
|
const saves = saveStore.ls()
|
||||||
|
const result: string[] = []
|
||||||
|
|
||||||
|
for (const save of saves) {
|
||||||
|
const saveState = saveStore.get(save)
|
||||||
|
|
||||||
|
if (saveState) {
|
||||||
|
if (saveState.simulation.mode === 'ic') {
|
||||||
|
result.push(saveState.simulation.name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new SimulationError(`Cannot find save ${save}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
37
src/modules/logic-gates/subjects/LogicGateList.ts
Normal file
37
src/modules/logic-gates/subjects/LogicGateList.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { templateStore } from '../../saving/stores/templateStore'
|
||||||
|
import { getAllics } from '../helpers/getAllIcs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for the items in the list
|
||||||
|
*/
|
||||||
|
export interface LogicGateNameWrapper {
|
||||||
|
source: 'base' | 'ic'
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subject containing a list with the names of all logic gate templates
|
||||||
|
*/
|
||||||
|
export const LogicGateList = new BehaviorSubject<LogicGateNameWrapper[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to update the list of logic gate templates.
|
||||||
|
*/
|
||||||
|
export const updateLogicGateList = () => {
|
||||||
|
const ics = getAllics().map(
|
||||||
|
(name): LogicGateNameWrapper => ({
|
||||||
|
source: 'ic',
|
||||||
|
name
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const templates = templateStore.ls().map(
|
||||||
|
(name): LogicGateNameWrapper => ({
|
||||||
|
source: 'base',
|
||||||
|
name
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
LogicGateList.next([...ics, ...templates])
|
||||||
|
}
|
|
@ -3,25 +3,118 @@ import { RendererState } from './types/SimulationSave'
|
||||||
|
|
||||||
export const defaultSimulationName = 'default'
|
export const defaultSimulationName = 'default'
|
||||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'and'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
value: 'green'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `context.set(0, context.get(0) && context.get(1))`
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/AND_gate']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'or'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
value: 'yellow'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `context.set(0, context.get(0) || context.get(1))`
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/OR_gate']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'xor'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
value: 'white'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `
|
||||||
|
const a = context.get(0)
|
||||||
|
const b = context.get(1)
|
||||||
|
const c = (a || b) && (!a || !b)
|
||||||
|
|
||||||
|
context.set(0, c)`
|
||||||
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/XOR_gate'],
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'not'
|
name: 'not'
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
value: 'red',
|
value: 'red'
|
||||||
type: 'color'
|
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `context.set(0, !context.get(0))`
|
activation: `context.set(0, !context.get(0))`
|
||||||
},
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/Inverter_(logic_gate)']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'button'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
value: 'red'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
onClick: `
|
||||||
|
const old = context.memory.state
|
||||||
|
const state = !old
|
||||||
|
|
||||||
|
context.set(0, state)
|
||||||
|
context.color(old ? 'red' : '#550000')
|
||||||
|
|
||||||
|
context.memory.state = state
|
||||||
|
`
|
||||||
|
},
|
||||||
pins: {
|
pins: {
|
||||||
inputs: {
|
inputs: {
|
||||||
count: 1,
|
count: 0
|
||||||
variable: false
|
}
|
||||||
},
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/Push-button']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'light bulb'
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
radius: 50
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
value: 'white'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `
|
||||||
|
context.color(context.get(0) ? 'yellow' : 'white')
|
||||||
|
`
|
||||||
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||||
|
pins: {
|
||||||
outputs: {
|
outputs: {
|
||||||
count: 1,
|
count: 0
|
||||||
variable: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,11 @@ export interface PinWrapper {
|
||||||
value: Pin
|
value: Pin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GateFunction = null | ((ctx: Context) => void)
|
||||||
|
|
||||||
export interface GateFunctions {
|
export interface GateFunctions {
|
||||||
activation: null | ((ctx: Context) => void)
|
activation: GateFunction
|
||||||
|
onClick: GateFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Gate {
|
export class Gate {
|
||||||
|
@ -37,7 +40,8 @@ export class Gate {
|
||||||
public template: GateTemplate
|
public template: GateTemplate
|
||||||
|
|
||||||
private functions: GateFunctions = {
|
private functions: GateFunctions = {
|
||||||
activation: null
|
activation: null,
|
||||||
|
onClick: null
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscriptions: Subscription[] = []
|
private subscriptions: Subscription[] = []
|
||||||
|
@ -53,6 +57,11 @@ export class Gate {
|
||||||
'context'
|
'context'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.functions.onClick = toFunction(
|
||||||
|
this.template.code.onClick,
|
||||||
|
'context'
|
||||||
|
)
|
||||||
|
|
||||||
this._pins.inputs = Gate.generatePins(
|
this._pins.inputs = Gate.generatePins(
|
||||||
this.template.pins.inputs,
|
this.template.pins.inputs,
|
||||||
1,
|
1,
|
||||||
|
@ -77,6 +86,12 @@ export class Gate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onClick() {
|
||||||
|
if (this.functions.onClick) {
|
||||||
|
this.functions.onClick(this.getContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
for (const pin of this.pins) {
|
for (const pin of this.pins) {
|
||||||
pin.value.dispose()
|
pin.value.dispose()
|
||||||
|
@ -104,7 +119,12 @@ export class Gate {
|
||||||
set: (index: number, state: boolean = false) => {
|
set: (index: number, state: boolean = false) => {
|
||||||
return this._pins.outputs[index].state.next(state)
|
return this._pins.outputs[index].state.next(state)
|
||||||
},
|
},
|
||||||
memory: this.memory
|
memory: this.memory,
|
||||||
|
color: (color: string) => {
|
||||||
|
if (this.template.material.type === 'color') {
|
||||||
|
this.template.material.value = color
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
},
|
},
|
||||||
pins: {
|
pins: {
|
||||||
inputs: {
|
inputs: {
|
||||||
count: 2,
|
count: 1,
|
||||||
variable: false
|
variable: false
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
|
@ -25,8 +25,7 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: 'context.set(0,true)',
|
activation: 'context.set(0,true)',
|
||||||
start: '',
|
onClick: ''
|
||||||
stop: ''
|
|
||||||
},
|
},
|
||||||
simulation: {
|
simulation: {
|
||||||
debounce: {
|
debounce: {
|
||||||
|
@ -36,5 +35,6 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
throttle: {
|
throttle: {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
info: []
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,12 @@ export interface GateTemplate {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
code: {
|
code: {
|
||||||
start: string
|
|
||||||
activation: string
|
activation: string
|
||||||
stop: string
|
onClick: string
|
||||||
}
|
}
|
||||||
simulation: {
|
simulation: {
|
||||||
throttle: TimePipe
|
throttle: TimePipe
|
||||||
debounce: TimePipe
|
debounce: TimePipe
|
||||||
}
|
}
|
||||||
|
info: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,9 @@ export class SimulationRenderer {
|
||||||
const { transform, id, pins } = gates[index]
|
const { transform, id, pins } = gates[index]
|
||||||
|
|
||||||
if (pointInSquare(worldPosition, transform)) {
|
if (pointInSquare(worldPosition, transform)) {
|
||||||
|
// run function
|
||||||
|
gates[index].onClick()
|
||||||
|
|
||||||
this.mouseManager.clear(worldPosition[0])
|
this.mouseManager.clear(worldPosition[0])
|
||||||
|
|
||||||
this.mouseState |= 1
|
this.mouseState |= 1
|
||||||
|
|
Loading…
Reference in a new issue