Merge branch 'prescientmoon:master' into patch-1
41
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
name: Build and deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: nix build .#erratic-gate-github-pages
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v2
|
||||||
|
with:
|
||||||
|
path: ./result/www
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v2
|
3
.gitignore
vendored
|
@ -2,3 +2,6 @@ node_modules
|
||||||
dist
|
dist
|
||||||
coverege
|
coverege
|
||||||
idea
|
idea
|
||||||
|
.direnv
|
||||||
|
result
|
||||||
|
.envrc
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
semi: false,
|
|
||||||
trailingComma: 'none',
|
|
||||||
singleQuote: true,
|
|
||||||
printWidth: 80,
|
|
||||||
tabWidth: 4
|
|
||||||
}
|
|
7
.prettierrc.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
6
.vscode/settings.json
vendored
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"prettier.eslintIntegration": true,
|
|
||||||
"explorer.autoReveal": false,
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
|
||||||
}
|
|
15
.vscode/template.code-snippets
vendored
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"Gate template": {
|
|
||||||
"prefix": "template",
|
|
||||||
"body": [
|
|
||||||
"import { PartialTemplate } from '../types/PartialTemplate'",
|
|
||||||
"",
|
|
||||||
"/**",
|
|
||||||
" * The template of the ${1:and} gate",
|
|
||||||
" */",
|
|
||||||
"const ${1}Template: PartialTemplate = ${2:[]}",
|
|
||||||
"",
|
|
||||||
"export default ${1}Template"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
30
README.md
|
@ -4,10 +4,8 @@ This is a logic gate simulator made for infoeducatie 2019.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Check out the [demo](https://logic-gate-simulator.herokuapp.com/)
|
Check out the [demo](https://mateiadrielrafael.github.io/erratic-gate/)
|
||||||
Or read the [docs](./docs/main.md)
|
Or read the [docs](./docs/main.md).
|
||||||
|
|
||||||
I've also made a [trello board](https://trello.com/b/LW3XSnGN/logic-gate-simulator) .
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
@ -21,30 +19,10 @@ Many thanks to:
|
||||||
|
|
||||||
- Simple & intuitive UI
|
- Simple & intuitive UI
|
||||||
- Multiple simulations support
|
- Multiple simulations support
|
||||||
- Integrated circuits
|
- (Configurable) integrated circuits
|
||||||
- Multiple bits per pin
|
- Multiple bits per pin
|
||||||
- Multiple language support: Romanian, English, Dutch, Turkish, Chinese & more in the future
|
- Multiple language support: Romanian, English, Dutch, Turkish, Chinese & more in the future
|
||||||
|
|
||||||
## Playing with the source
|
## Playing with the source
|
||||||
|
|
||||||
To run locally clone this repo, and then install all dependencies by rrunning:
|
This repo provides a nix flake for deployment.
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the development server by running:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Or bundle the source with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Assets & code wich i didn't make myself:
|
|
||||||
|
|
||||||
You can read a full list with what i don't own [here](./docs/assets-i-dont-own.md)
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@babel/preset-env',
|
|
||||||
'@babel/preset-react',
|
|
||||||
'@babel/preset-typescript'
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
|
||||||
'@babel/plugin-syntax-dynamic-import',
|
|
||||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
|
||||||
['@babel/plugin-proposal-class-properties', { loose: true }]
|
|
||||||
],
|
|
||||||
env: {
|
|
||||||
test: {
|
|
||||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
53
build.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
import { htmlPlugin } from '@craftamap/esbuild-plugin-html'
|
||||||
|
import { sassPlugin } from 'esbuild-sass-plugin'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
const serve = process.env.ESBUILD_SERVE === '1'
|
||||||
|
const baseurl = process.env.ESBUILD_BASEURL || ''
|
||||||
|
|
||||||
|
console.log(`Building with baseurl ${baseurl}`)
|
||||||
|
|
||||||
|
const ctx = await esbuild.context({
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
minify: true,
|
||||||
|
bundle: true,
|
||||||
|
metafile: true,
|
||||||
|
splitting: true,
|
||||||
|
outdir: 'dist',
|
||||||
|
format: 'esm',
|
||||||
|
target: ['es2020'],
|
||||||
|
assetNames: 'assets/[name]',
|
||||||
|
chunkNames: 'chunks/[name]',
|
||||||
|
loader: {
|
||||||
|
'.svg': 'file'
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.BASEURL': JSON.stringify(baseurl)
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
htmlPlugin({
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
filename: 'index.html',
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
favicon: 'public/favicon.ico',
|
||||||
|
htmlTemplate: 'public/index.html',
|
||||||
|
scriptLoading: 'module'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
sassPlugin({})
|
||||||
|
],
|
||||||
|
publicPath: baseurl
|
||||||
|
})
|
||||||
|
|
||||||
|
if (serve) {
|
||||||
|
const { port, host } = await ctx.serve({ servedir: 'dist' })
|
||||||
|
console.log(`Serving on ${host}:${port}`)
|
||||||
|
} else {
|
||||||
|
await ctx.rebuild()
|
||||||
|
await ctx.dispose()
|
||||||
|
fs.cpSync('./dist/index.html', './dist/404.html') // Needed to please github pages
|
||||||
|
console.log(`Project bundled successfully`)
|
||||||
|
}
|
Before Width: | Height: | Size: 41 KiB |
BIN
docs/assets/buffer-unconnected.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
docs/assets/components.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/assets/empty.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 157 KiB |
BIN
docs/assets/gates.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/assets/notification.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
33
docs/tutorials/basic-operations.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Basic opeartions
|
||||||
|
|
||||||
|
To demonstrate the basic usage of the simulator we will construct one of the simplest logic gates: the `buffer` gate.
|
||||||
|
|
||||||
|
First of all we need to create something called an `integrated circuit`. You can think of integrated circuits like a box containing some components. You only put the components there once and can then reuse that box as many times as you want.
|
||||||
|
|
||||||
|
To create an integrated circuit, click on `Create simulation`, then on `integrated circuit` and type in the name of the circuit - in this case `buffer`.
|
||||||
|
|
||||||
|
You should see something like this:
|
||||||
|
|
||||||
|
![empty IC](../assets/empty.png)
|
||||||
|
|
||||||
|
Now we need to actually add some logic gates. To do that you need to:
|
||||||
|
|
||||||
|
1. click on `Logic gates`, which should bring up a screen which looks similar to this:
|
||||||
|
|
||||||
|
![gates](../assets/gates.png)
|
||||||
|
|
||||||
|
2. Click on the logic gates you want to add to your integrated circuit! In our case we need to add a `button` and a `light bulb`
|
||||||
|
|
||||||
|
> After you add any logic gate you should get a confirmation message which looks something like this: ![notification](../assets/notification.png)
|
||||||
|
|
||||||
|
3. To go back to the simulation view click the `Back to simulation` button. Now you should see something like this:
|
||||||
|
|
||||||
|
![components](../assets/components.png)
|
||||||
|
|
||||||
|
4. As you probably noticed, the components are on top of eachother. You can use your right mouse button to drag them around!
|
||||||
|
|
||||||
|
> Note 1: while your mouse is outside of gate you can start a selection. After you selected some gates you can use your mouse to drag all of them.
|
||||||
|
|
||||||
|
> Note 2: this isn't limited to movement. In the future you'll learn about many actions you can perform which also work on multiple gates :D
|
||||||
|
|
||||||
|
> Note 3: While we're on the topic of selection: You can use `ctrl + a` (or `command + a` on macs) to select everything
|
58
flake.lock
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694529238,
|
||||||
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1695360818,
|
||||||
|
"narHash": "sha256-JlkN3R/SSoMTa+CasbxS1gq+GpGxXQlNZRUh9+LIy/0=",
|
||||||
|
"path": "/nix/store/09yvj6yyxspzfivv91bcxwrjxawpk1g2-source",
|
||||||
|
"rev": "e35dcc04a3853da485a396bdd332217d0ac9054f",
|
||||||
|
"type": "path"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
60
flake.nix
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
description = "Logic gate simulator";
|
||||||
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
outputs = { nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
packages.erratic-gate = pkgs.buildNpmPackage {
|
||||||
|
name = "erratic-gate";
|
||||||
|
|
||||||
|
buildInputs = [ pkgs.nodejs_18 ];
|
||||||
|
|
||||||
|
src = pkgs.lib.cleanSource ./.;
|
||||||
|
npmDepsHash = "sha256-f5mw6IjkhZgsIuzCz9d7DvoAdceY1y+yWXn1BOonsVI=";
|
||||||
|
|
||||||
|
ESBUILD_BASEURL = "";
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir $out
|
||||||
|
cp -r dist $out/www
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
packages.erratic-gate-github-pages = packages.erratic-gate;
|
||||||
|
packages.default = packages.erratic-gate;
|
||||||
|
|
||||||
|
devShells.default =
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs;
|
||||||
|
with nodePackages_latest; [
|
||||||
|
nodejs
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.compute-npm-dep-hash = {
|
||||||
|
type = "app";
|
||||||
|
program = pkgs.lib.getExe (pkgs.writeShellApplication {
|
||||||
|
name = "generate-layout-previes";
|
||||||
|
runtimeInputs = [ pkgs.prefetch-npm-deps ];
|
||||||
|
text = "prefetch-npm-deps ./package-lock.json";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# {{{ Caching and whatnot
|
||||||
|
nixConfig = {
|
||||||
|
# extra-substituters = [
|
||||||
|
# "erratic-gate.cachix.org-1:Ijiu/v//aVpKO4xBqV+2AM2s2uQYOnGCfoj9fYRXxtk" # I think I need this for neovim-nightly?
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# extra-trusted-public-keys = [
|
||||||
|
# "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
||||||
|
# ];
|
||||||
|
};
|
||||||
|
# }}}
|
||||||
|
}
|
||||||
|
|
12837
package-lock.json
generated
51
package.json
|
@ -1,61 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "logic-gate-simulator",
|
"name": "erratic-gate",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "./src/index.ts",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server --open --mode development",
|
"build": "node ./build.js",
|
||||||
"build": "cross-env NODE_ENV=production webpack",
|
"check": "tsc"
|
||||||
"build:server": "cross-env NODE_ENV=server webpack",
|
|
||||||
"show": "gource -f --start-date \"2019-07-01 12:00\" --key --hide dirnames,filenames,bloom -s 0.3",
|
|
||||||
"start": "node ./dist/server"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.5",
|
"@craftamap/esbuild-plugin-html": "^0.6.1",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
|
||||||
"@babel/preset-env": "^7.5.5",
|
|
||||||
"@babel/preset-react": "^7.0.0",
|
|
||||||
"@babel/preset-typescript": "^7.3.3",
|
|
||||||
"@types/deepmerge": "^2.2.0",
|
"@types/deepmerge": "^2.2.0",
|
||||||
"@types/express": "^4.17.0",
|
|
||||||
"@types/gh-pages": "^2.0.0",
|
|
||||||
"@types/mainloop.js": "^1.0.5",
|
"@types/mainloop.js": "^1.0.5",
|
||||||
"@types/react-helmet": "^5.0.8",
|
"@types/node": "^20.8.9",
|
||||||
|
"@types/react-dom": "^18.2.14",
|
||||||
"@types/react-router-dom": "^4.3.4",
|
"@types/react-router-dom": "^4.3.4",
|
||||||
"babel-loader": "^8.0.6",
|
"esbuild": "^0.19.5",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"esbuild-sass-plugin": "^2.16.0",
|
||||||
"babel-regenerator-runtime": "^6.5.0",
|
"typescript": "^5.0.2"
|
||||||
"copy-webpack-plugin": "^5.1.1",
|
|
||||||
"cross-env": "^5.2.0",
|
|
||||||
"css-loader": "^3.0.0",
|
|
||||||
"file-loader": "^4.1.0",
|
|
||||||
"gh-pages": "^2.0.1",
|
|
||||||
"html-webpack-inline-source-plugin": "0.0.10",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"mini-css-extract-plugin": "^0.8.0",
|
|
||||||
"node-sass": "^4.13.1",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
|
||||||
"sass-loader": "^7.1.0",
|
|
||||||
"style-loader": "^0.23.1",
|
|
||||||
"typescript": "^3.8.3",
|
|
||||||
"webpack": "^4.36.1",
|
|
||||||
"webpack-cli": "^3.3.6",
|
|
||||||
"webpack-dev-server": "^3.7.2",
|
|
||||||
"webpack-merge": "^4.2.1",
|
|
||||||
"webpack-node-externals": "^1.7.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
|
||||||
"@eix-js/utils": "0.0.6",
|
"@eix-js/utils": "0.0.6",
|
||||||
"@material-ui/core": "^4.2.1",
|
"@material-ui/core": "^4.2.1",
|
||||||
"deepmerge": "^4.0.0",
|
"deepmerge": "^4.0.0",
|
||||||
"express": "^4.17.1",
|
|
||||||
"keycode": "^2.2.0",
|
"keycode": "^2.2.0",
|
||||||
"mainloop.js": "^1.0.4",
|
"mainloop.js": "^1.0.4",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-helmet": "^5.2.1",
|
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"react-toastify": "^5.3.2",
|
"react-toastify": "^5.3.2",
|
||||||
"rxjs": "^6.5.2",
|
"rxjs": "^6.5.2",
|
||||||
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
66
public/index.html
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Erratic gate</title>
|
||||||
|
|
||||||
|
<meta name="pinterest" content="nopin" />
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css?family=Righteous&display=swap"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta property="og:title" content="Erratic gate" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="A logic gate simulator made for infoeducatie 2019"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:url"
|
||||||
|
content="https://mateiadrielrafael.github.io/erratic-gate/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="Description"
|
||||||
|
content="A logic gate simulator made for infoeducatie 2019"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body
|
||||||
|
ondragstart="return false;"
|
||||||
|
ondrop="return false;"
|
||||||
|
oncontextmenu="return false"
|
||||||
|
>
|
||||||
|
<!--The react app is rendered here-->
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<!--The splash screen structure-->
|
||||||
|
<div class="Splash">
|
||||||
|
<!--Loading animation-->
|
||||||
|
<div class="loading">
|
||||||
|
<div class="lds-ripple">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--In case someone tries to laod this with js disabled-->
|
||||||
|
<noscript> JavaScript must be enabled to run this app. </noscript>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,30 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- This doesnt work inside react helmetv-->
|
|
||||||
<meta name="pinterest" content="nopin" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body
|
|
||||||
ondragstart="return false;"
|
|
||||||
ondrop="return false;"
|
|
||||||
oncontextmenu="return false"
|
|
||||||
>
|
|
||||||
<!--The react app is rendered here-->
|
|
||||||
<div id="app"></div>
|
|
||||||
|
|
||||||
<!--The splash screen structure-->
|
|
||||||
<div class="Splash">
|
|
||||||
<!--Loading animation-->
|
|
||||||
<div class="loading">
|
|
||||||
<div class="lds-ripple">
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--In case someone tries to laod this with js disabled-->
|
|
||||||
<noscript> JavaScript must be enabled to run this app. </noscript>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,4 +1,3 @@
|
||||||
import React from 'react'
|
|
||||||
import App from './modules/core/components/App'
|
import App from './modules/core/components/App'
|
||||||
|
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
|
@ -10,6 +9,7 @@ import { take, filter } from 'rxjs/operators'
|
||||||
import { logWelcome } from './modules/core/helpers/logWelcome'
|
import { logWelcome } from './modules/core/helpers/logWelcome'
|
||||||
import { initRenderer } from './modules/simulationRenderer/helpers/initRenderer'
|
import { initRenderer } from './modules/simulationRenderer/helpers/initRenderer'
|
||||||
import { updateLogicGateList } from './modules/logic-gates/subjects/LogicGateList'
|
import { updateLogicGateList } from './modules/logic-gates/subjects/LogicGateList'
|
||||||
|
import { initAutoSave } from './modules/simulation-actions/helpers/initAutoSave'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function wich does the heavy lifting for starting the app
|
* The function wich does the heavy lifting for starting the app
|
||||||
|
@ -18,7 +18,7 @@ export const start = async () => {
|
||||||
// This will resolve at the first render
|
// This will resolve at the first render
|
||||||
const result = loadSubject
|
const result = loadSubject
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(a => a),
|
filter((a) => a),
|
||||||
take(1)
|
take(1)
|
||||||
)
|
)
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
@ -41,6 +41,9 @@ export const start = async () => {
|
||||||
// Update the logic gates in local storage
|
// Update the logic gates in local storage
|
||||||
updateLogicGateList()
|
updateLogicGateList()
|
||||||
|
|
||||||
|
// start the autosaving stuff
|
||||||
|
initAutoSave()
|
||||||
|
|
||||||
// Render app component
|
// Render app component
|
||||||
render(<App />, document.getElementById('app'))
|
render(<App />, document.getElementById('app'))
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,52 @@
|
||||||
import '../styles/reset'
|
import '../styles/reset.scss'
|
||||||
import './App.scss'
|
import './App.scss'
|
||||||
import './Scrollbars.scss'
|
import './Scrollbars.scss'
|
||||||
import 'react-toastify/dist/ReactToastify.css'
|
import 'react-toastify/dist/ReactToastify.css'
|
||||||
|
|
||||||
import { ToastContainer } from 'react-toastify'
|
import { ToastContainer } from 'react-toastify'
|
||||||
import { theme as muiTheme } from '../constants'
|
import { theme as muiTheme } from '../constants'
|
||||||
import { Route } from 'react-router-dom'
|
import { BrowserRouter, Route } from 'react-router-dom'
|
||||||
|
|
||||||
import React, { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
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 Head from './Head'
|
|
||||||
import Root from './Root'
|
import Root from './Root'
|
||||||
import LogicGatePage from '../../logic-gates/components/LogicGatesPage'
|
import LogicGatePage from '../../logic-gates/components/LogicGatesPage'
|
||||||
import { loadSubject } from '../subjects/loadedSubject'
|
import { loadSubject } from '../subjects/loadedSubject'
|
||||||
import { CustomRouter } from './CustomRouter'
|
|
||||||
import LogicGateInfoPage from '../../logic-gate-info/components/LogicGateInfoPage'
|
import LogicGateInfoPage from '../../logic-gate-info/components/LogicGateInfoPage'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSubject.next(true)
|
loadSubject.next(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head />
|
<CssBaseline />
|
||||||
<CssBaseline />
|
|
||||||
|
|
||||||
<Theme theme={muiTheme}>
|
<Theme theme={muiTheme}>
|
||||||
<CustomRouter>
|
<BrowserRouter basename={process.env.BASEURL}>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
<Route path="/" component={Root} exact />
|
<Route path="/" component={Root} exact />
|
||||||
<Route path="/gates" component={LogicGatePage} />
|
<Route path="/gates" component={LogicGatePage} />
|
||||||
<Route path="/info/:name" component={LogicGateInfoPage} />
|
<Route path="/info/:name" component={LogicGateInfoPage} />
|
||||||
</CustomRouter>
|
</BrowserRouter>
|
||||||
</Theme>
|
</Theme>
|
||||||
|
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-left"
|
position="top-left"
|
||||||
autoClose={5000}
|
autoClose={5000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick
|
closeOnClick
|
||||||
rtl={false}
|
rtl={false}
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
|
||||||
import { history } from '../constants'
|
|
||||||
|
|
||||||
export class CustomRouter extends BrowserRouter {
|
|
||||||
public history = history
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
import Helmet from 'react-helmet'
|
|
||||||
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 Head = () => {
|
|
||||||
return (
|
|
||||||
<Helmet>
|
|
||||||
<title>{title}</title>
|
|
||||||
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Head
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { createMuiTheme } from '@material-ui/core/styles'
|
import { createTheme } from '@material-ui/core/styles'
|
||||||
import { red, deepPurple } from '@material-ui/core/colors'
|
import { red, deepPurple } from '@material-ui/core/colors'
|
||||||
import { simulationMode } from '../saving/types/SimulationSave'
|
import { simulationMode } from '../saving/types/SimulationSave'
|
||||||
import { createBrowserHistory } from 'history'
|
|
||||||
|
|
||||||
export const theme = createMuiTheme({
|
export const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
primary: deepPurple,
|
primary: deepPurple,
|
||||||
|
@ -32,8 +31,3 @@ export const icons: IconInterface = {
|
||||||
* The width of the sidebar
|
* The width of the sidebar
|
||||||
*/
|
*/
|
||||||
export const sidebarWidth = 240
|
export const sidebarWidth = 240
|
||||||
|
|
||||||
/**
|
|
||||||
* The history to be used by the router
|
|
||||||
*/
|
|
||||||
export const history = createBrowserHistory()
|
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* In case the guys who look at my projet open the console -_-
|
* In case the people who look at my projet open the console -_-
|
||||||
*/
|
*/
|
||||||
export const logWelcome = () => {
|
export const logWelcome = () => {
|
||||||
const commonStyles = 'padding: 3px'
|
const commonStyles = 'padding: 3px'
|
||||||
const titleStyles = `font-size: 3em;`
|
const titleStyles = `font-size: 3em;`
|
||||||
|
|
||||||
// console.log('%c Hello!',` `${titleStyles})
|
console.log(
|
||||||
console.log(
|
`%c Hello
|
||||||
`%c Hello
|
|
||||||
%c I don't know if you see this,
|
%c I don't know if you see this,
|
||||||
but if you do than you are probably wondering...
|
but if you do than you are probably wondering...
|
||||||
Why did I include this? The answer is - I don't know.
|
Why did I include this? The answer is - I don't know.
|
||||||
At first, it seemed like a good idea to include a welcome message in the console
|
At first, it seemed like a good idea to include a welcome message in the console
|
||||||
(in case someone randomly openes it), but now i don't even know what i'm doing :)
|
(in case someone randomly openes it), but now i don't even know what i'm doing :)
|
||||||
Anyways, I hope you are having a good time in my simulator!!!`
|
Anyways, I hope you are having a good time in my simulator!!!`
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(s => s.trim())
|
.map((s) => s.trim())
|
||||||
.join(' '),
|
.join(' '),
|
||||||
`${titleStyles}`,
|
`${titleStyles}`,
|
||||||
`${commonStyles}`
|
`${commonStyles}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,8 +169,6 @@ audio,
|
||||||
canvas,
|
canvas,
|
||||||
video {
|
video {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
*display: inline;
|
|
||||||
*zoom: 1;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +267,7 @@ legend {
|
||||||
border: 0; /* 1 */
|
border: 0; /* 1 */
|
||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: normal; /* 2 */
|
white-space: normal; /* 2 */
|
||||||
*margin-left: -7px; /* 3 */
|
margin-left: -7px; /* 3 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -286,7 +284,6 @@ textarea {
|
||||||
font-size: 100%; /* 1 */
|
font-size: 100%; /* 1 */
|
||||||
margin: 0; /* 2 */
|
margin: 0; /* 2 */
|
||||||
vertical-align: baseline; /* 3 */
|
vertical-align: baseline; /* 3 */
|
||||||
*vertical-align: middle; /* 3 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -327,7 +324,7 @@ input[type="reset"],
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
-webkit-appearance: button; /* 2 */
|
-webkit-appearance: button; /* 2 */
|
||||||
cursor: pointer; /* 3 */
|
cursor: pointer; /* 3 */
|
||||||
*overflow: visible; /* 4 */
|
overflow: visible; /* 4 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -350,8 +347,8 @@ input[type='checkbox'],
|
||||||
input[type='radio'] {
|
input[type='radio'] {
|
||||||
box-sizing: border-box; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
padding: 0; /* 2 */
|
padding: 0; /* 2 */
|
||||||
*height: 13px; /* 3 */
|
height: 13px; /* 3 */
|
||||||
*width: 13px; /* 3 */
|
width: 13px; /* 3 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { SimulationState } from '../../saving/types/SimulationSave'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import {
|
import {
|
||||||
GateTemplate,
|
GateTemplate,
|
||||||
Property,
|
|
||||||
PropGroup,
|
PropGroup,
|
||||||
isGroup
|
isGroup
|
||||||
} from '../../simulation/types/GateTemplate'
|
} from '../../simulation/types/GateTemplate'
|
||||||
|
@ -24,9 +23,10 @@ import { getTemplateSafely } from '../../logic-gates/helpers/getTemplateSafely'
|
||||||
* Compiles a simulation into a logicGate
|
* Compiles a simulation into a logicGate
|
||||||
*
|
*
|
||||||
* @param simulaton The simulation to compile
|
* @param simulaton The simulation to compile
|
||||||
|
* @param log Allow disabling logging
|
||||||
*/
|
*/
|
||||||
export const compileIc = (state: SimulationState) => {
|
export const compileIc = (state: SimulationState, log = false) => {
|
||||||
const { mode, name, gates } = state
|
const { mode, name } = state
|
||||||
|
|
||||||
if (mode === 'project') {
|
if (mode === 'project') {
|
||||||
throw new SimulationError('Cannot compile project')
|
throw new SimulationError('Cannot compile project')
|
||||||
|
@ -59,7 +59,7 @@ export const compileIc = (state: SimulationState) => {
|
||||||
category: categories.ic,
|
category: categories.ic,
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/ic')
|
fill: require('../../../assets/ic.svg')
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
enabled: !!props.length,
|
enabled: !!props.length,
|
||||||
|
@ -82,10 +82,13 @@ export const compileIc = (state: SimulationState) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
templateStore.set(name, result)
|
templateStore.set(name, result)
|
||||||
toast(
|
|
||||||
...createToastArguments(
|
if (log) {
|
||||||
translation.messages.compiledIc(name),
|
toast(
|
||||||
'markunread_mailbox'
|
...createToastArguments(
|
||||||
|
translation.messages.compiledIc(name),
|
||||||
|
'markunread_mailbox'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import {
|
import { CurrentLanguage } from '../stores/currentLanguage'
|
||||||
CurrentLanguage,
|
|
||||||
CurrentLanguageLocalStore
|
|
||||||
} from '../stores/currentLanguage'
|
|
||||||
import { allSupportedLanguages } from '../constants'
|
import { allSupportedLanguages } from '../constants'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,12 +7,12 @@ export const DeutschTranslation: Translation = {
|
||||||
language: 'deutsch',
|
language: 'deutsch',
|
||||||
sidebar: {
|
sidebar: {
|
||||||
createSimulation: 'Simulation erstellen',
|
createSimulation: 'Simulation erstellen',
|
||||||
logicGates: ' Logische Tore',
|
logicGates: 'Logikgatter',
|
||||||
openSimulation: ' Simulationen öffnen ',
|
openSimulation: 'Simulationen öffnen',
|
||||||
simulation: 'Simulation',
|
simulation: 'Simulation',
|
||||||
language: ' Sprache ',
|
language: 'Sprache',
|
||||||
backToSimulation: ' Zurück zur Simulation',
|
backToSimulation: 'Zurück zur Simulation',
|
||||||
backToGates: ' Zurück zur Logische Tore'
|
backToGates: 'Zurück zu den Logikgattern'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
mode: {
|
mode: {
|
||||||
|
@ -23,33 +23,33 @@ export const DeutschTranslation: Translation = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
question: 'Was wollen Sie Ihre Simulation genannt werden?'
|
question: 'Wie soll Ihre Simulation genannt werden?'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
save: 'Speichern',
|
save: 'Speichern',
|
||||||
clean: 'Reinigen',
|
clean: 'Bereinigen',
|
||||||
refresh: 'Aktualisierung',
|
refresh: 'Aktualisieren',
|
||||||
undo: 'Rückgängig machen',
|
undo: 'Rückgängig machen',
|
||||||
paste: 'Einfügen',
|
paste: 'Einfügen',
|
||||||
copy: 'Kopieren',
|
copy: 'Kopieren',
|
||||||
duplicate: 'Duplizieren',
|
duplicate: 'Duplizieren',
|
||||||
cut: 'Schnitt',
|
cut: 'Ausschneiden',
|
||||||
'select all': 'Wählen Sie Alle',
|
'select all': 'Alles auswählen',
|
||||||
'delete selection': 'Auswahl löschen',
|
'delete selection': 'Auswahl löschen',
|
||||||
'delete simulation': 'Simulation löschen'
|
'delete simulation': 'Simulation löschen'
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name => `Erfolgreich erstellte Simulation '${name}'`,
|
createdSimulation: name => `Simulation '${name}' erfolgreich erstellt`,
|
||||||
switchedToSimulation: name =>
|
switchedToSimulation: name =>
|
||||||
`Erfolgreich auf Simulation umgestellt '${name}'`,
|
`Erfolgreich zu Simulation '${name}' gewechselt`,
|
||||||
savedSimulation: name =>
|
savedSimulation: name =>
|
||||||
`Erfolgreich gespeicherte Simulation '${name}'`,
|
`Simulation '${name}' erfolgreich gespeichert`,
|
||||||
compiledIc: name => `Erfolgreich kompilierte Schaltung '${name}'`,
|
compiledIc: name => `Integrierter Schaltkreis '${name}' erfolgreich kompiliert`,
|
||||||
cleaned: name => `Erfolgreich bereinigte Simulation '${name}'`,
|
cleaned: name => `Simulation '${name}' erfolgreich bereinigt`,
|
||||||
refreshed: name => `Erfolgreich aktualisierte Simulation '${name}'`,
|
refreshed: name => `Simulation '${name}' erfolgreich aktualisiert`,
|
||||||
undone: name => `Simulation erfolgreich rückgängig gemacht ' ${name}'`,
|
undone: name => `Simulation '${name}' erfolgreich Rückgängig gemacht`,
|
||||||
deletedSimulation: name => `Simulation erfolgreich gelöscht ' ${name}'`,
|
deletedSimulation: name => `Simulation '${name}' erfolgreich gelöscht`,
|
||||||
addedGate: name => `Tor erfolgreich hinzugefügt`
|
addedGate: name => `Logikgatter erfolgreich hinzugefügt`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,16 @@ export const EnglishTranslation: Translation = {
|
||||||
'delete simulation': 'Delete simulation'
|
'delete simulation': 'Delete simulation'
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
autoSave: 'The sim saves your work automatically :D',
|
||||||
switchedToSimulation: name =>
|
createdSimulation: (name) => `Succesfully created simulation '${name}'`,
|
||||||
|
switchedToSimulation: (name) =>
|
||||||
`Succesfully switched to simulation '${name}'`,
|
`Succesfully switched to simulation '${name}'`,
|
||||||
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
savedSimulation: (name) => `Succesfully saved simulation '${name}'`,
|
||||||
compiledIc: name => `Succesfully compiled circuit '${name}'`,
|
compiledIc: (name) => `Succesfully compiled circuit '${name}'`,
|
||||||
cleaned: name => `Succesfully cleaned simulation '${name}'`,
|
cleaned: (name) => `Succesfully cleaned simulation '${name}'`,
|
||||||
refreshed: name => `Succesfully refreshed simulation '${name}'`,
|
refreshed: (name) => `Succesfully refreshed simulation '${name}'`,
|
||||||
undone: name => `Succesfully undone 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}'`
|
addedGate: (name) => `Succesfully added gate '${name}'`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ export interface Translation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messages: {
|
messages: {
|
||||||
|
autoSave?: string
|
||||||
createdSimulation: NameSentence
|
createdSimulation: NameSentence
|
||||||
switchedToSimulation: NameSentence
|
switchedToSimulation: NameSentence
|
||||||
savedSimulation: NameSentence
|
savedSimulation: NameSentence
|
||||||
|
|
|
@ -117,7 +117,7 @@ const GateRawProperty = ({
|
||||||
raw.type === 'text' ||
|
raw.type === 'text' ||
|
||||||
raw.type === 'string'
|
raw.type === 'string'
|
||||||
) {
|
) {
|
||||||
return (input = (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
label={displayableName}
|
label={displayableName}
|
||||||
|
@ -126,9 +126,9 @@ const GateRawProperty = ({
|
||||||
multiline={raw.type === 'string'}
|
multiline={raw.type === 'string'}
|
||||||
rowsMax={7}
|
rowsMax={7}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
} else if (raw.type === 'boolean') {
|
} else if (raw.type === 'boolean') {
|
||||||
return (input = (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="checkbox-label">{displayableName}</span>
|
<span className="checkbox-label">{displayableName}</span>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
|
@ -139,7 +139,7 @@ const GateRawProperty = ({
|
||||||
checked={!!outputSnapshot}
|
checked={!!outputSnapshot}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
</>
|
</>
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|
|
@ -10,30 +10,29 @@ import LogicGate from './LogicGate'
|
||||||
* The component containing the info / actions about all logic gates
|
* The component containing the info / actions about all logic gates
|
||||||
*/
|
*/
|
||||||
const LogicGatePage = () => {
|
const LogicGatePage = () => {
|
||||||
const gates = useObservable(() => LogicGateList, [])
|
const gates = useObservable(() => LogicGateList, [])
|
||||||
const renderer = getRendererSafely()
|
const renderer = getRendererSafely()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<div className="page" id="gates-page">
|
<div className="page" id="gates-page">
|
||||||
<div className="gate-grid">
|
<div className="gate-grid">
|
||||||
{gates
|
{gates
|
||||||
.map(getTemplateSafely)
|
.map(getTemplateSafely)
|
||||||
.sort((a, b) => a.category - b.category)
|
.sort((a, b) => a.category - b.category)
|
||||||
.filter(template => {
|
.filter((template) => {
|
||||||
return (
|
return (
|
||||||
renderer.simulation.mode === 'project' ||
|
renderer.simulation.mode === 'project' ||
|
||||||
template.metadata.name !==
|
template.metadata.name !== renderer.simulation.name
|
||||||
renderer.simulation.name
|
)
|
||||||
)
|
})
|
||||||
})
|
.map((template, index) => {
|
||||||
.map((template, index) => {
|
return <LogicGate key={index} template={template} />
|
||||||
return <LogicGate key={index} template={template} />
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</main>
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LogicGatePage
|
export default LogicGatePage
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
@import '../../core/styles/indexes.scss';
|
@import '../../../core/styles/indexes.scss';
|
||||||
@import '../../core/styles/colors.scss';
|
@import '../../../core/styles/colors.scss';
|
||||||
@import '../../core/styles/mixins/flex.scss';
|
@import '../../../core/styles/mixins/flex.scss';
|
||||||
@import '../../core/styles/mixins/full-screen.scss';
|
@import '../../../core/styles/mixins/full-screen.scss';
|
||||||
@import '../../core/styles/mixins/visibility.scss';
|
@import '../../../core/styles/mixins/visibility.scss';
|
||||||
|
|
||||||
@mixin modal-container {
|
@mixin modal-container {
|
||||||
@include flex();
|
@include flex();
|
||||||
|
|
|
@ -7,6 +7,20 @@ import { toast } from 'react-toastify'
|
||||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||||
import { compileIc } from '../../integrated-circuits/helpers/compileIc'
|
import { compileIc } from '../../integrated-circuits/helpers/compileIc'
|
||||||
|
import { EnglishTranslation } from '../../internalisation/translations/english'
|
||||||
|
|
||||||
|
export const notifyAboutAutosave = () => {
|
||||||
|
const translation = CurrentLanguage.getTranslation()
|
||||||
|
|
||||||
|
toast(
|
||||||
|
...createToastArguments(
|
||||||
|
translation.messages.autoSave ||
|
||||||
|
EnglishTranslation.messages.autoSave ||
|
||||||
|
'this sentence was not translated yet',
|
||||||
|
'save'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the state from a renderer in localStorage,
|
* Saves the state from a renderer in localStorage,
|
||||||
|
@ -22,20 +36,11 @@ export const save = (renderer: SimulationRenderer) => {
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
const state = getRendererState(renderer)
|
const state = getRendererState(renderer)
|
||||||
const translation = CurrentLanguage.getTranslation()
|
|
||||||
|
|
||||||
saveStore.set(current, state)
|
saveStore.set(current, state)
|
||||||
|
|
||||||
if (state.simulation.mode === 'ic') {
|
if (state.simulation.mode === 'ic') {
|
||||||
compileIc(state.simulation)
|
compileIc(state.simulation)
|
||||||
}
|
}
|
||||||
|
|
||||||
toast(
|
|
||||||
...createToastArguments(
|
|
||||||
translation.messages.savedSimulation(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'
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { toast } from 'react-toastify'
|
||||||
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||||
import { dumpSimulation } from './dumpSimulation'
|
import { dumpSimulation } from './dumpSimulation'
|
||||||
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||||
|
import { compileIc } from '../../integrated-circuits/helpers/compileIc'
|
||||||
|
import { getRendererState } from './getState'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to switch to a simulation
|
* Used to switch to a simulation
|
||||||
|
@ -22,6 +24,12 @@ export const switchTo = (simulationName: string = 'default') => {
|
||||||
if (rendererSubject.value) {
|
if (rendererSubject.value) {
|
||||||
const renderer = rendererSubject.value
|
const renderer = rendererSubject.value
|
||||||
const translation = CurrentLanguage.getTranslation()
|
const translation = CurrentLanguage.getTranslation()
|
||||||
|
const state = getRendererState(renderer)
|
||||||
|
|
||||||
|
// compile the ic just in case
|
||||||
|
if (state.simulation.mode === 'ic') {
|
||||||
|
compileIc(state.simulation, true)
|
||||||
|
}
|
||||||
|
|
||||||
dumpSimulation(renderer)
|
dumpSimulation(renderer)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ const _4bitDecoderTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/4decoder')
|
fill: require('../../../assets/4decoder.svg')
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
scale: _4BitScale
|
scale: _4BitScale
|
||||||
|
|
|
@ -24,7 +24,7 @@ const _4bitEncoderTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/4encoder')
|
fill: require('../../../assets/4encoder.svg')
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
scale: _4BitScale
|
scale: _4BitScale
|
||||||
|
|
|
@ -9,7 +9,7 @@ const andTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/and')
|
fill: require('../../../assets/and.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -24,7 +24,7 @@ const bitMergerTemplate: PartialTemplate = {
|
||||||
category: categories.compressing,
|
category: categories.compressing,
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/merger')
|
fill: require('../../../assets/merger.svg')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ const bitSplitterTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/splitter')
|
fill: require('../../../assets/splitter.svg')
|
||||||
},
|
},
|
||||||
category: categories.compressing,
|
category: categories.compressing,
|
||||||
pins: {
|
pins: {
|
||||||
|
|
|
@ -11,7 +11,7 @@ const fullAdderTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/full-adder')
|
fill: require('../../../assets/full-adder.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: adderActivation(true)
|
activation: adderActivation(true)
|
||||||
|
|
|
@ -11,7 +11,7 @@ const halfAdderTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/half-adder')
|
fill: require('../../../assets/half-adder.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: adderActivation(false)
|
activation: adderActivation(false)
|
||||||
|
|
|
@ -9,7 +9,7 @@ const nandTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/nand')
|
fill: require('../../../assets/nand.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -9,7 +9,7 @@ const norTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/nor')
|
fill: require('../../../assets/nor.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -9,7 +9,7 @@ const notTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/not')
|
fill: require('../../../assets/not.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -9,7 +9,7 @@ const orTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/or')
|
fill: require('../../../assets/or.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -11,7 +11,7 @@ const parallelDelayerTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/parallel')
|
fill: require('../../../assets/parallel.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -11,7 +11,7 @@ const sequentialDelayerTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/sequential')
|
fill: require('../../../assets/sequential.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -9,7 +9,7 @@ const xnorTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/xnor')
|
fill: require('../../../assets/xnor.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -9,7 +9,7 @@ const xorTemplate: PartialTemplate = {
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
type: 'image',
|
type: 'image',
|
||||||
fill: require('../../../assets/xor')
|
fill: require('../../../assets/xor.svg')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `
|
activation: `
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SidebarAction } from './types/SidebarAction'
|
import { SidebarAction } from './types/SidebarAction'
|
||||||
import { possibleAction } from './types/possibleAction'
|
import { possibleAction } from './types/possibleAction'
|
||||||
import { save } from '../saving/helpers/save'
|
import { save, notifyAboutAutosave } from '../saving/helpers/save'
|
||||||
import { refresh } from './helpers/refresh'
|
import { refresh } from './helpers/refresh'
|
||||||
import { undo } from './helpers/undo'
|
import { undo } from './helpers/undo'
|
||||||
import { createActionConfig } from './helpers/createActionConfig'
|
import { createActionConfig } from './helpers/createActionConfig'
|
||||||
|
@ -30,7 +30,7 @@ export const actionIcons: Record<possibleAction, string> = {
|
||||||
* Array with all the actions for the SimulationAction component to render
|
* Array with all the actions for the SimulationAction component to render
|
||||||
*/
|
*/
|
||||||
export const SidebarActions: Record<possibleAction, SidebarAction> = {
|
export const SidebarActions: Record<possibleAction, SidebarAction> = {
|
||||||
...createActionConfig('save', save, ['control', 's']),
|
...createActionConfig('save', notifyAboutAutosave, ['control', 's']),
|
||||||
...createActionConfig(
|
...createActionConfig(
|
||||||
'refresh',
|
'refresh',
|
||||||
{
|
{
|
||||||
|
|
12
src/modules/simulation-actions/helpers/initAutoSave.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { interval } from 'rxjs'
|
||||||
|
import { save } from '../../saving/helpers/save'
|
||||||
|
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
|
||||||
|
|
||||||
|
const everySecond = interval(1000)
|
||||||
|
|
||||||
|
export const initAutoSave = () => {
|
||||||
|
everySecond.subscribe(() => {
|
||||||
|
const renderer = getRendererSafely()
|
||||||
|
save(renderer)
|
||||||
|
})
|
||||||
|
}
|
|
@ -3,35 +3,30 @@ import { PinWrapper } from './Gate'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
|
|
||||||
export class Wire {
|
export class Wire {
|
||||||
public id: number
|
public id: number
|
||||||
public active = true
|
public active = true
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public start: PinWrapper,
|
public start: PinWrapper,
|
||||||
public end: PinWrapper,
|
public end: PinWrapper,
|
||||||
ic: boolean = false,
|
ic: boolean = false,
|
||||||
id?: number
|
id?: number
|
||||||
) {
|
) {
|
||||||
if (!ic && end.value.pairs.size !== 0) {
|
if (!ic && end.value.pairs.size !== 0) {
|
||||||
throw new SimulationError('An input pin can only have 1 pair')
|
throw new SimulationError('An input pin can only have 1 pair')
|
||||||
}
|
|
||||||
|
|
||||||
end.value.addPair(start.value, true, !ic)
|
|
||||||
start.value.addPair(end.value, false, !ic)
|
|
||||||
|
|
||||||
// if (ic) {
|
|
||||||
// start.value.state.subscribe(console.log)
|
|
||||||
// end.value.state.subscribe(console.log)
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.id = id !== undefined ? id : idStore.generate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
end.value.addPair(start.value, true, !ic)
|
||||||
this.end.value.removePair(this.start.value)
|
start.value.addPair(end.value, false, !ic)
|
||||||
this.start.value.removePair(this.end.value)
|
|
||||||
this.end.value.state.next("0")
|
|
||||||
|
|
||||||
this.active = false
|
this.id = id !== undefined ? id : idStore.generate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
this.end.value.removePair(this.start.value)
|
||||||
|
this.start.value.removePair(this.end.value)
|
||||||
|
this.end.value.state.next('0')
|
||||||
|
|
||||||
|
this.active = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,8 @@ export const DefaultGateTemplate: GateTemplate = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
color: 'white'
|
color: 'white'
|
||||||
},
|
},
|
||||||
category: categories.basic
|
category: categories.basic,
|
||||||
|
info: []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -92,4 +92,5 @@ export interface GateTemplate {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
}
|
}
|
||||||
category: number // for better sorting
|
category: number // for better sorting
|
||||||
|
info: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ import { SelectedPins } from '../types/SelectedPins'
|
||||||
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 {
|
import {
|
||||||
fromSimulationState,
|
fromSimulationState,
|
||||||
fromCameraState
|
fromCameraState
|
||||||
} from '../../saving/helpers/fromState'
|
} from '../../saving/helpers/fromState'
|
||||||
import merge from 'deepmerge'
|
import merge from 'deepmerge'
|
||||||
import { handleScroll } from '../helpers/scaleCanvas'
|
import { handleScroll } from '../helpers/scaleCanvas'
|
||||||
|
@ -29,134 +29,132 @@ import { handleMouseMove } from '../helpers/handleMouseMove'
|
||||||
import { Gate } from '../../simulation/classes/Gate'
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
public mouseUpOutput = new Subject<MouseEventInfo>()
|
public mouseUpOutput = new Subject<MouseEventInfo>()
|
||||||
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
||||||
public wheelOutput = new Subject<unknown>()
|
public wheelOutput = new Subject<unknown>()
|
||||||
|
|
||||||
public selectedGates: Record<selectionType, Set<number>> = {
|
public selectedGates: Record<selectionType, Set<number>> = {
|
||||||
temporary: new Set<number>(),
|
temporary: new Set<number>(),
|
||||||
permanent: new Set<number>()
|
permanent: new Set<number>()
|
||||||
}
|
}
|
||||||
|
|
||||||
public options: SimulationRendererOptions
|
public options: SimulationRendererOptions
|
||||||
public camera = new Camera()
|
public camera = new Camera()
|
||||||
|
|
||||||
public selectedArea = new Transform()
|
public selectedArea = new Transform()
|
||||||
public clipboard: GateInitter[] = []
|
public clipboard: GateInitter[] = []
|
||||||
public wireClipboard: WireState[] = []
|
public wireClipboard: WireState[] = []
|
||||||
|
|
||||||
// first bit = dragging
|
// first bit = dragging
|
||||||
// second bit = panning
|
// second bit = panning
|
||||||
// third bit = selecting
|
// third bit = selecting
|
||||||
public mouseState = 0b000
|
public mouseState = 0b000
|
||||||
|
|
||||||
public lastMousePosition: vector2 = [0, 0]
|
public lastMousePosition: vector2 = [0, 0]
|
||||||
|
|
||||||
// this is used for spawning gates
|
// this is used for spawning gates
|
||||||
public spawnCount = 0
|
public spawnCount = 0
|
||||||
|
|
||||||
public selectedPins: SelectedPins = {
|
public selectedPins: SelectedPins = {
|
||||||
start: null,
|
start: null,
|
||||||
end: null
|
end: null
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
options: Partial<SimulationRendererOptions> = {},
|
options: Partial<SimulationRendererOptions> = {},
|
||||||
public simulation = new Simulation('project', 'default')
|
public simulation = new Simulation('project', 'default')
|
||||||
) {
|
) {
|
||||||
this.options = merge(defaultSimulationRendererOptions, options)
|
this.options = merge(defaultSimulationRendererOptions, options)
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
this.mouseDownOutput.subscribe(handleMouseDown(this))
|
this.mouseDownOutput.subscribe(handleMouseDown(this))
|
||||||
this.mouseUpOutput.subscribe(handleMouseUp(this))
|
this.mouseUpOutput.subscribe(handleMouseUp(this))
|
||||||
this.mouseMoveOutput.subscribe(handleMouseMove(this))
|
this.mouseMoveOutput.subscribe(handleMouseMove(this))
|
||||||
|
|
||||||
this.reloadSave()
|
this.reloadSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateWheelListener(ref: RefObject<HTMLCanvasElement>) {
|
public updateWheelListener(ref: RefObject<HTMLCanvasElement>) {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.addEventListener('wheel', (event) => {
|
ref.current.addEventListener('wheel', (event) => {
|
||||||
if (!modalIsOpen() && location.pathname === '/') {
|
if (!modalIsOpen() && location.pathname === `${process.env.BASEURL}/`) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
handleScroll(event, this.camera)
|
handleScroll(event, this.camera)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a simulation state
|
* Loads a simulation state
|
||||||
*
|
*
|
||||||
* @param save LThe state to load
|
* @param save LThe state to load
|
||||||
*/
|
*/
|
||||||
public loadSave(save: RendererState) {
|
public loadSave(save: RendererState) {
|
||||||
this.simulation = fromSimulationState(save.simulation)
|
this.simulation = fromSimulationState(save.simulation)
|
||||||
this.camera = fromCameraState(save.camera)
|
this.camera = fromCameraState(save.camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
public reloadSave() {
|
public reloadSave() {
|
||||||
dumpSimulation(this)
|
dumpSimulation(this)
|
||||||
|
|
||||||
const current = currentStore.get()
|
const current = currentStore.get()
|
||||||
const save = saveStore.get(current)
|
const save = saveStore.get(current)
|
||||||
|
|
||||||
if (!save) return
|
if (!save) return
|
||||||
if (!(save.simulation || save.camera)) return
|
if (!(save.simulation || save.camera)) return
|
||||||
|
|
||||||
this.loadSave(save)
|
this.loadSave(save)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all selected gates in the simulation
|
* Gets all selected gates in the simulation
|
||||||
*
|
*
|
||||||
* @throws SimulationError if an id isnt valid
|
* @throws SimulationError if an id isnt valid
|
||||||
* @throws SimulationError if the id doesnt have a data prop
|
* @throws SimulationError if the id doesnt have a data prop
|
||||||
*/
|
*/
|
||||||
public getSelected(): Gate[] {
|
public getSelected(): Gate[] {
|
||||||
return setToArray(this.allSelectedIds()).map((id) => {
|
return setToArray(this.allSelectedIds()).map((id) => {
|
||||||
const gate = this.simulation.gates.get(id)
|
const gate = this.simulation.gates.get(id)
|
||||||
|
|
||||||
if (!gate) {
|
if (!gate) {
|
||||||
throw new SimulationError(`Cannot find gate with id ${id}`)
|
throw new SimulationError(`Cannot find gate with id ${id}`)
|
||||||
} else if (!gate.data) {
|
} else if (!gate.data) {
|
||||||
throw new SimulationError(
|
throw new SimulationError(`Cannot find data of gate with id ${id}`)
|
||||||
`Cannot find data of gate with id ${id}`
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return gate.data
|
return gate.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper to merge the temporary and permanent selection
|
* helper to merge the temporary and permanent selection
|
||||||
*/
|
*/
|
||||||
public allSelectedIds() {
|
public allSelectedIds() {
|
||||||
return new Set([
|
return new Set([
|
||||||
...setToArray(this.selectedGates.permanent),
|
...setToArray(this.selectedGates.permanent),
|
||||||
...setToArray(this.selectedGates.temporary)
|
...setToArray(this.selectedGates.temporary)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to clear all selected sets
|
* Helper to clear all selected sets
|
||||||
*/
|
*/
|
||||||
public clearSelection() {
|
public clearSelection() {
|
||||||
this.selectedGates.permanent.clear()
|
this.selectedGates.permanent.clear()
|
||||||
this.selectedGates.temporary.clear()
|
this.selectedGates.temporary.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the selected pins of the renderer
|
* Clears the selected pins of the renderer
|
||||||
*/
|
*/
|
||||||
public clearPinSelection() {
|
public clearPinSelection() {
|
||||||
this.selectedPins.end = null
|
this.selectedPins.end = null
|
||||||
this.selectedPins.start = null
|
this.selectedPins.start = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,28 +3,26 @@ import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
import { fromHexColorString, fromChunks } from '../../colors/helpers/fromHex'
|
import { fromHexColorString, fromChunks } from '../../colors/helpers/fromHex'
|
||||||
|
|
||||||
export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
||||||
let color = 'rgba(0,0,0,0)'
|
let color = 'rgba(0,0,0,0)'
|
||||||
|
|
||||||
if (pin.pairs.size) {
|
if (pin.pairs.size) {
|
||||||
const { open, closed } = renderer.options.gates.pinFill
|
const { open, closed } = renderer.options.gates.pinFill
|
||||||
const digits = Array.from(pin.state.value).map(Number)
|
const digits = Array.from(pin.state.value).map(Number)
|
||||||
|
|
||||||
const colors = digits.map(digit => (digit ? open : closed))
|
const colors = digits.map((digit) => (digit ? open : closed))
|
||||||
const chunked = colors.map(fromHexColorString)
|
const chunked = colors.map(fromHexColorString)
|
||||||
|
|
||||||
const summed = [0, 1, 2]
|
const summed = [0, 1, 2]
|
||||||
.map(key =>
|
.map((key) =>
|
||||||
chunked
|
chunked
|
||||||
.flat()
|
.flat()
|
||||||
.filter((v, index) => index % 3 === key)
|
.filter((v, index) => index % 3 === key)
|
||||||
.reduce((acc, curr) => acc + curr, 0)
|
.reduce((acc, curr) => acc + curr, 0)
|
||||||
)
|
)
|
||||||
.map(value => Math.floor(value / digits.length))
|
.map((value) => Math.floor(value / digits.length))
|
||||||
|
|
||||||
color = fromChunks(summed)
|
color = fromChunks(summed)
|
||||||
|
}
|
||||||
|
|
||||||
// console.log(color)
|
return color
|
||||||
}
|
|
||||||
|
|
||||||
return color
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,28 +20,28 @@ b * (a - x0) = a - x1
|
||||||
x1 = a - b * (a - x0)
|
x1 = a - b * (a - x0)
|
||||||
*/
|
*/
|
||||||
export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
||||||
const sign = -e.deltaY / Math.abs(e.deltaY)
|
const sign = -e.deltaY / Math.abs(e.deltaY)
|
||||||
const zoom = scrollStep ** sign
|
const zoom = scrollStep ** sign
|
||||||
|
|
||||||
if (!e.deltaY) {
|
if (!e.deltaY) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { position, scale } = camera.transform
|
const { position, scale } = camera.transform
|
||||||
|
|
||||||
const mousePosition = [e.clientX, e.clientY]
|
const mousePosition = [e.clientX, e.clientY]
|
||||||
const oldPosition = [...mousePosition] as vector2
|
const oldPosition = [...mousePosition] as vector2
|
||||||
|
|
||||||
const oldScale = scale[0]
|
const oldScale = scale[0]
|
||||||
const newScale = clamp(zoomLimits[0], zoomLimits[1], oldScale * zoom)
|
const newScale = clamp(zoomLimits[0], zoomLimits[1], oldScale * zoom)
|
||||||
|
|
||||||
camera.transform.scale = repeat(newScale, 2) as vector2
|
camera.transform.scale = repeat(newScale, 2) as vector2
|
||||||
|
|
||||||
const scaleFraction = newScale / oldScale
|
const scaleFraction = newScale / oldScale
|
||||||
const newPosition = substract(
|
const newPosition = substract(
|
||||||
oldPosition,
|
oldPosition,
|
||||||
multiply(substract(oldPosition, position), scaleFraction)
|
multiply(substract(oldPosition, position), scaleFraction)
|
||||||
)
|
)
|
||||||
|
|
||||||
camera.transform.position = newPosition
|
camera.transform.position = newPosition
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import { CacheInstancesByKey } from '@eix-js/utils'
|
|
||||||
|
|
||||||
@CacheInstancesByKey(Infinity)
|
|
||||||
export class LocalStore<T> {
|
export class LocalStore<T> {
|
||||||
public constructor(public name: string) {
|
public constructor(public name: string) {
|
||||||
if (!localStorage.getItem(name)) {
|
if (!localStorage.getItem(name)) {
|
||||||
|
@ -13,9 +10,7 @@ export class LocalStore<T> {
|
||||||
|
|
||||||
if (!raw)
|
if (!raw)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`An error occured when accesing ${
|
`An error occured when accesing ${this.name} in the local storage!`
|
||||||
this.name
|
|
||||||
} in the local storage!`
|
|
||||||
)
|
)
|
||||||
else {
|
else {
|
||||||
return JSON.parse(raw)
|
return JSON.parse(raw)
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Disallow: /server.js
|
|
||||||
Disallow: /js
|
|
||||||
Disallow: /css
|
|
|
@ -1,16 +0,0 @@
|
||||||
import express, { static as _static } from 'express'
|
|
||||||
import { resolve } from 'path'
|
|
||||||
|
|
||||||
// create express app
|
|
||||||
const app = express()
|
|
||||||
|
|
||||||
// serve static assets
|
|
||||||
app.use(_static(__dirname))
|
|
||||||
|
|
||||||
// serve single page application
|
|
||||||
app.get('*', (rex, res) => {
|
|
||||||
res.sendFile(resolve(__dirname, 'index.html'))
|
|
||||||
})
|
|
||||||
|
|
||||||
// listen to the port from .env (default to 8080)
|
|
||||||
app.listen(process.env.PORT || 8080)
|
|
|
@ -1,14 +1,26 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"moduleResolution": "node",
|
"target": "ESNext",
|
||||||
"esModuleInterop": true,
|
"lib": [
|
||||||
"jsx": "preserve",
|
"DOM",
|
||||||
"experimentalDecorators": true,
|
"DOM.Iterable",
|
||||||
"target": "ESNext",
|
"ESNext"
|
||||||
"downlevelIteration": true,
|
],
|
||||||
"strictNullChecks": true,
|
"module": "ESNext",
|
||||||
"module": "esnext"
|
"skipLibCheck": true,
|
||||||
},
|
/* Bundler mode */
|
||||||
"exclude": ["node_modules"],
|
"moduleResolution": "bundler",
|
||||||
"include": ["src"]
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
/* Features */
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"strictNullChecks": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,171 +0,0 @@
|
||||||
const { resolve } = require('path')
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
|
|
||||||
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'
|
|
||||||
|
|
||||||
const projectRoot = resolve(__dirname)
|
|
||||||
const sourceFolder = resolve(projectRoot, 'src')
|
|
||||||
const buildFolder = resolve(projectRoot, 'dist')
|
|
||||||
const htmlTemplateFile = resolve(sourceFolder, 'index.html')
|
|
||||||
|
|
||||||
const babelRule = {
|
|
||||||
test: /\.(js|tsx?)$/,
|
|
||||||
use: 'babel-loader'
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileRule = {
|
|
||||||
test: /\.(png|svg|jpg|gif|ico)$/,
|
|
||||||
use: ['file-loader']
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssAndSass = [
|
|
||||||
isProduction
|
|
||||||
? MiniCssExtractPlugin.loader
|
|
||||||
: {
|
|
||||||
loader: 'style-loader',
|
|
||||||
options: {
|
|
||||||
singleton: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'css-loader'
|
|
||||||
]
|
|
||||||
|
|
||||||
const cssRule = {
|
|
||||||
test: /\.css$/,
|
|
||||||
use: cssAndSass
|
|
||||||
}
|
|
||||||
|
|
||||||
const sassRule = {
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [
|
|
||||||
...cssAndSass,
|
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: {
|
|
||||||
includePaths: [sourceFolder]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverConfig = {
|
|
||||||
mode: 'production',
|
|
||||||
target: 'node',
|
|
||||||
externals: [nodeExternals()],
|
|
||||||
module: {
|
|
||||||
rules: [babelRule]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.js', '.json']
|
|
||||||
},
|
|
||||||
entry: [resolve(sourceFolder, 'server')],
|
|
||||||
output: {
|
|
||||||
filename: 'server.js',
|
|
||||||
path: buildFolder,
|
|
||||||
publicPath: '/'
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
__dirname: false
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new CopyPlugin([
|
|
||||||
{
|
|
||||||
from: resolve(sourceFolder, 'public'),
|
|
||||||
to: buildFolder
|
|
||||||
}
|
|
||||||
])
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseConfig = {
|
|
||||||
mode: 'none',
|
|
||||||
entry: ['babel-regenerator-runtime', resolve(sourceFolder, 'index')],
|
|
||||||
output: {
|
|
||||||
filename: 'js/[name].js',
|
|
||||||
path: buildFolder,
|
|
||||||
publicPath: '/'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [babelRule, sassRule, cssRule, fileRule]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [
|
|
||||||
'.js',
|
|
||||||
'.ts',
|
|
||||||
'.tsx',
|
|
||||||
'.scss',
|
|
||||||
'.png',
|
|
||||||
'.svg',
|
|
||||||
'.jpg',
|
|
||||||
'.gif'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const devConfig = {
|
|
||||||
mode: 'development',
|
|
||||||
plugins: [
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: htmlTemplateFile,
|
|
||||||
chunksSortMode: 'dependency'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
devtool: 'inline-source-map',
|
|
||||||
devServer: {
|
|
||||||
historyApiFallback: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prodConfig = {
|
|
||||||
mode: 'production',
|
|
||||||
optimization: {
|
|
||||||
minimize: true,
|
|
||||||
nodeEnv: 'production'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: 'css/[name].min.css'
|
|
||||||
}),
|
|
||||||
new OptimizeCssAssetsWebpackPlugin(),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: htmlTemplateFile,
|
|
||||||
minify: {
|
|
||||||
removeComments: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
useShortDoctype: true,
|
|
||||||
removeEmptyAttributes: true,
|
|
||||||
removeStyleLinkTypeAttributes: true,
|
|
||||||
keepClosingSlash: true,
|
|
||||||
minifyJS: true,
|
|
||||||
minifyCSS: true,
|
|
||||||
minifyURLs: true
|
|
||||||
},
|
|
||||||
inject: true
|
|
||||||
// favicon: faviconPath
|
|
||||||
}),
|
|
||||||
new HtmlWebpackInlineSourcePlugin()
|
|
||||||
],
|
|
||||||
devtool: 'source-map'
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFinalConfig() {
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
console.info('Running production config')
|
|
||||||
return [webpackMerge(baseConfig, prodConfig), serverConfig]
|
|
||||||
} else if (process.env.NODE_ENV === 'server') {
|
|
||||||
console.info('Running server config')
|
|
||||||
return [serverConfig]
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('Running development config')
|
|
||||||
return webpackMerge(baseConfig, devConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = getFinalConfig()
|
|