internalisation
This commit is contained in:
parent
097c44e86e
commit
0c76f3cdf6
BIN
docs/assets/files.png
Normal file
BIN
docs/assets/files.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
|
@ -6,6 +6,8 @@ import { handleErrors } from './modules/errors/helpers/handleErrors'
|
|||
import { initKeyBindings } from './modules/keybindings/helpers/initialiseKeyBindings'
|
||||
import { initBaseTemplates } from './modules/saving/helpers/initBaseTemplates'
|
||||
|
||||
console.clear()
|
||||
|
||||
handleErrors()
|
||||
initKeyBindings()
|
||||
initBaseTemplates()
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React, { Component, createRef, Ref, RefObject } from 'react'
|
||||
import FluidCanvas from './FluidCanvas'
|
||||
import loop from 'mainloop.js'
|
||||
import { Gate } from '../../simulation/classes/Gate'
|
||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
||||
import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation'
|
||||
import { addGate } from '../../simulation/helpers/addGate'
|
||||
import { rendererSubject } from '../subjects/rendererSubject'
|
||||
|
||||
class Canvas extends Component {
|
||||
|
@ -18,8 +16,6 @@ class Canvas extends Component {
|
|||
|
||||
rendererSubject.next(this.renderer)
|
||||
|
||||
addGate(this.renderer.simulation, 'not')
|
||||
|
||||
loop.setDraw(() => {
|
||||
if (this.renderingContext) {
|
||||
renderSimulation(this.renderingContext, this.renderer)
|
||||
|
|
28
src/modules/core/components/CreateSimulationButton.tsx
Normal file
28
src/modules/core/components/CreateSimulationButton.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import Icon from '@material-ui/core/Icon'
|
||||
import { handleCreating } from '../../create-simulation/helpers/handleCreating'
|
||||
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||
|
||||
/**
|
||||
* The component for the 'Create simulation' button from the top of the sidebar.
|
||||
*
|
||||
* The only way i found to apply a different color to the ListItem button was
|
||||
* by using !important in the scss.
|
||||
*/
|
||||
const CreateSimulationButton = () => {
|
||||
const translation = useTranslation()
|
||||
|
||||
return (
|
||||
<ListItem button className="contained" onClick={handleCreating}>
|
||||
<ListItemIcon>
|
||||
<Icon>note_add</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText>{translation.sidebar.createSimulation}</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateSimulationButton
|
93
src/modules/core/components/LogicGates.tsx
Normal file
93
src/modules/core/components/LogicGates.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
import React, { useState } from 'react'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
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 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'
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @throws SimulationError if the data about a simulation cant be found in localStorage
|
||||
*/
|
||||
const LogicGates = () => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
const simulations = useObservable(() => allGatesSubject, [])
|
||||
|
||||
const translation = useTranslation()
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem
|
||||
button
|
||||
onClick={event => {
|
||||
updateTemplateList()
|
||||
setAnchorEl(event.currentTarget)
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Icon>memory</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogicGates
|
|
@ -5,24 +5,48 @@ 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 Typography from '@material-ui/core/Typography'
|
||||
import { saveStore } from '../../saving/stores/saveStore'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { useObservable } from 'rxjs-hooks'
|
||||
import { rendererSubject } from '../subjects/rendererSubject'
|
||||
import { currentStore } from '../../saving/stores/currentStore'
|
||||
import { switchTo } from '../../saving/helpers/switchTo'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { icons } from '../constants'
|
||||
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||
|
||||
/**
|
||||
* Returns a list with the names of all saved simulations
|
||||
*/
|
||||
const allSimulations = () => {
|
||||
return saveStore.ls()
|
||||
}
|
||||
|
||||
/**
|
||||
* Subject to make React update the dom when new simulations are stored
|
||||
*/
|
||||
const allSimulationSubject = 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 simulation names.
|
||||
*/
|
||||
const updateSimulationList = () => {
|
||||
allSimulationSubject.next(allSimulations())
|
||||
allSimulationSubject.next(allSimulations().sort())
|
||||
}
|
||||
|
||||
/**
|
||||
* Component wich contains the sidebar 'Open simulation' button
|
||||
*
|
||||
* @throws SimulationError if the data about a simulation cant be found in localStorage
|
||||
*/
|
||||
const OpenSimulation = () => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
const simulations = useObservable(() => allSimulationSubject, [])
|
||||
|
||||
const translation = useTranslation()
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
@ -39,7 +63,9 @@ const OpenSimulation = () => {
|
|||
<ListItemIcon>
|
||||
<Icon>folder_open</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText>Open simulation</ListItemText>
|
||||
<ListItemText>
|
||||
{translation.sidebar.openSimulation}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
|
||||
<Menu
|
||||
|
@ -48,23 +74,36 @@ const OpenSimulation = () => {
|
|||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{simulations.map((simulation, index) => (
|
||||
{simulations.map((simulationName, index) => {
|
||||
const simulationData = saveStore.get(simulationName)
|
||||
|
||||
if (!simulationData) {
|
||||
throw new SimulationError(
|
||||
`Cannot get data for simulation ${simulationName}`
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
onClick={() => {
|
||||
if (rendererSubject.value) {
|
||||
const renderer = rendererSubject.value
|
||||
|
||||
currentStore.set(simulation)
|
||||
renderer.reloadSave()
|
||||
}
|
||||
|
||||
switchTo(simulationName)
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
{simulation}
|
||||
<ListItemIcon>
|
||||
<Icon>
|
||||
{
|
||||
icons.simulationMode[
|
||||
simulationData.simulation.mode
|
||||
]
|
||||
}
|
||||
</Icon>
|
||||
</ListItemIcon>
|
||||
<Typography>{simulationName}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,35 +1,51 @@
|
|||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import Drawer from '@material-ui/core/Drawer'
|
||||
import List from '@material-ui/core/List'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import Icon from '@material-ui/core/Icon'
|
||||
import { handleCreating } from '../../create-simulation/helpers/handleCreating'
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
||||
import OpenSimulation from './OpenSimulation'
|
||||
import CreateSimulationButton from './CreateSimulationButton'
|
||||
import LogicGates from './LogicGates'
|
||||
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
||||
/**
|
||||
* The width of the sidebar
|
||||
*/
|
||||
const sidebarWidth = 240
|
||||
|
||||
const drawerWidth = 240
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
/**
|
||||
* The z-index of the sidebar.
|
||||
*/
|
||||
const sidebarZIndex = 5
|
||||
|
||||
/**
|
||||
* The styles for the sidebar component
|
||||
*/
|
||||
const useStyles = makeStyles(
|
||||
createStyles({
|
||||
// This class is applied on the sidebar container
|
||||
root: {
|
||||
display: 'flex',
|
||||
zIndex: 5
|
||||
zIndex: sidebarZIndex
|
||||
},
|
||||
|
||||
// This is the class of the actual sidebar
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
zIndex: 5
|
||||
width: sidebarWidth,
|
||||
zIndex: sidebarZIndex,
|
||||
flexShrink: 0
|
||||
},
|
||||
|
||||
// This is the class for the surface of the sidebar
|
||||
drawerPaper: {
|
||||
padding: '4px',
|
||||
width: drawerWidth,
|
||||
background: `#111111`,
|
||||
zIndex: 5
|
||||
padding: '4px',
|
||||
width: sidebarWidth,
|
||||
zIndex: sidebarZIndex
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* The sidebar component
|
||||
*/
|
||||
const Sidebar = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
|
@ -44,18 +60,10 @@ const Sidebar = () => {
|
|||
paper: classes.drawerPaper
|
||||
}}
|
||||
>
|
||||
<List component="nav" aria-label="Main mailbox folders">
|
||||
<ListItem
|
||||
button
|
||||
className="contained"
|
||||
onClick={handleCreating}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Icon>note_add</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText>Create simulation</ListItemText>
|
||||
</ListItem>
|
||||
<List component="nav">
|
||||
<CreateSimulationButton />
|
||||
<OpenSimulation />
|
||||
<LogicGates />
|
||||
</List>
|
||||
</Drawer>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createMuiTheme } from '@material-ui/core/styles'
|
||||
import { red, deepPurple } from '@material-ui/core/colors'
|
||||
import { simulationMode } from '../saving/types/SimulationSave'
|
||||
|
||||
export const theme = createMuiTheme({
|
||||
palette: {
|
||||
|
@ -8,3 +9,20 @@ export const theme = createMuiTheme({
|
|||
secondary: red
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Used to get intellisense from visual studio code
|
||||
*/
|
||||
export interface IconInterface {
|
||||
simulationMode: Record<simulationMode, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the names of icons reuseed truogh the app
|
||||
*/
|
||||
export const icons: IconInterface = {
|
||||
simulationMode: {
|
||||
project: 'gamepad',
|
||||
ic: 'memory'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,21 +4,19 @@ import { useObservable } from 'rxjs-hooks'
|
|||
import { CreateSimulationStore } from '../stores/CreateSimulationStore'
|
||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||
import Icon from '@material-ui/core/Icon'
|
||||
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||
|
||||
export interface CreateSimulationOption {
|
||||
mode: simulationMode
|
||||
icon: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const createSimulationOptions: CreateSimulationOption[] = [
|
||||
{
|
||||
name: 'project',
|
||||
mode: 'project',
|
||||
icon: 'gamepad'
|
||||
},
|
||||
{
|
||||
name: 'integrated circuit',
|
||||
icon: 'memory',
|
||||
mode: 'ic'
|
||||
}
|
||||
|
@ -26,6 +24,7 @@ export const createSimulationOptions: CreateSimulationOption[] = [
|
|||
|
||||
const CreateSimulation = () => {
|
||||
const open = useObservable(() => CreateSimulationStore.data.open, false)
|
||||
const translation = useTranslation()
|
||||
|
||||
const closeModal = () => {
|
||||
CreateSimulationStore.actions.next('quit')
|
||||
|
@ -38,7 +37,7 @@ const CreateSimulation = () => {
|
|||
onClick={closeModal}
|
||||
>
|
||||
<div id="create-title">
|
||||
What kind of simulation do you want to create?
|
||||
{translation.createSimulation.mode.question}
|
||||
</div>
|
||||
|
||||
<div id="create-options">
|
||||
|
@ -57,7 +56,11 @@ const CreateSimulation = () => {
|
|||
<Icon>{option.icon}</Icon>
|
||||
</div>
|
||||
<div className="create-option-name" id={option.mode}>
|
||||
{option.name}
|
||||
{
|
||||
translation.createSimulation.mode.options[
|
||||
option.mode
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { CreateSimulationStore } from '../stores/CreateSimulationStore'
|
||||
import { initSimulation } from '../../saving/helpers/initSimulation'
|
||||
import { switchTo } from '../../saving/helpers/switchTo'
|
||||
|
||||
export const handleCreating = async () => {
|
||||
const options = await CreateSimulationStore.create()
|
||||
|
@ -8,5 +9,7 @@ export const handleCreating = async () => {
|
|||
|
||||
const simulation = initSimulation(options.name, options.mode)
|
||||
|
||||
switchTo(options.name)
|
||||
|
||||
return simulation
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BehaviorSubject, Subject } from 'rxjs'
|
|||
import { take } from 'rxjs/operators'
|
||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||
import { InputStore } from '../../input/stores/InputStore'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
|
||||
export type CreateSimulationStoreAction = 'quit' | 'submit'
|
||||
|
||||
|
@ -13,6 +14,8 @@ export const CreateSimulationStore = {
|
|||
.pipe(take(1))
|
||||
.toPromise()
|
||||
|
||||
const translation = CurrentLanguage.getTranslation()
|
||||
|
||||
CreateSimulationStore.close()
|
||||
|
||||
if (action === 'quit') {
|
||||
|
@ -20,7 +23,7 @@ export const CreateSimulationStore = {
|
|||
}
|
||||
|
||||
const name = await InputStore.get(
|
||||
'What do you want your simulation to be called?'
|
||||
translation.createSimulation.name.question
|
||||
)
|
||||
|
||||
if (!name) {
|
||||
|
|
12
src/modules/internalisation/constants.ts
Normal file
12
src/modules/internalisation/constants.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { supportedLanguages } from './types/supportedLanguages'
|
||||
import { Translation } from './types/TranslationInterface'
|
||||
import { EnglishTranslation } from './translations/english'
|
||||
import { RomanianTranslation } from './translations/romanian'
|
||||
|
||||
/**
|
||||
* Object with all translations
|
||||
*/
|
||||
export const translations: Record<supportedLanguages, Translation> = {
|
||||
english: EnglishTranslation,
|
||||
['română']: RomanianTranslation
|
||||
}
|
16
src/modules/internalisation/helpers/useLanguage.ts
Normal file
16
src/modules/internalisation/helpers/useLanguage.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useObservable } from 'rxjs-hooks'
|
||||
import currentLanguageSubject from '../subjects/currentLanguageSubject'
|
||||
import { translations } from '../constants'
|
||||
|
||||
/**
|
||||
* Hook to get the current translation
|
||||
*/
|
||||
export const useTranslation = () => {
|
||||
const currentLanguage = useObservable(
|
||||
() => currentLanguageSubject,
|
||||
'english'
|
||||
)
|
||||
const translation = translations[currentLanguage]
|
||||
|
||||
return translation
|
||||
}
|
53
src/modules/internalisation/stores/currentLanguage.ts
Normal file
53
src/modules/internalisation/stores/currentLanguage.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { supportedLanguages } from '../types/supportedLanguages'
|
||||
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||
import currentLanguageSubject from './../subjects/currentLanguageSubject'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { translations } from '../constants'
|
||||
|
||||
/**
|
||||
* Local store containing the current selected language
|
||||
*/
|
||||
export const CurrentLanguageLocalStore = new LocalStore<supportedLanguages>(
|
||||
'language'
|
||||
)
|
||||
|
||||
// This makes sure the language isnt undefined
|
||||
if (!CurrentLanguageLocalStore.get()) {
|
||||
CurrentLanguageLocalStore.set('english')
|
||||
}
|
||||
|
||||
currentLanguageSubject.next(CurrentLanguageLocalStore.get() || 'english')
|
||||
|
||||
/**
|
||||
* The preffered interface for interacting with CurrentLanguageLocalStore
|
||||
*/
|
||||
const CurrentLanguage = {
|
||||
set(name: supportedLanguages) {
|
||||
CurrentLanguageLocalStore.set(name)
|
||||
currentLanguageSubject.next(name)
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to get the current selected store
|
||||
*
|
||||
* @throws SimulationError if the language cannot be found
|
||||
*/
|
||||
get() {
|
||||
const language = CurrentLanguageLocalStore.get()
|
||||
|
||||
if (!language) {
|
||||
throw new SimulationError(`Current language not found`)
|
||||
}
|
||||
|
||||
return language
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to get the current translation outside React components
|
||||
*/
|
||||
getTranslation() {
|
||||
return translations[CurrentLanguage.get()]
|
||||
}
|
||||
}
|
||||
|
||||
export { CurrentLanguage }
|
|
@ -0,0 +1,7 @@
|
|||
import { BehaviorSubject } from 'rxjs'
|
||||
import { supportedLanguages } from '../types/supportedLanguages'
|
||||
|
||||
/**
|
||||
* Subject with the current language
|
||||
*/
|
||||
export default new BehaviorSubject<supportedLanguages>('english')
|
31
src/modules/internalisation/translations/english.ts
Normal file
31
src/modules/internalisation/translations/english.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Translation } from '../types/TranslationInterface'
|
||||
|
||||
/**
|
||||
* The enaglish translation
|
||||
*/
|
||||
export const EnglishTranslation: Translation = {
|
||||
language: 'english',
|
||||
sidebar: {
|
||||
createSimulation: 'Create simulation',
|
||||
logicGates: 'Logic gates',
|
||||
openSimulation: 'Open simulations'
|
||||
},
|
||||
createSimulation: {
|
||||
mode: {
|
||||
question: 'What kind of simulation do you want to create?',
|
||||
options: {
|
||||
ic: 'Integrated circuit',
|
||||
project: 'Project'
|
||||
}
|
||||
},
|
||||
name: {
|
||||
question: 'What do you want your simulation to be called?'
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
||||
switchedToSimulation: name =>
|
||||
`Succesfully switched to simulation '${name}'`,
|
||||
savedSimulation: name => `Succesfully saved simulation '${name}'`
|
||||
}
|
||||
}
|
32
src/modules/internalisation/translations/romanian.ts
Normal file
32
src/modules/internalisation/translations/romanian.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Translation } from '../types/TranslationInterface'
|
||||
|
||||
/**
|
||||
* The romanian translation
|
||||
*/
|
||||
export const RomanianTranslation: Translation = {
|
||||
language: 'română',
|
||||
sidebar: {
|
||||
createSimulation: 'Creează o simulație',
|
||||
openSimulation: 'Deschide o simulație',
|
||||
logicGates: 'Porți logice'
|
||||
},
|
||||
createSimulation: {
|
||||
mode: {
|
||||
question: 'Ce fel de simulație vrei să creiezi?',
|
||||
options: {
|
||||
ic: 'Circuit integrat',
|
||||
project: 'Proiect'
|
||||
}
|
||||
},
|
||||
name: {
|
||||
question: 'Cum vrei să numești simulația?'
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
createdSimulation: name =>
|
||||
`Simulația '${name}' a fost creiată cu succes`,
|
||||
switchedToSimulation: name =>
|
||||
`Simulația '${name}' a fost deschisă cu succes`,
|
||||
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`
|
||||
}
|
||||
}
|
31
src/modules/internalisation/types/TranslationInterface.ts
Normal file
31
src/modules/internalisation/types/TranslationInterface.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { supportedLanguages } from './supportedLanguages'
|
||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||
|
||||
export type SentenceFactory<T extends string[]> = (...names: T) => string
|
||||
export type NameSentence = SentenceFactory<[string]>
|
||||
|
||||
/**
|
||||
* The interface all translations need to follow
|
||||
*/
|
||||
export interface Translation {
|
||||
language: supportedLanguages
|
||||
sidebar: {
|
||||
createSimulation: string
|
||||
openSimulation: string
|
||||
logicGates: string
|
||||
}
|
||||
createSimulation: {
|
||||
mode: {
|
||||
question: string
|
||||
options: Record<simulationMode, string>
|
||||
}
|
||||
name: {
|
||||
question: string
|
||||
}
|
||||
}
|
||||
messages: {
|
||||
createdSimulation: NameSentence
|
||||
switchedToSimulation: NameSentence
|
||||
savedSimulation: NameSentence
|
||||
}
|
||||
}
|
4
src/modules/internalisation/types/supportedLanguages.ts
Normal file
4
src/modules/internalisation/types/supportedLanguages.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Type containing the names of all supported languages
|
||||
*/
|
||||
export type supportedLanguages = 'română' | 'english'
|
|
@ -13,5 +13,5 @@
|
|||
z-index: $modal-index;
|
||||
color: white;
|
||||
background-color: $modal-bg-color;
|
||||
font-family: 'Righteous';
|
||||
font-family: 'Righteous', cursive;
|
||||
}
|
||||
|
|
|
@ -1 +1,6 @@
|
|||
/**
|
||||
* Very basic JSON based object cloning
|
||||
*
|
||||
* @param state The object to clone
|
||||
*/
|
||||
export const cloneState = <T>(state: T): T => JSON.parse(JSON.stringify(state))
|
||||
|
|
16
src/modules/saving/helpers/dumpSimulation.ts
Normal file
16
src/modules/saving/helpers/dumpSimulation.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||
|
||||
/**
|
||||
* Does the cleanup for switching to another simulation
|
||||
*
|
||||
* @param renderer The renderer to clean up
|
||||
*/
|
||||
export const dumpSimulation = (renderer: SimulationRenderer) => {
|
||||
renderer.simulation.dispose()
|
||||
renderer.lastMousePosition = [0, 0]
|
||||
renderer.selectedGate = null
|
||||
renderer.selectedPins = {
|
||||
end: null,
|
||||
start: null
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||
import { Gate, PinWrapper } from '../../simulation/classes/Gate'
|
||||
import {
|
||||
TransformState,
|
||||
RendererState,
|
||||
CameraState,
|
||||
SimulationState
|
||||
} from '../types/SimulationSave'
|
||||
|
@ -12,6 +10,10 @@ import { Simulation } from '../../simulation/classes/Simulation'
|
|||
import { Wire } from '../../simulation/classes/Wire'
|
||||
import { templateStore } from '../stores/templateStore'
|
||||
|
||||
/**
|
||||
* Contains methods for transforming saved state into the respective class instances
|
||||
*/
|
||||
|
||||
export const fromTransformState = (state: TransformState): Transform => {
|
||||
return new Transform(state.position, state.scale, state.rotation)
|
||||
}
|
||||
|
@ -25,7 +27,7 @@ export const fromCameraState = (state: CameraState): Camera => {
|
|||
}
|
||||
|
||||
export const fromSimulationState = (state: SimulationState): Simulation => {
|
||||
const simulation = new Simulation(state.mode)
|
||||
const simulation = new Simulation(state.mode, state.name)
|
||||
|
||||
for (const gateState of state.gates) {
|
||||
const gate = new Gate(
|
||||
|
|
|
@ -14,6 +14,10 @@ import { Camera } from '../../simulationRenderer/classes/Camera'
|
|||
import { Simulation } from '../../simulation/classes/Simulation'
|
||||
import { Wire } from '../../simulation/classes/Wire'
|
||||
|
||||
/**
|
||||
* Methods for gettings the savable state from class instances
|
||||
*/
|
||||
|
||||
export const getTransformState = (transform: Transform): TransformState => {
|
||||
return {
|
||||
position: transform.position,
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import { baseTemplates } from '../constants'
|
||||
import { templateStore } from '../stores/templateStore'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
|
||||
/**
|
||||
* Stores the base logic gate templates into localStorage
|
||||
*
|
||||
* @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.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,20 @@ import { cloneState } from './cloneState'
|
|||
import { saveStore } from '../stores/saveStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
|
||||
/**
|
||||
* Inits a simulation by:
|
||||
* 1) first initialising the place in localstorage
|
||||
* where the simulation will be saved
|
||||
* 2) notifying the used about that
|
||||
*
|
||||
* @param name - the name of the simulation
|
||||
* @param mode - the mode of the simulation
|
||||
*/
|
||||
export const initSimulation = (name: string, mode: simulationMode) => {
|
||||
const state = cloneState(baseSave)
|
||||
const translation = CurrentLanguage.getTranslation()
|
||||
|
||||
state.simulation.name = name
|
||||
state.simulation.mode = mode
|
||||
|
@ -15,7 +26,7 @@ export const initSimulation = (name: string, mode: simulationMode) => {
|
|||
|
||||
toast.success(
|
||||
...createToastArguments(
|
||||
`Successfully created simulation ${name}`,
|
||||
translation.messages.createdSimulation(name),
|
||||
'check'
|
||||
)
|
||||
)
|
||||
|
|
|
@ -5,16 +5,32 @@ import { getRendererState } from './getState'
|
|||
import { saveStore } from '../stores/saveStore'
|
||||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
|
||||
/**
|
||||
* Saves the state from a renderer in localStorage,
|
||||
* then notifies the user about it
|
||||
*
|
||||
* @throws SimulationError if the simulation name
|
||||
* cannot found in the currentStore
|
||||
*
|
||||
* @param renderer - the renderer to saev the state of
|
||||
*/
|
||||
export const save = (renderer: SimulationRenderer) => {
|
||||
const current = currentStore.get()
|
||||
|
||||
if (current) {
|
||||
const state = getRendererState(renderer)
|
||||
const translation = CurrentLanguage.getTranslation()
|
||||
|
||||
saveStore.set(current, state)
|
||||
|
||||
toast(...createToastArguments(`Succesfully saved ${current}`, 'save'))
|
||||
toast(
|
||||
...createToastArguments(
|
||||
translation.messages.savedSimulation(current),
|
||||
'save'
|
||||
)
|
||||
)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
'Cannot save without knowing the name of the active simulation'
|
||||
|
|
42
src/modules/saving/helpers/switchTo.ts
Normal file
42
src/modules/saving/helpers/switchTo.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { currentStore } from '../stores/currentStore'
|
||||
import { rendererSubject } from '../../core/subjects/rendererSubject'
|
||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||
import { toast } from 'react-toastify'
|
||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||
import { dumpSimulation } from './dumpSimulation'
|
||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||
|
||||
/**
|
||||
* Used to switch to a simulation
|
||||
*
|
||||
* @throws SimulationError if theres no renderer stored in the rendererSubject
|
||||
*
|
||||
* @param simulationName The name of the simulation to switch to
|
||||
*
|
||||
* @example
|
||||
* switchTo()
|
||||
* switchTo('test')
|
||||
*
|
||||
*/
|
||||
export const switchTo = (simulationName: string = 'default') => {
|
||||
if (rendererSubject.value) {
|
||||
const renderer = rendererSubject.value
|
||||
const translation = CurrentLanguage.getTranslation()
|
||||
|
||||
dumpSimulation(renderer)
|
||||
|
||||
currentStore.set(simulationName)
|
||||
renderer.reloadSave()
|
||||
|
||||
toast(
|
||||
...createToastArguments(
|
||||
translation.messages.switchedToSimulation(simulationName),
|
||||
'arrow_right_alt'
|
||||
)
|
||||
)
|
||||
} else {
|
||||
throw new SimulationError(
|
||||
`Renderer not found while trying to switch to simulation '${simulationName}'`
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||
import { defaultSimulationName } from '../constants'
|
||||
|
||||
/**
|
||||
* Stores the name of the current simulation
|
||||
*/
|
||||
const currentStore = new LocalStore<string>('currentSave')
|
||||
|
||||
// This makes sure the store isnt empty
|
||||
if (!currentStore.get()) {
|
||||
currentStore.set(defaultSimulationName)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||
import { RendererState } from '../types/SimulationSave'
|
||||
|
||||
/**
|
||||
* This store is used to save all simulations.
|
||||
*/
|
||||
export const saveStore = new LocalStore<RendererState>('saves')
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { LocalStore } from '../../storage/classes/LocalStore'
|
||||
import { GateTemplate } from '../../simulation/types/GateTemplate'
|
||||
|
||||
/**
|
||||
* This store is used to save all logic gate templates
|
||||
*/
|
||||
export const templateStore = new LocalStore<DeepPartial<GateTemplate>>(
|
||||
'templates'
|
||||
)
|
||||
|
|
|
@ -27,6 +27,7 @@ import { wireConnectedToGate } from '../helpers/wireConnectedToGate'
|
|||
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
||||
import { RefObject } from 'react'
|
||||
import { Singleton } from '@eix-js/utils'
|
||||
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
||||
|
||||
export class SimulationRenderer {
|
||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||
|
@ -200,6 +201,8 @@ export class SimulationRenderer {
|
|||
this.lastMousePosition = this.camera.toWordPostition(event.position)
|
||||
})
|
||||
|
||||
dumpSimulation(this)
|
||||
|
||||
this.reloadSave()
|
||||
this.initKeyBindings()
|
||||
}
|
||||
|
@ -222,7 +225,6 @@ export class SimulationRenderer {
|
|||
if (!save) return
|
||||
if (!(save.simulation || save.camera)) return
|
||||
|
||||
this.simulation.dispose()
|
||||
this.simulation = fromSimulationState(save.simulation)
|
||||
this.camera = fromCameraState(save.camera)
|
||||
} catch (e) {
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
|
||||
.toast-content-container > #toast-content-icon {
|
||||
font-size: 2em;
|
||||
margin: 10px;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue