create / open simulations
This commit is contained in:
parent
057c2268ac
commit
097c44e86e
4
docs/dev/z-indexes.md
Normal file
4
docs/dev/z-indexes.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Z-indexes
|
||||||
|
|
||||||
|
- 5 = sidebar
|
||||||
|
- 10 = create-simulation modal
|
|
@ -6,7 +6,7 @@
|
||||||
"dev": "webpack-dev-server --open --mode development",
|
"dev": "webpack-dev-server --open --mode development",
|
||||||
"build": "cross-env NODE_ENV=production webpack",
|
"build": "cross-env NODE_ENV=production webpack",
|
||||||
"deploy": "ts-node deploy",
|
"deploy": "ts-node deploy",
|
||||||
"show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom"
|
"show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom -s 3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Righteous&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body
|
<body
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { BehaviorSubject } from 'rxjs'
|
|
||||||
|
|
||||||
export type Question = null | {
|
|
||||||
text: string
|
|
||||||
options: {
|
|
||||||
text: string
|
|
||||||
icon: string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QuestionSubject = new BehaviorSubject<Question>(null)
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import '../styles/global-styles/global-styles.scss';
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -10,7 +10,8 @@ import Canvas from './Canvas'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
import Theme from '@material-ui/styles/ThemeProvider'
|
import Theme from '@material-ui/styles/ThemeProvider'
|
||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import QuestionModal from './QuestionModal'
|
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
|
||||||
|
import Input from '../../input/components/Input'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -19,7 +20,8 @@ const App = () => {
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Canvas />
|
<Canvas />
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<QuestionModal />
|
<CreateSimulation />
|
||||||
|
<Input />
|
||||||
</Theme>
|
</Theme>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
|
||||||
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
||||||
import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation'
|
import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation'
|
||||||
import { addGate } from '../../simulation/helpers/addGate'
|
import { addGate } from '../../simulation/helpers/addGate'
|
||||||
|
import { rendererSubject } from '../subjects/rendererSubject'
|
||||||
|
|
||||||
class Canvas extends Component {
|
class Canvas extends Component {
|
||||||
private canvasRef: RefObject<HTMLCanvasElement> = createRef()
|
private canvasRef: RefObject<HTMLCanvasElement> = createRef()
|
||||||
|
@ -15,6 +16,8 @@ class Canvas extends Component {
|
||||||
public constructor(props: {}) {
|
public constructor(props: {}) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
rendererSubject.next(this.renderer)
|
||||||
|
|
||||||
addGate(this.renderer.simulation, 'not')
|
addGate(this.renderer.simulation, 'not')
|
||||||
|
|
||||||
loop.setDraw(() => {
|
loop.setDraw(() => {
|
||||||
|
|
73
src/modules/core/components/OpenSimulation.tsx
Normal file
73
src/modules/core/components/OpenSimulation.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
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 { 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'
|
||||||
|
|
||||||
|
const allSimulations = () => {
|
||||||
|
return saveStore.ls()
|
||||||
|
}
|
||||||
|
const allSimulationSubject = new BehaviorSubject<string[]>([])
|
||||||
|
const updateSimulationList = () => {
|
||||||
|
allSimulationSubject.next(allSimulations())
|
||||||
|
}
|
||||||
|
|
||||||
|
const OpenSimulation = () => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||||
|
const simulations = useObservable(() => allSimulationSubject, [])
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
onClick={event => {
|
||||||
|
updateSimulationList()
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>folder_open</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Open simulation</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
keepMounted
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{simulations.map((simulation, index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={index}
|
||||||
|
onClick={() => {
|
||||||
|
if (rendererSubject.value) {
|
||||||
|
const renderer = rendererSubject.value
|
||||||
|
|
||||||
|
currentStore.set(simulation)
|
||||||
|
renderer.reloadSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{simulation}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OpenSimulation
|
|
@ -1,14 +0,0 @@
|
||||||
import './QuestionModal.scss'
|
|
||||||
import { useObservable } from 'rxjs-hooks'
|
|
||||||
import { QuestionSubject } from '../QuestionModalSubjects'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
const QuestionModal = () => {
|
|
||||||
const question = useObservable(() => QuestionSubject)
|
|
||||||
|
|
||||||
if (!question) return <></>
|
|
||||||
|
|
||||||
return <div className="questionModal">{question.text}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default QuestionModal
|
|
|
@ -1,29 +1,31 @@
|
||||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
|
import React, { useState } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import Drawer from '@material-ui/core/Drawer'
|
import Drawer from '@material-ui/core/Drawer'
|
||||||
import Button from '@material-ui/core/Button'
|
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'
|
||||||
|
|
||||||
const drawerWidth = 240
|
const drawerWidth = 240
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
display: 'flex'
|
display: 'flex',
|
||||||
|
zIndex: 5
|
||||||
},
|
},
|
||||||
drawer: {
|
drawer: {
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
flexShrink: 0
|
flexShrink: 0,
|
||||||
|
zIndex: 5
|
||||||
},
|
},
|
||||||
drawerPaper: {
|
drawerPaper: {
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
background: `#111111`
|
background: `#111111`,
|
||||||
},
|
zIndex: 5
|
||||||
drawerHeader: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '0 8px',
|
|
||||||
...theme.mixins.toolbar,
|
|
||||||
justifyContent: 'flex-start'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -42,9 +44,19 @@ const Sidebar = () => {
|
||||||
paper: classes.drawerPaper
|
paper: classes.drawerPaper
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button variant={'contained'} color="primary">
|
<List component="nav" aria-label="Main mailbox folders">
|
||||||
New Simulation
|
<ListItem
|
||||||
</Button>
|
button
|
||||||
|
className="contained"
|
||||||
|
onClick={handleCreating}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>note_add</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Create simulation</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
<OpenSimulation />
|
||||||
|
</List>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
2
src/modules/core/styles/colors.scss
Normal file
2
src/modules/core/styles/colors.scss
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
$modal-bg-color: rgba(0, 0, 0, 0.7);
|
||||||
|
$primary: #673ab7;
|
2
src/modules/core/styles/global-styles/global-styles.scss
Normal file
2
src/modules/core/styles/global-styles/global-styles.scss
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
@import './mui-overrides.scss';
|
||||||
|
@import './toasts.scss';
|
6
src/modules/core/styles/global-styles/mui-overrides.scss
Normal file
6
src/modules/core/styles/global-styles/mui-overrides.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@import '../colors.scss';
|
||||||
|
|
||||||
|
.MuiListItem-root.contained {
|
||||||
|
// i spent hours trying to find a better solution
|
||||||
|
background-color: $primary !important;
|
||||||
|
}
|
6
src/modules/core/styles/global-styles/toasts.scss
Normal file
6
src/modules/core/styles/global-styles/toasts.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
div.Toastify__toast {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
div.Toastify__taast-body {
|
||||||
|
color: white;
|
||||||
|
}
|
1
src/modules/core/styles/indexes.scss
Normal file
1
src/modules/core/styles/indexes.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
$modal-index: 10;
|
6
src/modules/core/styles/mixins/flex.scss
Normal file
6
src/modules/core/styles/mixins/flex.scss
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@mixin flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
.questionModal {
|
@mixin full-screen {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
10
src/modules/core/styles/mixins/visibility.scss
Normal file
10
src/modules/core/styles/mixins/visibility.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
@mixin hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.6s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin visible {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
6
src/modules/core/subjects/rendererSubject.ts
Normal file
6
src/modules/core/subjects/rendererSubject.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { BehaviorSubject } from 'rxjs'
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
|
||||||
|
export const rendererSubject = new BehaviorSubject<null | SimulationRenderer>(
|
||||||
|
null
|
||||||
|
)
|
30
src/modules/create-simulation/components/CreateOption.scss
Normal file
30
src/modules/create-simulation/components/CreateOption.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#create-options {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-option {
|
||||||
|
@include flex();
|
||||||
|
|
||||||
|
background-color: #444444;
|
||||||
|
height: 17em;
|
||||||
|
width: 17em;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-option > .create-option-icon > * {
|
||||||
|
font-size: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-option > .create-option-name {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ic {
|
||||||
|
font-size: 1.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-option:hover {
|
||||||
|
border: 0.3em solid white;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
@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 './CreateOption.scss';
|
||||||
|
|
||||||
|
#create-content {
|
||||||
|
@include modal-container();
|
||||||
|
}
|
||||||
|
|
||||||
|
.shown#create-content {
|
||||||
|
@include visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
#create-title {
|
||||||
|
@include modal-title();
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './CreateSimulation.scss'
|
||||||
|
import { useObservable } from 'rxjs-hooks'
|
||||||
|
import { CreateSimulationStore } from '../stores/CreateSimulationStore'
|
||||||
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
import Icon from '@material-ui/core/Icon'
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const CreateSimulation = () => {
|
||||||
|
const open = useObservable(() => CreateSimulationStore.data.open, false)
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
CreateSimulationStore.actions.next('quit')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={open ? 'shown' : ''}
|
||||||
|
id="create-content"
|
||||||
|
onClick={closeModal}
|
||||||
|
>
|
||||||
|
<div id="create-title">
|
||||||
|
What kind of simulation do you want to create?
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="create-options">
|
||||||
|
{createSimulationOptions.map((option, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="create-option"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
CreateSimulationStore.data.output.next(option.mode)
|
||||||
|
CreateSimulationStore.actions.next('submit')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="create-option-icon">
|
||||||
|
<Icon>{option.icon}</Icon>
|
||||||
|
</div>
|
||||||
|
<div className="create-option-name" id={option.mode}>
|
||||||
|
{option.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateSimulation
|
12
src/modules/create-simulation/helpers/handleCreating.ts
Normal file
12
src/modules/create-simulation/helpers/handleCreating.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { CreateSimulationStore } from '../stores/CreateSimulationStore'
|
||||||
|
import { initSimulation } from '../../saving/helpers/initSimulation'
|
||||||
|
|
||||||
|
export const handleCreating = async () => {
|
||||||
|
const options = await CreateSimulationStore.create()
|
||||||
|
|
||||||
|
if (!options) return null
|
||||||
|
|
||||||
|
const simulation = initSimulation(options.name, options.mode)
|
||||||
|
|
||||||
|
return simulation
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { BehaviorSubject, Subject } from 'rxjs'
|
||||||
|
import { take } from 'rxjs/operators'
|
||||||
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
import { InputStore } from '../../input/stores/InputStore'
|
||||||
|
|
||||||
|
export type CreateSimulationStoreAction = 'quit' | 'submit'
|
||||||
|
|
||||||
|
export const CreateSimulationStore = {
|
||||||
|
create: async () => {
|
||||||
|
CreateSimulationStore.open()
|
||||||
|
|
||||||
|
const action = await CreateSimulationStore.actions
|
||||||
|
.pipe(take(1))
|
||||||
|
.toPromise()
|
||||||
|
|
||||||
|
CreateSimulationStore.close()
|
||||||
|
|
||||||
|
if (action === 'quit') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = await InputStore.get(
|
||||||
|
'What do you want your simulation to be called?'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: CreateSimulationStore.data.output.value,
|
||||||
|
name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
CreateSimulationStore.data.open.next(true)
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
CreateSimulationStore.data.open.next(false)
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
open: new BehaviorSubject(false),
|
||||||
|
output: new BehaviorSubject<simulationMode>('project')
|
||||||
|
},
|
||||||
|
actions: new Subject<CreateSimulationStoreAction>()
|
||||||
|
}
|
35
src/modules/input/components/Input.scss
Normal file
35
src/modules/input/components/Input.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@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';
|
||||||
|
|
||||||
|
#input-container {
|
||||||
|
@include modal-container();
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible#input-container {
|
||||||
|
@include visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
#input-container > #input-title {
|
||||||
|
@include modal-title();
|
||||||
|
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input-container > #actual-input {
|
||||||
|
background-color: transparent;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.5em;
|
||||||
|
font-family: inherit;
|
||||||
|
|
||||||
|
height: 4rem;
|
||||||
|
width: 80%;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
border-bottom: 5px solid white;
|
||||||
|
}
|
48
src/modules/input/components/Input.tsx
Normal file
48
src/modules/input/components/Input.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import React from 'react'
|
||||||
|
import keycode from 'keycode'
|
||||||
|
import './Input.scss'
|
||||||
|
|
||||||
|
import { useObservable } from 'rxjs-hooks'
|
||||||
|
import { InputStore } from '../stores/InputStore'
|
||||||
|
|
||||||
|
const Input = () => {
|
||||||
|
const open = useObservable(() => InputStore.data.open, false)
|
||||||
|
const question = useObservable(() => InputStore.data.question, '')
|
||||||
|
const output = useObservable(() => InputStore.data.output, '')
|
||||||
|
|
||||||
|
const handleQuit = () => {
|
||||||
|
InputStore.actions.next('quit')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="input-container"
|
||||||
|
onClick={handleQuit}
|
||||||
|
className={open ? 'visible' : ''}
|
||||||
|
>
|
||||||
|
<div id="input-title">{question}</div>
|
||||||
|
<input
|
||||||
|
autoFocus={true}
|
||||||
|
value={output}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
id="actual-input"
|
||||||
|
onChange={e => {
|
||||||
|
const element = e.target as HTMLInputElement
|
||||||
|
|
||||||
|
InputStore.data.output.next(element.value)
|
||||||
|
}}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (keycode('enter') === e.keyCode) {
|
||||||
|
e.preventDefault()
|
||||||
|
return InputStore.actions.next('submit')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input
|
34
src/modules/input/stores/InputStore.ts
Normal file
34
src/modules/input/stores/InputStore.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { Subject, BehaviorSubject } from 'rxjs'
|
||||||
|
import { take } from 'rxjs/operators'
|
||||||
|
|
||||||
|
export type InputAction = 'quit' | 'submit'
|
||||||
|
|
||||||
|
export const InputStore = {
|
||||||
|
async get(text: string) {
|
||||||
|
InputStore.open(text)
|
||||||
|
|
||||||
|
const action = await InputStore.actions.pipe(take(1)).toPromise()
|
||||||
|
|
||||||
|
InputStore.close()
|
||||||
|
|
||||||
|
if (action === 'quit') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return InputStore.data.output.value
|
||||||
|
},
|
||||||
|
open(text: string) {
|
||||||
|
InputStore.data.open.next(true)
|
||||||
|
InputStore.data.output.next('')
|
||||||
|
InputStore.data.question.next(text)
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
InputStore.data.open.next(false)
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
question: new BehaviorSubject(''),
|
||||||
|
output: new BehaviorSubject(''),
|
||||||
|
open: new BehaviorSubject(false)
|
||||||
|
},
|
||||||
|
actions: new Subject<InputAction>()
|
||||||
|
}
|
17
src/modules/modals/styles/mixins/modal-container.scss
Normal file
17
src/modules/modals/styles/mixins/modal-container.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@import '../../core/styles/indexes.scss';
|
||||||
|
@import '../../core/styles/colors.scss';
|
||||||
|
@import '../../core/styles/mixins/flex.scss';
|
||||||
|
@import '../../core/styles/mixins/full-screen.scss';
|
||||||
|
@import '../../core/styles/mixins/visibility.scss';
|
||||||
|
|
||||||
|
@mixin modal-container {
|
||||||
|
@include flex();
|
||||||
|
@include full-screen();
|
||||||
|
@include hidden();
|
||||||
|
|
||||||
|
justify-content: space-evenly;
|
||||||
|
z-index: $modal-index;
|
||||||
|
color: white;
|
||||||
|
background-color: $modal-bg-color;
|
||||||
|
font-family: 'Righteous';
|
||||||
|
}
|
5
src/modules/modals/styles/mixins/modal-title.scss
Normal file
5
src/modules/modals/styles/mixins/modal-title.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@mixin modal-title {
|
||||||
|
font-size: 2.75em;
|
||||||
|
width: 80%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { GateTemplate } from '../simulation/types/GateTemplate'
|
import { GateTemplate } from '../simulation/types/GateTemplate'
|
||||||
|
import { RendererState } from './types/SimulationSave'
|
||||||
|
|
||||||
export const defaultSimulationName = 'default'
|
export const defaultSimulationName = 'default'
|
||||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
|
@ -25,3 +26,19 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const baseSave: RendererState = {
|
||||||
|
camera: {
|
||||||
|
transform: {
|
||||||
|
position: [0, 0],
|
||||||
|
scale: [1, 1],
|
||||||
|
rotation: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
simulation: {
|
||||||
|
gates: [],
|
||||||
|
mode: 'project',
|
||||||
|
wires: [],
|
||||||
|
name: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1
src/modules/saving/helpers/cloneState.ts
Normal file
1
src/modules/saving/helpers/cloneState.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const cloneState = <T>(state: T): T => JSON.parse(JSON.stringify(state))
|
|
@ -48,7 +48,8 @@ export const getSimulationState = (simulation: Simulation): SimulationState => {
|
||||||
return {
|
return {
|
||||||
gates: Array.from(simulation.gates).map(getGateState),
|
gates: Array.from(simulation.gates).map(getGateState),
|
||||||
wires: simulation.wires.map(getWireState),
|
wires: simulation.wires.map(getWireState),
|
||||||
mode: simulation.mode
|
mode: simulation.mode,
|
||||||
|
name: simulation.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
src/modules/saving/helpers/initSimulation.ts
Normal file
24
src/modules/saving/helpers/initSimulation.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { simulationMode } from '../types/SimulationSave'
|
||||||
|
import { baseSave } from '../constants'
|
||||||
|
import { cloneState } from './cloneState'
|
||||||
|
import { saveStore } from '../stores/saveStore'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||||
|
|
||||||
|
export const initSimulation = (name: string, mode: simulationMode) => {
|
||||||
|
const state = cloneState(baseSave)
|
||||||
|
|
||||||
|
state.simulation.name = name
|
||||||
|
state.simulation.mode = mode
|
||||||
|
|
||||||
|
saveStore.set(name, state)
|
||||||
|
|
||||||
|
toast.success(
|
||||||
|
...createToastArguments(
|
||||||
|
`Successfully created simulation ${name}`,
|
||||||
|
'check'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ export const save = (renderer: SimulationRenderer) => {
|
||||||
|
|
||||||
saveStore.set(current, state)
|
saveStore.set(current, state)
|
||||||
|
|
||||||
toast.info(...createToastArguments(`Succesfully saved ${current}`))
|
toast(...createToastArguments(`Succesfully saved ${current}`, 'save'))
|
||||||
} else {
|
} else {
|
||||||
throw new SimulationError(
|
throw new SimulationError(
|
||||||
'Cannot save without knowing the name of the active simulation'
|
'Cannot save without knowing the name of the active simulation'
|
||||||
|
|
|
@ -35,6 +35,7 @@ export interface SimulationState {
|
||||||
wires: WireState[]
|
wires: WireState[]
|
||||||
|
|
||||||
mode: simulationMode
|
mode: simulationMode
|
||||||
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RendererState {
|
export interface RendererState {
|
||||||
|
|
|
@ -8,7 +8,10 @@ export class Simulation {
|
||||||
public gates = new GateStorage()
|
public gates = new GateStorage()
|
||||||
public wires: Wire[] = []
|
public wires: Wire[] = []
|
||||||
|
|
||||||
public constructor(public mode: simulationMode = 'project') {}
|
public constructor(
|
||||||
|
public mode: simulationMode = 'project',
|
||||||
|
public name: string
|
||||||
|
) {}
|
||||||
|
|
||||||
public push(...gates: Gate[]) {
|
public push(...gates: Gate[]) {
|
||||||
for (const gate of gates) {
|
for (const gate of gates) {
|
||||||
|
|
|
@ -5,6 +5,12 @@ const store = new LocalStore<number>('id')
|
||||||
export const idStore = {
|
export const idStore = {
|
||||||
generate() {
|
generate() {
|
||||||
const current = store.get()
|
const current = store.get()
|
||||||
|
|
||||||
|
if (current === undefined) {
|
||||||
|
store.set(1)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
store.set(current + 1)
|
store.set(current + 1)
|
||||||
|
|
||||||
return current + 1
|
return current + 1
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Camera } from './Camera'
|
import { Camera } from './Camera'
|
||||||
import { Simulation } from '../../simulation/classes/Simulation'
|
import { Simulation } from '../../simulation/classes/Simulation'
|
||||||
import { Subject, fromEvent } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
||||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
@ -12,14 +12,12 @@ import { defaultSimulationRendererOptions } from '../constants'
|
||||||
import { getPinPosition } from '../helpers/pinPosition'
|
import { getPinPosition } from '../helpers/pinPosition'
|
||||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||||
import { SelectedPins } from '../types/SelectedPins'
|
import { SelectedPins } from '../types/SelectedPins'
|
||||||
import { getRendererState } from '../../saving/helpers/getState'
|
|
||||||
import { Wire } from '../../simulation/classes/Wire'
|
import { Wire } from '../../simulation/classes/Wire'
|
||||||
import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap'
|
import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap'
|
||||||
import { save } from '../../saving/helpers/save'
|
import { save } from '../../saving/helpers/save'
|
||||||
import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings'
|
import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings'
|
||||||
import { currentStore } from '../../saving/stores/currentStore'
|
import { currentStore } from '../../saving/stores/currentStore'
|
||||||
import { saveStore } from '../../saving/stores/saveStore'
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
|
||||||
import {
|
import {
|
||||||
fromSimulationState,
|
fromSimulationState,
|
||||||
fromCameraState
|
fromCameraState
|
||||||
|
@ -28,7 +26,7 @@ import merge from 'deepmerge'
|
||||||
import { wireConnectedToGate } from '../helpers/wireConnectedToGate'
|
import { wireConnectedToGate } from '../helpers/wireConnectedToGate'
|
||||||
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
||||||
import { RefObject } from 'react'
|
import { RefObject } from 'react'
|
||||||
// import { WheelEvent } from 'react'
|
import { Singleton } from '@eix-js/utils'
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
|
@ -57,7 +55,7 @@ export class SimulationRenderer {
|
||||||
public constructor(
|
public constructor(
|
||||||
public ref: RefObject<HTMLCanvasElement>,
|
public ref: RefObject<HTMLCanvasElement>,
|
||||||
options: Partial<SimulationRendererOptions> = {},
|
options: Partial<SimulationRendererOptions> = {},
|
||||||
public simulation = new Simulation()
|
public simulation = new Simulation('project', 'default')
|
||||||
) {
|
) {
|
||||||
this.options = merge(defaultSimulationRendererOptions, options)
|
this.options = merge(defaultSimulationRendererOptions, options)
|
||||||
|
|
||||||
|
|
|
@ -36,21 +36,18 @@ export class LocalStore<T> {
|
||||||
return this.getAll()[key]
|
return this.getAll()[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(key: string | T = 'index', value?: T) {
|
public set(key: string | T, value?: T) {
|
||||||
|
let finalKey = key as string
|
||||||
|
let finalValue = value as T
|
||||||
|
|
||||||
if (typeof key !== 'string' || value === undefined) {
|
if (typeof key !== 'string' || value === undefined) {
|
||||||
localStorage.setItem(
|
finalKey = 'index'
|
||||||
this.name,
|
finalValue = key as T
|
||||||
JSON.stringify({
|
|
||||||
index: key
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(
|
|
||||||
this.name,
|
|
||||||
JSON.stringify({
|
|
||||||
[key]: value
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentData = this.getAll()
|
||||||
|
|
||||||
|
currentData[finalKey] = finalValue
|
||||||
|
localStorage.setItem(this.name, JSON.stringify(currentData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/modules/toasts/components/ToastContent.scss
Normal file
14
src/modules/toasts/components/ToastContent.scss
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
@import '../../core/styles/mixins/flex.scss';
|
||||||
|
|
||||||
|
.toast-content-container {
|
||||||
|
@include flex();
|
||||||
|
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-direction: row;
|
||||||
|
font-size: 1em;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content-container > #toast-content-icon {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
19
src/modules/toasts/components/ToastContent.tsx
Normal file
19
src/modules/toasts/components/ToastContent.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './ToastContent.scss'
|
||||||
|
import Icon from '@material-ui/core/Icon'
|
||||||
|
|
||||||
|
export interface ToastContentProps {
|
||||||
|
message: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToastContent = (props: ToastContentProps) => {
|
||||||
|
return (
|
||||||
|
<div className="toast-content-container">
|
||||||
|
<Icon id="toast-content-icon">{props.icon}</Icon>
|
||||||
|
<div id="toast-content-message">{props.message}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToastContent
|
|
@ -1,15 +0,0 @@
|
||||||
import { ToastOptions } from 'react-toastify'
|
|
||||||
|
|
||||||
export const createToastArguments = (
|
|
||||||
message: string
|
|
||||||
): [string, ToastOptions] => [
|
|
||||||
message,
|
|
||||||
{
|
|
||||||
position: 'top-left',
|
|
||||||
autoClose: 5000,
|
|
||||||
hideProgressBar: false,
|
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true
|
|
||||||
}
|
|
||||||
]
|
|
18
src/modules/toasts/helpers/createToastArguments.tsx
Normal file
18
src/modules/toasts/helpers/createToastArguments.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { ToastOptions } from 'react-toastify'
|
||||||
|
import React from 'react'
|
||||||
|
import ToastContent from '../components/ToastContent'
|
||||||
|
|
||||||
|
export const createToastArguments = (
|
||||||
|
message: string,
|
||||||
|
icon?: string
|
||||||
|
): [string | JSX.Element, ToastOptions] => [
|
||||||
|
icon ? <ToastContent message={message} icon={icon} /> : message,
|
||||||
|
{
|
||||||
|
position: 'bottom-right',
|
||||||
|
autoClose: 5000,
|
||||||
|
hideProgressBar: true,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in a new issue