+}
+
+/**
+ * Stores the names of icons reuseed truogh the app
+ */
+export const icons: IconInterface = {
+ simulationMode: {
+ project: 'gamepad',
+ ic: 'memory'
+ }
+}
diff --git a/src/modules/create-simulation/components/CreateSimulation.tsx b/src/modules/create-simulation/components/CreateSimulation.tsx
index 74b4e3f..72784ba 100644
--- a/src/modules/create-simulation/components/CreateSimulation.tsx
+++ b/src/modules/create-simulation/components/CreateSimulation.tsx
@@ -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}
>
- What kind of simulation do you want to create?
+ {translation.createSimulation.mode.question}
@@ -57,7 +56,11 @@ const CreateSimulation = () => {
{option.icon}
- {option.name}
+ {
+ translation.createSimulation.mode.options[
+ option.mode
+ ]
+ }
))}
diff --git a/src/modules/create-simulation/helpers/handleCreating.ts b/src/modules/create-simulation/helpers/handleCreating.ts
index 0c9cb92..7fa4a3f 100644
--- a/src/modules/create-simulation/helpers/handleCreating.ts
+++ b/src/modules/create-simulation/helpers/handleCreating.ts
@@ -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
}
diff --git a/src/modules/create-simulation/stores/CreateSimulationStore.ts b/src/modules/create-simulation/stores/CreateSimulationStore.ts
index c4546fe..93016a1 100644
--- a/src/modules/create-simulation/stores/CreateSimulationStore.ts
+++ b/src/modules/create-simulation/stores/CreateSimulationStore.ts
@@ -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) {
diff --git a/src/modules/internalisation/constants.ts b/src/modules/internalisation/constants.ts
new file mode 100644
index 0000000..c7f743a
--- /dev/null
+++ b/src/modules/internalisation/constants.ts
@@ -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 = {
+ english: EnglishTranslation,
+ ['română']: RomanianTranslation
+}
diff --git a/src/modules/internalisation/helpers/useLanguage.ts b/src/modules/internalisation/helpers/useLanguage.ts
new file mode 100644
index 0000000..2439bd1
--- /dev/null
+++ b/src/modules/internalisation/helpers/useLanguage.ts
@@ -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
+}
diff --git a/src/modules/internalisation/stores/currentLanguage.ts b/src/modules/internalisation/stores/currentLanguage.ts
new file mode 100644
index 0000000..c2cada7
--- /dev/null
+++ b/src/modules/internalisation/stores/currentLanguage.ts
@@ -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(
+ '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 }
diff --git a/src/modules/internalisation/subjects/currentLanguageSubject.ts b/src/modules/internalisation/subjects/currentLanguageSubject.ts
new file mode 100644
index 0000000..219fde6
--- /dev/null
+++ b/src/modules/internalisation/subjects/currentLanguageSubject.ts
@@ -0,0 +1,7 @@
+import { BehaviorSubject } from 'rxjs'
+import { supportedLanguages } from '../types/supportedLanguages'
+
+/**
+ * Subject with the current language
+ */
+export default new BehaviorSubject('english')
diff --git a/src/modules/internalisation/translations/english.ts b/src/modules/internalisation/translations/english.ts
new file mode 100644
index 0000000..1c784fd
--- /dev/null
+++ b/src/modules/internalisation/translations/english.ts
@@ -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}'`
+ }
+}
diff --git a/src/modules/internalisation/translations/romanian.ts b/src/modules/internalisation/translations/romanian.ts
new file mode 100644
index 0000000..333eca4
--- /dev/null
+++ b/src/modules/internalisation/translations/romanian.ts
@@ -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`
+ }
+}
diff --git a/src/modules/internalisation/types/TranslationInterface.ts b/src/modules/internalisation/types/TranslationInterface.ts
new file mode 100644
index 0000000..4995db1
--- /dev/null
+++ b/src/modules/internalisation/types/TranslationInterface.ts
@@ -0,0 +1,31 @@
+import { supportedLanguages } from './supportedLanguages'
+import { simulationMode } from '../../saving/types/SimulationSave'
+
+export type SentenceFactory = (...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
+ }
+ name: {
+ question: string
+ }
+ }
+ messages: {
+ createdSimulation: NameSentence
+ switchedToSimulation: NameSentence
+ savedSimulation: NameSentence
+ }
+}
diff --git a/src/modules/internalisation/types/supportedLanguages.ts b/src/modules/internalisation/types/supportedLanguages.ts
new file mode 100644
index 0000000..fadf838
--- /dev/null
+++ b/src/modules/internalisation/types/supportedLanguages.ts
@@ -0,0 +1,4 @@
+/**
+ * Type containing the names of all supported languages
+ */
+export type supportedLanguages = 'română' | 'english'
diff --git a/src/modules/modals/styles/mixins/modal-container.scss b/src/modules/modals/styles/mixins/modal-container.scss
index bc54c99..e2b0cb7 100644
--- a/src/modules/modals/styles/mixins/modal-container.scss
+++ b/src/modules/modals/styles/mixins/modal-container.scss
@@ -13,5 +13,5 @@
z-index: $modal-index;
color: white;
background-color: $modal-bg-color;
- font-family: 'Righteous';
+ font-family: 'Righteous', cursive;
}
diff --git a/src/modules/saving/helpers/cloneState.ts b/src/modules/saving/helpers/cloneState.ts
index d4692bb..94e9de8 100644
--- a/src/modules/saving/helpers/cloneState.ts
+++ b/src/modules/saving/helpers/cloneState.ts
@@ -1 +1,6 @@
+/**
+ * Very basic JSON based object cloning
+ *
+ * @param state The object to clone
+ */
export const cloneState = (state: T): T => JSON.parse(JSON.stringify(state))
diff --git a/src/modules/saving/helpers/dumpSimulation.ts b/src/modules/saving/helpers/dumpSimulation.ts
new file mode 100644
index 0000000..4100086
--- /dev/null
+++ b/src/modules/saving/helpers/dumpSimulation.ts
@@ -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
+ }
+}
diff --git a/src/modules/saving/helpers/fromState.ts b/src/modules/saving/helpers/fromState.ts
index 81d1837..02bd080 100644
--- a/src/modules/saving/helpers/fromState.ts
+++ b/src/modules/saving/helpers/fromState.ts
@@ -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(
diff --git a/src/modules/saving/helpers/getState.ts b/src/modules/saving/helpers/getState.ts
index 4c516c1..1cc1892 100644
--- a/src/modules/saving/helpers/getState.ts
+++ b/src/modules/saving/helpers/getState.ts
@@ -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,
diff --git a/src/modules/saving/helpers/initBaseTemplates.ts b/src/modules/saving/helpers/initBaseTemplates.ts
index f961702..2a2badc 100644
--- a/src/modules/saving/helpers/initBaseTemplates.ts
+++ b/src/modules/saving/helpers/initBaseTemplates.ts
@@ -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.`
+ )
}
}
}
diff --git a/src/modules/saving/helpers/initSimulation.ts b/src/modules/saving/helpers/initSimulation.ts
index cd3c783..c423f97 100644
--- a/src/modules/saving/helpers/initSimulation.ts
+++ b/src/modules/saving/helpers/initSimulation.ts
@@ -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'
)
)
diff --git a/src/modules/saving/helpers/save.ts b/src/modules/saving/helpers/save.ts
index 90f5e78..3ba0821 100644
--- a/src/modules/saving/helpers/save.ts
+++ b/src/modules/saving/helpers/save.ts
@@ -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'
diff --git a/src/modules/saving/helpers/switchTo.ts b/src/modules/saving/helpers/switchTo.ts
new file mode 100644
index 0000000..827de37
--- /dev/null
+++ b/src/modules/saving/helpers/switchTo.ts
@@ -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}'`
+ )
+ }
+}
diff --git a/src/modules/saving/stores/currentStore.ts b/src/modules/saving/stores/currentStore.ts
index 6f7920c..745ea65 100644
--- a/src/modules/saving/stores/currentStore.ts
+++ b/src/modules/saving/stores/currentStore.ts
@@ -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('currentSave')
+// This makes sure the store isnt empty
if (!currentStore.get()) {
currentStore.set(defaultSimulationName)
}
diff --git a/src/modules/saving/stores/saveStore.ts b/src/modules/saving/stores/saveStore.ts
index 1656424..dff300e 100644
--- a/src/modules/saving/stores/saveStore.ts
+++ b/src/modules/saving/stores/saveStore.ts
@@ -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('saves')
diff --git a/src/modules/saving/stores/templateStore.ts b/src/modules/saving/stores/templateStore.ts
index 74dcf60..5897051 100644
--- a/src/modules/saving/stores/templateStore.ts
+++ b/src/modules/saving/stores/templateStore.ts
@@ -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>(
'templates'
)
diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
index 49defd5..0acadab 100644
--- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts
+++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
@@ -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()
@@ -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) {
diff --git a/src/modules/toasts/components/ToastContent.scss b/src/modules/toasts/components/ToastContent.scss
index 2acd320..ec3bd7d 100644
--- a/src/modules/toasts/components/ToastContent.scss
+++ b/src/modules/toasts/components/ToastContent.scss
@@ -11,4 +11,5 @@
.toast-content-container > #toast-content-icon {
font-size: 2em;
+ margin: 10px;
}