the base logicGates page

This commit is contained in:
Matei Adriel 2019-07-28 12:16:49 +03:00
parent c4883b9484
commit 8893967cb8
39 changed files with 519 additions and 293 deletions

136
package-lock.json generated
View file

@ -1956,11 +1956,6 @@
"integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==",
"dev": true
},
"add-px-to-style": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/add-px-to-style/-/add-px-to-style-1.0.0.tgz",
"integrity": "sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo="
},
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
@ -3173,6 +3168,48 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"copy-webpack-plugin": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz",
"integrity": "sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==",
"dev": true,
"requires": {
"cacache": "^11.3.3",
"find-cache-dir": "^2.1.0",
"glob-parent": "^3.1.0",
"globby": "^7.1.1",
"is-glob": "^4.0.1",
"loader-utils": "^1.2.3",
"minimatch": "^3.0.4",
"normalize-path": "^3.0.0",
"p-limit": "^2.2.0",
"schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0",
"webpack-log": "^2.0.0"
},
"dependencies": {
"globby": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
"integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
"dev": true,
"requires": {
"array-union": "^1.0.1",
"dir-glob": "^2.0.0",
"glob": "^7.1.2",
"ignore": "^3.3.5",
"pify": "^3.0.0",
"slash": "^1.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
}
}
},
"core-js": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
@ -3739,6 +3776,32 @@
"randombytes": "^2.0.0"
}
},
"dir-glob": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
"integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
"dev": true,
"requires": {
"path-type": "^3.0.0"
},
"dependencies": {
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dev": true,
"requires": {
"pify": "^3.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
}
}
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -3773,16 +3836,6 @@
"utila": "~0.4"
}
},
"dom-css": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/dom-css/-/dom-css-2.1.0.tgz",
"integrity": "sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI=",
"requires": {
"add-px-to-style": "1.0.0",
"prefix-style": "2.0.1",
"to-camel-case": "1.0.0"
}
},
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
@ -5880,6 +5933,12 @@
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
},
"ignore": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
"import-fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@ -7722,7 +7781,8 @@
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"pify": {
"version": "2.3.0",
@ -8385,11 +8445,6 @@
"integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==",
"dev": true
},
"prefix-style": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz",
"integrity": "sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY="
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@ -8560,14 +8615,6 @@
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
"dev": true
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"requires": {
"performance-now": "^2.1.0"
}
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -8621,16 +8668,6 @@
"scheduler": "^0.13.6"
}
},
"react-custom-scrollbars": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz",
"integrity": "sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts=",
"requires": {
"dom-css": "^2.0.0",
"prop-types": "^15.5.10",
"raf": "^3.1.0"
}
},
"react-dom": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
@ -10243,25 +10280,12 @@
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
"dev": true
},
"to-camel-case": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz",
"integrity": "sha1-GlYFSy+daWKYzmamCJcyK29CPkY=",
"requires": {
"to-space-case": "^1.0.0"
}
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-no-case": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz",
"integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo="
},
"to-object-path": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
@ -10304,14 +10328,6 @@
"repeat-string": "^1.6.1"
}
},
"to-space-case": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz",
"integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=",
"requires": {
"to-no-case": "^1.0.0"
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",

View file

@ -6,7 +6,6 @@
"dev": "webpack-dev-server --open --mode development",
"build": "cross-env NODE_ENV=production webpack",
"build:server": "cross-env NODE_ENV=server webpack",
"deploy": "ts-node deploy",
"show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom -s 0.3",
"start": "node ./dist/server"
},
@ -27,6 +26,7 @@
"babel-loader": "^8.0.6",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-regenerator-runtime": "^6.5.0",
"copy-webpack-plugin": "^5.0.4",
"cross-env": "^5.2.0",
"css-loader": "^3.0.0",
"file-loader": "^4.1.0",
@ -53,7 +53,6 @@
"keycode": "^2.2.0",
"mainloop.js": "^1.0.4",
"react": "^16.8.6",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1",
"react-router-dom": "^5.0.1",

View file

@ -1,6 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head> </head>
<head>
<!-- This doesnt work inside react helmetv-->
<meta name="pinterest" content="nopin" />
</head>
<body
ondragstart="return false;"

View file

@ -6,18 +6,25 @@ import { handleErrors } from './modules/errors/helpers/handleErrors'
import { initKeyBindings } from './modules/keybindings/helpers/initialiseKeyBindings'
import { initBaseTemplates } from './modules/saving/helpers/initBaseTemplates'
import { loadSubject } from './modules/core/subjects/loadedSubject'
import { take } from 'rxjs/operators'
import { take, filter } from 'rxjs/operators'
import { logWelcome } from './modules/core/helpers/logWelcome'
import { initRenderer } from './modules/simulationRenderer/helpers/initRenderer'
import { updateLogicGateList } from './modules/logic-gates/subjects/LogicGateList'
export const start = async () => {
console.clear()
const result = loadSubject.pipe(take(1)).toPromise()
const result = loadSubject
.pipe(
filter(a => a),
take(1)
)
.toPromise()
handleErrors()
initRenderer()
initKeyBindings()
initBaseTemplates()
logWelcome()
updateLogicGateList()
render(<App />, document.getElementById('app'))

View file

@ -1,4 +1,6 @@
@import '../styles/global-styles/global-styles.scss';
@import '../../core/styles/mixins/full-screen.scss';
@import '../styles/colors.scss';
html,
body {
@ -8,7 +10,7 @@ body {
overflow: hidden;
}
canvas {
background-color: #222222;
z-index: -1;
.page {
@include page-width();
background-color: $bg;
}

View file

@ -1,33 +1,38 @@
import '../styles/reset'
import './App.scss'
import './Scrollbars.scss'
import 'react-toastify/dist/ReactToastify.css'
import { ToastContainer } from 'react-toastify'
import { theme as muiTheme } from '../constants'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import React from 'react'
import Canvas from './Canvas'
import React, { useEffect } from 'react'
import CssBaseline from '@material-ui/core/CssBaseline'
import Theme from '@material-ui/styles/ThemeProvider'
import Sidebar from './Sidebar'
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
import Input from '../../input/components/Input'
import LogicGateModal from '../../logic-gates/components/LogicGateModal'
import Head from './Head'
import Root from './Root'
import LogicGatePage from '../../logic-gates/components/LogicGatesPage'
import { loadSubject } from '../subjects/loadedSubject'
const App = () => {
useEffect(() => {
loadSubject.next(true)
})
return (
<>
<Head />
<CssBaseline />
<Theme theme={muiTheme}>
<CssBaseline />
<Canvas />
<Sidebar />
<CreateSimulation />
<Input />
<LogicGateModal />
<Router>
<Sidebar />
<Route path="/" component={Root} exact />
<Route path="/gates" component={LogicGatePage} />
</Router>
</Theme>
<ToastContainer

View file

@ -0,0 +1,26 @@
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 { useTranslation } from '../../internalisation/helpers/useLanguage'
import { Link } from 'react-router-dom'
const BackToSimulation = () => {
const translation = useTranslation()
return (
<Link to="/">
<ListItem button className="contained">
<ListItemIcon>
<Icon>arrow_back_ios</Icon>
</ListItemIcon>
<ListItemText>
{translation.sidebar.backToSimulation}
</ListItemText>
</ListItem>
</Link>
)
}
export default BackToSimulation

View file

@ -1,34 +1,30 @@
import React, { Component, createRef, Ref, RefObject } from 'react'
import React, { Component, createRef, RefObject } from 'react'
import FluidCanvas from './FluidCanvas'
import loop from 'mainloop.js'
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
import { Subscription } from 'rxjs'
import { rendererSubject } from '../subjects/rendererSubject'
import { loadSubject } from '../subjects/loadedSubject'
import { filter } from 'rxjs/operators'
class Canvas extends Component {
private canvasRef: RefObject<HTMLCanvasElement> = createRef()
private renderingContext: CanvasRenderingContext2D | null
private renderer = new SimulationRenderer(this.canvasRef)
public constructor(props: {}) {
super(props)
rendererSubject.next(this.renderer)
loop.setDraw(() => {
if (this.renderingContext) {
renderSimulation(this.renderingContext, this.renderer)
renderSimulation(this.renderingContext, getRendererSafely())
}
})
}
public componentDidMount() {
loadSubject.next(true)
if (this.canvasRef.current) {
this.renderingContext = this.canvasRef.current.getContext('2d')
this.renderer.updateWheelListener()
getRendererSafely().updateWheelListener(this.canvasRef)
}
loop.start()
@ -39,12 +35,14 @@ class Canvas extends Component {
}
public render() {
const renderer = getRendererSafely()
return (
<FluidCanvas
ref={this.canvasRef}
mouseDownOuput={this.renderer.mouseDownOutput}
mouseUpOutput={this.renderer.mouseUpOutput}
mouseMoveOutput={this.renderer.mouseMoveOutput}
mouseDownOuput={renderer.mouseDownOutput}
mouseUpOutput={renderer.mouseUpOutput}
mouseMoveOutput={renderer.mouseMoveOutput}
/>
)
}

View file

@ -33,6 +33,7 @@ const FluidCanvas = forwardRef(
return (
<canvas
className="page"
ref={ref}
width={currentWidth}
height={currentHeight}

View file

@ -4,7 +4,7 @@ import React from 'react'
const title = 'Logic gate simulator'
const description = 'A logic gate simulator made for infoeducatie 2019'
const url = 'https://logic-gate-simulator.herokuapp.com/'
const thumbail = require('../../../assets/thumbail.png')
const thumbail = require('../../../assets/favicon.ico')
const Head = () => {
return (
@ -34,6 +34,11 @@ const Head = () => {
<meta property="og:image" content={`${url}${thumbail}`} />
<meta property="og:url" content={url} />
<meta
name="Description"
content="A logic gate simulator made for infoeducatie 2019"
/>
<link rel="icon" href={require('../../../assets/favicon.ico')} />
</Helmet>
)

View file

@ -4,8 +4,8 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Icon from '@material-ui/core/Icon'
import { useTranslation } from '../../internalisation/helpers/useLanguage'
import { open } from '../../logic-gates/components/LogicGateModal'
import { updateLogicGateList } from '../../logic-gates/subjects/LogicGateList'
import { Link } from 'react-router-dom'
/**
* Component wich contains the sidebar 'Open simulation' button
@ -16,18 +16,19 @@ const LogicGates = () => {
const translation = useTranslation()
return (
<ListItem
button
onClick={() => {
updateLogicGateList()
open.next(true)
}}
>
<ListItemIcon>
<Icon>memory</Icon>
</ListItemIcon>
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
</ListItem>
<Link to="/gates">
<ListItem
button
onClick={() => {
updateLogicGateList()
}}
>
<ListItemIcon>
<Icon>memory</Icon>
</ListItemIcon>
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
</ListItem>
</Link>
)
}

View file

@ -0,0 +1,16 @@
import React from 'react'
import Canvas from './Canvas'
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
import Input from '../../input/components/Input'
const Root = () => {
return (
<>
<Canvas />
<CreateSimulation />
<Input />
</>
)
}
export default Root

View file

@ -0,0 +1,19 @@
/* width */
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
}

View file

@ -7,6 +7,8 @@ import LogicGates from './LogicGates'
import { makeStyles, createStyles } from '@material-ui/core/styles'
import Language from './Language'
import SimulationActions from '../../simulation-actions/components/SimulationActions'
import { Route, Switch } from 'react-router'
import BackToSimulation from './BackToSimulation'
/**
* The width of the sidebar
*/
@ -56,11 +58,21 @@ const useStyles = makeStyles(
const Sidebar = () => {
const classes = useStyles()
const rootSidebarContent = () => {
return (
<>
<CreateSimulationButton />
<OpenSimulation />
<LogicGates />
<SimulationActions />
</>
)
}
return (
<div className={classes.root}>
<Drawer
className={classes.drawer}
variant="persistent"
variant={'persistent'}
anchor="right"
open={true}
classes={{
@ -68,10 +80,10 @@ const Sidebar = () => {
}}
>
<List component="nav" className={classes.list}>
<CreateSimulationButton />
<OpenSimulation />
<LogicGates />
<SimulationActions />
<Switch>
<Route path="/" exact component={rootSidebarContent} />
<Route path="*" component={BackToSimulation} />
</Switch>
</List>
<Language />

View file

@ -1,3 +1,4 @@
$modal-bg-color: rgba(0, 0, 0, 0.7);
$primary: #673ab7;
$grey: #444444;
$bg: #222222;

View file

@ -5,3 +5,8 @@
top: 0;
left: 0;
}
@mixin page-width {
@include full-screen();
width: calc(100% - 240px);
}

View file

@ -1,3 +1,3 @@
import { Subject } from 'rxjs'
import { BehaviorSubject } from 'rxjs'
export const loadSubject = new Subject<true>()
export const loadSubject = new BehaviorSubject(false)

View file

@ -10,7 +10,8 @@ export const EnglishTranslation: Translation = {
logicGates: 'Logic gates',
openSimulation: 'Open simulations',
simulation: 'Simulation',
language: 'Language'
language: 'Language',
backToSimulation: 'Back to simulation'
},
createSimulation: {
mode: {
@ -42,6 +43,7 @@ export const EnglishTranslation: Translation = {
cleaned: name => `Succesfully cleaned simulation '${name}'`,
refreshed: name => `Succesfully refreshed simulation '${name}'`,
undone: name => `Succesfully undone simulation '${name}'`,
deletedSimulation: name => `Succesfully deleted simulation '${name}'`
deletedSimulation: name => `Succesfully deleted simulation '${name}'`,
addedGate: name => `Succesfully added gate '${name}'`
}
}

View file

@ -10,7 +10,8 @@ export const DutchTranslation: Translation = {
logicGates: 'Logische poorten',
openSimulation: 'Open simulatie',
simulation: 'Todo',
language: 'Taal'
language: 'Taal',
backToSimulation: 'Todo'
},
actions: {
'delete selection': 'Todo',

View file

@ -10,7 +10,8 @@ export const RomanianTranslation: Translation = {
openSimulation: 'Deschide o simulație',
logicGates: 'Porți logice',
simulation: 'Simulație',
language: 'Limba'
language: 'Limba',
backToSimulation: 'Înapoi la simulație'
},
createSimulation: {
mode: {
@ -44,6 +45,7 @@ export const RomanianTranslation: Translation = {
refreshed: name => `Simulația '${name}' a fost reîncărcată cu succes`,
undone: name => `Acțiunea a fost întoarsă`,
deletedSimulation: name =>
`Simulația '${name}' a fost ștearsă cu succes`
`Simulația '${name}' a fost ștearsă cu succes`,
addedGate: name => `Componentul '${name}' a fost adăugat cu succes`
}
}

View file

@ -16,6 +16,7 @@ export interface Translation {
logicGates: string
simulation: string
language: string
backToSimulation: string
}
createSimulation: {
mode: {
@ -35,6 +36,7 @@ export interface Translation {
cleaned: NameSentence
undone: NameSentence
deletedSimulation: NameSentence
addedGate: NameSentence
}
actions: Record<possibleAction, string>
}

View file

@ -31,7 +31,7 @@ export const initKeyBindings = (bindings: KeyBindingMap = keyBindings) => {
}
window.addEventListener('keydown', e => {
if (!modalIsOpen()) {
if (!modalIsOpen() && location.pathname === '/') {
const current: {
keys: string[]
callback: Function

View file

@ -0,0 +1,22 @@
import React from 'react'
import Icon from '@material-ui/core/Icon'
import IconButton from '@material-ui/core/IconButton'
import { LogicGateProps } from './LogicGate'
import { addGateFromTemplate } from '../helpers/addGateFromTemplate'
const AddGate = ({ template }: LogicGateProps) => {
return (
<div className="gate-info-icon">
<IconButton
aria-label="add"
onClick={() => {
addGateFromTemplate(template)
}}
>
<Icon>add</Icon>
</IconButton>
</div>
)
}
export default AddGate

View file

@ -0,0 +1,25 @@
import React from 'react'
import Icon from '@material-ui/core/Icon'
import IconButton from '@material-ui/core/IconButton'
import { LogicGateProps } from './LogicGate'
import { randomItem } from '../../internalisation/helpers/randomItem'
const GateInfo = ({ template }: LogicGateProps) => {
const info = template.info
if (info.length === 0) {
return <></>
} else {
return (
<div className="gate-info-icon">
<a href={randomItem(info)} target="blank">
<IconButton aria-label="info">
<Icon>info</Icon>
</IconButton>
</a>
</div>
)
}
}
export default GateInfo

View file

@ -0,0 +1,19 @@
import React from 'react'
import Icon from '@material-ui/core/Icon'
import { LogicGateProps } from './LogicGate'
const GateSettings = ({ template }: LogicGateProps) => {
const tags = template.tags
if (tags.includes('base')) {
return <></>
} else {
return (
<div className="gate-info-icon">
<Icon>settings</Icon>
</div>
)
}
}
export default GateSettings

View file

@ -0,0 +1,31 @@
@import '../../core/styles/mixins/flex.scss';
$gate-margin: 1em;
.gate > section > .gate-preview > * {
width: 100%;
}
.gate > section > .gate-preview > * {
display: block;
height: 10em;
width: 10em;
margin: $gate-margin;
}
.gate > section > .gate-name {
width: 100%;
text-align: center;
}
.gate > section > .gate-icons {
@include flex();
width: calc(100% - 2 * #{$gate-margin});
height: 100%;
margin: $gate-margin;
flex-direction: row;
justify-content: space-evenly;
}

View file

@ -0,0 +1,46 @@
import './LogicGate.scss'
import React from 'react'
import { GateTemplate } from '../../simulation/types/GateTemplate'
import GateInfo from './GateInfo'
import GateSettings from './GateSettings'
import AddGate from './AddGate'
export interface LogicGateProps {
template: GateTemplate
}
const LogicGate = ({ template }: LogicGateProps) => {
const gatePreview =
template.material.type === 'image' ? (
<img src={template.material.fill} alt={template.metadata.name} />
) : (
<div
style={{
backgroundColor: template.material.fill
}}
/>
)
const rawName = template.metadata.name
const name = `${rawName[0].toUpperCase()}${rawName.substr(1)}`
return (
<div className="gate">
<section>
<div className="gate-preview">{gatePreview}</div>
</section>
<section>
<div className="gate-name">{name}</div>
</section>
<section>
<div className="gate-icons">
<GateInfo template={template} />
<GateSettings template={template} />
<AddGate template={template} />
</div>
</section>
</div>
)
}
export default LogicGate

View file

@ -1,70 +0,0 @@
@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;
overflow-y: auto;
height: 100%;
}
.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: 2.5em;
}
#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;
}
#logic-gate-modal-container > .logic-gate-item:first-child {
// height: 4em;
opacity: 0;
}

View file

@ -1,90 +0,0 @@
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 { randomItem } from '../../internalisation/helpers/randomItem'
import { gateIcons } from '../constants'
import { getTemplateSafely } from '../helpers/getTemplateSafely'
import { getRendererSafely } from '../helpers/getRendererSafely'
/**
* 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, [])
const renderer = getRendererSafely()
return (
<div
className={openSnapshot ? 'visible' : ''}
id="logic-gate-modal-container"
onClick={handleClose}
>
<div className="logic-gate-item">---</div>
{gates
.map(getTemplateSafely)
.filter(template => {
return (
renderer.simulation.mode === 'project' ||
template.metadata.name !== renderer.simulation.name
)
})
.map((template, index) => {
const { name } = template.metadata
return (
<div
key={index}
className="logic-gate-item"
onClick={e => {
addGate(renderer, name)
e.stopPropagation()
}}
>
<Icon className="lgi-icon logic-gate-item-type">
{gateIcons[template.tags[0]]}
</Icon>
<Typography className="logic-gate-item-name">
{name}
</Typography>
{template.info.length ? (
<a
target="_blank"
className="logic-gate-item-info"
href={randomItem(template.info)}
onClick={e => {
e.stopPropagation()
// e.preventDefault()
}}
>
<Icon className="lgi-icon">info</Icon>
</a>
) : (
''
)}
</div>
)
})}
</div>
)
}
export default LogicGateModal

View file

@ -0,0 +1,34 @@
@import '../../core/styles/colors.scss';
@import '../../core/styles/mixins/flex.scss';
#gates-page {
color: white;
}
.gate-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-auto-rows: 1fr;
}
.gate-grid::before {
content: '';
width: 0;
padding-bottom: 100%;
grid-row: 1 / 1;
grid-column: 1 / 1;
}
.gate-grid > *:first-child {
grid-row: 1 / 1;
grid-column: 1 / 1;
}
.gate-grid > * {
@include flex();
justify-content: start;
margin: 0.3em;
background-color: $grey;
height: 20em;
}

View file

@ -0,0 +1,38 @@
import './LogicGatesPage.scss'
import React from 'react'
import { useObservable } from 'rxjs-hooks'
import { LogicGateList } from '../subjects/LogicGateList'
import { getTemplateSafely } from '../helpers/getTemplateSafely'
import { getRendererSafely } from '../helpers/getRendererSafely'
import LogicGate from './LogicGate'
/**
* The component containing the info / actions about all logic gates
*/
const LogicGatePage = () => {
const gates = useObservable(() => LogicGateList, [])
const renderer = getRendererSafely()
return (
<main>
<div className="page" id="gates-page">
<div className="gate-grid">
{gates
.map(getTemplateSafely)
.filter(template => {
return (
renderer.simulation.mode === 'project' ||
template.metadata.name !==
renderer.simulation.name
)
})
.map((template, index) => {
return <LogicGate key={index} template={template} />
})}
</div>
</div>
</main>
)
}
export default LogicGatePage

View file

@ -0,0 +1,12 @@
import { GateTemplate } from '../../simulation/types/GateTemplate'
import { addGate } from '../../simulation/helpers/addGate'
import { getRendererSafely } from './getRendererSafely'
/**
* Adds a gate to the current simulation from its template
*
* @param template The template to create
*/
export const addGateFromTemplate = (template: GateTemplate) => {
addGate(getRendererSafely(), template.metadata.name)
}

View file

@ -1,11 +1,6 @@
import { InputStore } from '../../input/stores/InputStore'
import { open as logicGateModalIsOpen } from '../../logic-gates/components/LogicGateModal'
import { CreateSimulationStore } from '../../create-simulation/stores/CreateSimulationStore'
export const modalIsOpen = () => {
return (
InputStore.data.open.value ||
logicGateModalIsOpen.value ||
CreateSimulationStore.data.open.value
)
return InputStore.data.open.value || CreateSimulationStore.data.open.value
}

View file

@ -6,9 +6,13 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
import { DefaultGateTemplate } from '../constants'
import { vector2 } from '../../../common/math/classes/Transform'
import { Screen } from '../../screen/helpers/Screen'
import { toast } from 'react-toastify'
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
export const addGate = (renderer: SimulationRenderer, templateName: string) => {
const template = templateStore.get(templateName)
const translation = CurrentLanguage.getTranslation()
if (!template)
throw new SimulationError(`Cannot find template ${templateName}`)
@ -32,4 +36,11 @@ export const addGate = (renderer: SimulationRenderer, templateName: string) => {
renderer.simulation.push(gate)
renderer.spawnCount++
toast(
...createToastArguments(
translation.messages.addedGate(templateName),
'add_circle_outline'
)
)
}

View file

@ -67,7 +67,6 @@ export class SimulationRenderer {
}
public constructor(
public ref: RefObject<HTMLCanvasElement>,
options: Partial<SimulationRendererOptions> = {},
public simulation = new Simulation('project', 'default')
) {
@ -83,6 +82,8 @@ export class SimulationRenderer {
this.lastMousePosition = worldPosition
console.log('click')
// We need to iterate from the last to the first
// because if we have 2 overlapping gates,
// we want to select the one on top
@ -290,9 +291,9 @@ export class SimulationRenderer {
this.reloadSave()
}
public updateWheelListener() {
if (this.ref.current) {
this.ref.current.addEventListener('wheel', event => {
public updateWheelListener(ref: RefObject<HTMLCanvasElement>) {
if (ref.current) {
ref.current.addEventListener('wheel', event => {
if (!modalIsOpen()) {
event.preventDefault()

View file

@ -0,0 +1,16 @@
import { rendererSubject } from '../../core/subjects/rendererSubject'
import { SimulationError } from '../../errors/classes/SimulationError'
import { SimulationRenderer } from '../classes/SimulationRenderer'
/**
* Helper to create the simulationRenderer
*
* @throws SimulationError if the renderer already exists
*/
export const initRenderer = () => {
if (rendererSubject.value) {
throw new SimulationError('Renderer already inited')
} else {
rendererSubject.next(new SimulationRenderer())
}
}

4
src/public/robots.txt Normal file
View file

@ -0,0 +1,4 @@
User-agent: *
Disallow: /server.js
Disallow: /js
Disallow: /css

View file

@ -5,7 +5,7 @@ const app = express()
app.use(_static(__dirname))
app.get('/', (rex, res) => {
app.get('*', (rex, res) => {
res.sendFile(resolve(__dirname, 'index.html'))
})

View file

@ -5,6 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const webpackMerge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const CopyPlugin = require('copy-webpack-plugin')
const isProduction = process.env.NODE_ENV === 'production'
@ -71,7 +72,15 @@ const serverConfig = {
},
node: {
__dirname: false
}
},
plugins: [
new CopyPlugin([
{
from: resolve(sourceFolder, 'public'),
to: buildFolder
}
])
]
}
const baseConfig = {