Compare commits

...

No commits in common. "prototype" and "main" have entirely different histories.

305 changed files with 12280 additions and 1366 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

41
.github/workflows/build.yaml vendored Normal file
View 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
- 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/main'
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
node_modules
dist
coverege
idea
.direnv
result
.envrc

7
.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"semi": false,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}

21
LICENSE.md Normal file
View file

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2019 Stepan Shabalin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.**

View file

@ -1,11 +1,22 @@
# Logic-gate-simulator
# Logic gate simulator
This was my first attempt at making a logic gate simulator. I did this right after finishing the 8th grade, and let's just say it wasn't so good:) If you want to check out an actually good logic gate simulator, check out [my latest attempt at doing it](https://github.com/Mateiadrielrafael/logicGateSimulator)
This is a logic gate simulator I originally wrote for infoeducatie 2019. Check out the [online instance](https://erratic-gate.moonythm.dev/).
Basic controls:
Tap on the blue box to add components
Tap on 2 pins to connect them
Tap on a cable to remove it
Enjoy XD
Contributions are welcome (if for some reason you want to contribute to this).
## Features
- Simple & intuitive UI
- (Parametrized) integrated circuits
- Multi-bit signals
- Multiple language support: Romanian, English, Dutch, Turkish, Chinese & more in the future
## Contributing
This repo provides a nix flake for deployment.
## Credits
Many thanks to:
1. [Canana](http://canana.xyz/) for the logo!
2. Player_0_1#2955 for all the testing and ideas
3. BlueGhost, Vyctor661, Mao for all the design reviews

60
build.js Normal file
View file

@ -0,0 +1,60 @@
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 || ''
const nodeEnv = process.env.NODE_ENV || 'production'
const isProd = nodeEnv !== 'development'
console.log(`Building with baseurl ${baseurl}`)
const ctx = await esbuild.context({
entryPoints: ['src/index.ts'],
minify: isProd,
bundle: true,
metafile: true,
splitting: isProd,
outdir: 'dist',
format: 'esm',
target: ['es2020'],
assetNames: 'assets/[name]',
chunkNames: 'chunks/[name]',
loader: {
'.svg': 'file'
},
define: {
'process.env.BASEURL': JSON.stringify(baseurl),
'process.env.NODE_ENV': JSON.stringify(nodeEnv)
},
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) {
await ctx.watch()
const { port, host } = await ctx.serve({
servedir: 'dist',
fallback: 'dist/index.html'
})
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`)
}

117
docs/actual-docs.md Normal file
View file

@ -0,0 +1,117 @@
# Simulator de porti logice
Programul este un simulator de porti logice care permite experimentarea, testarea si cunoasterea ansamblelor de porti logice. Simulatorul suporta circuite integrate, mai multi biti pe acelasi pin, salvarea simulatiilor si mai multe limbii.
## Arhitectura:
### Aspecte generale:
- Proiectul este scris folosing stilul de programare reactiva (bazata pe streamuri asincron)
### Structora fisierelor:
In folderul [src](../src) se afla urmatoarele fisiere si foldere:
- [assets](../src/assets) - aici este stocata toata arta folosita in simulator. Totul in afara de fisierul [favicon.ico](../src/assets/favicon.ico) a fost desenat de mine folosind [Figma](https://www.figma.com/)
- [public](../src/public) - contine toate fisierele ce trebuie copiate in folderul dist atunci cand aplicatia e compilata
- [index.html](../src/index.html) - fisierul de intrare al intregii aplicatii
- [index.ts](../src/index.ts) - fisierul care initializeaza animatia de incarcare
- [main.tsx](../src/main.tsx) - fisierul care se ocupa de pornirea aplicatiei. Acest fisier va fi descris mai amanuntit in sectiune `Performanta`
- [server.ts](../src/server.ts) - fisierul care serveste resursele statice
- [modules](../src/modules) & [common](../src/common) - Aceste foldere contin aplicatia propriu-zisa
### Folderele 'modules' si 'common'
Am ales sa folosesc structura folosita de [wavedistrict](https://gitlab.com/wavedistrict/web-client/tree/master/src). Aceasta consta in 2 foldere: `common` si `modules`. Acestea contin subfoldere numite dupa functionalitatea pe care o implementeaza. La randul lor, acestea, contin foldere care sunt numite dupa tipul de fisiere continute.
Exemplu:
```
/modules
/simulationRenderer
/subjects
mySubject.ts
/components
Simulation.tsx
/common
/dom
/helpers
safeQuerSelector.ts
```
Reprezentare grafica a fisierelor (fiecare cerc reprezinta un fisier):
![files](../docs/assets/files.png)
### Module:
Proiectul este impartit in 18 module distincte:
1. `Activation`: se ocupa cu transformarea functiilor de `activare` in cod javascript
2. `Colors`: se ocupa de manipularea culorilor
3. `Core`: contine componentele vizuale de baza ale aplicatiei
4. `Create-simulation`: implementeaza procesul de creere a unei simulatii
5. `Errors`: se ocupa de prinsul si afisatul erorilor
6. `Input`: se ocupa de utilizarea textului ca input
7. `Integrated-circuits`: se ocupa de compilarea circuitelor integrate
8. `Internalisation`: se ocupa de afisarea textului in diferite limbi
9. `Keybindings`: se ocupa de utilizarea tastaturii ca input
10. `Logic-gates-info`: implementeaza pagina cu informatii despre portile logice
11. `Logic-gates`: implementeaza pagina de unde se pot adauga porti logice
12. `Modals`: implementeaza caracteristicile generale folosite de toate dialogurile
13. `Saving`: se ocupa de salvarea simulatiilor
14. `Screen`: se ocupua de adaptarea aplicatiei la orice rezolutie
15. `Simulation`: se ocupa de simularea circuitelor
16. `Storage`: se ocupa de salvarea datelor in `localStorage`
17. `Toasts`: se ocupa de stilizarea notificarilor oferite de `react-toastify`.
18. `Vector2`: functii de baza care permit folosirea arrayurilor pentru geometria vectoriala
### Performanta
- Programarea reactiva este bine cunoscuta pentru ca poate crea memory leaks foarte usor. Pentru a ma asigura ca asa ceva nu se poate intampla, am folosit urmatoarele 2 tehnici:
1. Folosirea operatorului `take()` pentru a astepta doar un anumit numar de valori
2. Fiecare clasa care foloseste streamuri are o metoda numita `.dispose()` care curata toate subscriptiile streamurilor folosite de instanta respectiva si cheama metoda cu acelasi nume pe toate proprietatile care la randul lor folosesc streamuri.
- Pentru ca userul sa nu vada un ecran gol nici macar o secunda, am urmat urmatorii pasi:
1. Userului ii este trimis fisierul `index.html` ,`index.js` si `splash.css` cu o marime totala de doar 18kb. Aceste fisiere au doar rolul de a afisa o animatie de incarcare pe ecran.
2. Dupa ce animatia porneste, restul fisierelor sunt cerute de la server.
3. Cand fisierul `main.js` este primit, acesta este rulat. El este responsabil pentru:
- rendarea aplicatiei
- initializarea hotkeyurilor
- prinderea erorilor si aratarea acestora intr-un format usor de inteles
- creerea `subiectelor` (streamuri care au o metoda numita `next`) necesare intregii aplicatii
- actualizarea portilor logice salvate in eventualitatea unor noi aparitii
4. Fisierul `main.js` expune o functie asincron numita `main` care este responsabila pentru functionalitatii enumerate mai sus. Aceasta functie este finalizata doar atunci cand toate aceste actiuni au fost realizate.
5. Cand functia `main` din fisierul `main.js` este terminata, fisierul `index.js` isi termina activitatea prin scaderea opacitatii animatiei de incarcare si in final scoaterea acesteia din `DOM`.
### Librarii, limbaje si unelte folosite:
Mentionez ca o mare parte din elementele acestei liste sunt libarii si unelte folosite in timpul developmentului, doar o mica parte ajungand in buildul final.
- [Sass](https://sass-lang.com/) pentru styling. Am ales sa foloses sass in special pentru ca ofera variabile si mixinuri care sunt rezolvate la compilare, acestea ajutand la refolosirea stilurilor. De exemplu, nu toate dialogurile au titluri, si la unele dintre ele layoutul este putin diferit, asa ca am hotarat sa scriu 2 mixinuri: [@modal-title()](../src/modules/modals/styles/mixins/modal-title.scss) si [@modal-container()](../src/modules/modals/styles/mixins/modal-container.scss), astfel respectand principiul compozitiei. Un alt exemplu sunt mixinurile [@flex()](../src/modules/core/styles/mixins/flex.scss) si [@full-screen()](../src/modules/core/styles/mixins/full-screen.scss) pe care le-am scris deoarece incorporeaza functionalitati pe care urma sa le folosesc in mai multe parti ale aplicatiei.
- [Typescript](https://www.typescriptlang.org/) - De aproximativ un an am facut trecerea de la javascript la typescript, si avantajele sunt enorme. Pot spune ca in acest proiect typescript m-a salvat de multe buguri deoarece ma anunta de existenta lor la compile-time.
- [Webpack](https://webpack.js.org/) & plugins & loaders pentru bundlingul asseturilor + code splitting.
- [Babel](https://babeljs.io/) pentru a compila jsx, typescript si pentru compatibilitatea cu browsere mai vechi.
- [React](https://reactjs.org/) & [React-dom](https://reactjs.org/docs/react-dom.html) & [React-router-dom](https://www.npmjs.com/package/react-router-dom) & [React-helmet](https://github.com/nfl/react-helmet) & [Material-ui](https://material-ui.com/) & [react-toastify](https://github.com/fkhadra/react-toastify) pentru ui. Aceste tehnologii au fost folosite pentru a crea bara laterala de pe partea dreapta, pagina /gates si pagina /info/:name. Mentionez ca rendarea, interactiunile (drag & drop, hotkeys, selection etc) intregii simulatii sunt facute de la 0 fara a folosi nici una din aceste libarii.
- [Github](https://github.com/) & [Git](https://git-scm.com/) - Git si github sunt niste unelte indispensabile pentru orice programator, si pot spune ca acest proiect a beneficiat mult deoarece am folosit aceste tehnologii. In primul rand, cu putin setup pot lansa pe heroku automat mereu cand rulez comanda `push` pe ramura `master`. In al doilea rand, la un moment dat laptopul meu a ramas fara baterie si inchizanduse cateva fisiere au fost corupte. Daca nu as fi folosit git acel incident mi-ar fi distru cateva ore de munca
- [Visual studio code](https://code.visualstudio.com/) Am ales acest editor deoarece are cel mai bun suport pentru typescript existent la momentul actual.
- [Eix-js](https://eix-js.github.io/core/) este un mic game engine pe care l-am scris impreuna cu un baiat de 13 ani din Rusia in timpul ludum-dare 44. In acest proiect am folosit modulul [utils](https://github.com/eix-js/utils) care ofera diferiti decoratori utili cum ar fi: [@Singleton](https://github.com/eix-js/utils/blob/master/src/modules/decorators/Singleton.ts) care imi permite sa modific automat constructorul unei clase in asa fel incat sa returneze mereu aceiasi instanta si [@CacheInstanceByKey](https://github.com/eix-js/utils/blob/master/src/modules/decorators/CacheInstancesByKey.ts) care imi permite sa ma asigur ca pentru fiecare cheie exista o singura instants existenta. Am extins si o implementare a unui [nod dintr-un cache LRU](https://github.com/eix-js/utils/blob/master/src/modules/classes/LruCache.ts) care mi-a permis sa ma asigur ca atunci cand mouseul este apasat, interactiunea se va produce asupra componentului care este cel mai `aproape de utilizator` (sau care are pozitia pe axa Z cea mai mare).
- [Rxjs](https://rxjs-dev.firebaseapp.com/) - Am folosit rxjs deoarece proiectul este construit folosind `programarea reactiva` (sau bazata pe streamuri asincron). Programarea reactiva este bine cunoscuta deoarece face creerea de `memory leaks` foarte usoara, asa ca proiectul este scris cu mare grija pentru a preveni orice posibil incident de acest tip.
- [Rxjs-hooks](https://github.com/LeetCode-OpenSource/rxjs-hooks) imi permite sa folosesc functia `useObservable()` cu ajutorul careia pot renda valorile streamurilor direct cu ajutorul React.
- [express](https://expressjs.com/) pentru servirea resurselor statice
- [keycode](https://www.npmjs.com/package/keycode) pentru a transforma numele unei taste in codul potrivit (utilizat in rendarea combinatilor de taste sub butoane)
- [mainloop.js](https://github.com/IceCreamYou/MainLoop.js?utm_source=recordnotfound.com) pentru a rula cod de 60 ori pe secunda intr-o maniera eficienta. Mentionez ca singurul lucru care este rulat in acest mod este codul responsabil pentru rendarea simulatiei. Simularea portilor logice este facuta doar atunci cand ceva se schima si doar acolo unde ceva s-a schimbat.
## Testare
Aplicatia propriu zisa nu dispune de unit / integration tests. Aceasta a fost testata de aproximativ 30 de persoane.
Mentionez ca am scris teste pentru o mica librarie pe care am folosit-o numita eix-js.
### De ce nu a fost nevoie de teste?
In afara de faptul ca mai multi oameni au testat manual proiectul, typescript m-a ajutat sa detecte aproximativ 75% din erori la compile-time.
## Testimoniale
- Player_0_1 a fost testerul principal de la incputul proiectului. El a gasit o multime de buguri pe care datorita lui am putut sa le aflu si sa le rezolv:
_I have been testing the Logic Gate Simulator by Adriel. While testing it I found a few bugs, the first bug I found was a problem with the wires. If I started a wire at a component and didn't connect the wire to another component and then deleted the component the wire was connected to it would make a floating wire that was still connected to my mouse that I couldn't get rid of. Another bug I ran into was the IC in/out pins where they would overlap because the IC wouldn't fit there size. The last major bug that I ran into dealt with the pasting and duplicating components. when you pasted in the components they would paste in right on top of what you copied/duplicated. so you wouldn't be able to get the components that you pasted/duplicated without having to drag each component off. After finding these problems I and Adriel were able to talk about them and he was able to find the problem and fix them efficiently. After Adriel solved each bug it was very easy to go back into the Simulator and start making circuits/ICs again. I tested a variety of circuits like making half-adders and full adders. I also tested different types of flip flops and bit adding circuits. Everything I've made worked very well and if something didn't work Adriel was able to fix it so it worked in the future. After using this Simulator I feel like it has a very good and working concept and I cant wait to see what's done with it in the future._

15
docs/assets-i-dont-own.md Normal file
View file

@ -0,0 +1,15 @@
# Assets & code i haven't made myself:
- [The logo](../src/assets/favicon.ico) was made by [Canana](http://canana.xyz/)
- [The Splash class](../src/modules/Splash/classes/Splash.ts) is a modified version of [this file](https://gitlab.com/wavedistrict/web-client/blob/master/src/modules/splash/classes/Splash.ts)
- [My webpack config](../webpack.config.js) is a modified version of [this file](https://gitlab.com/wavedistrict/web-client/blob/master/src/modules/splash/classes/Splash.ts)
- [The reset.css file](../src/modules/core/styles/reset.scss) is a modified version of the one found [here](https://meyerweb.com/eric/tools/css/reset/)
- [The removeElement function](../src/common/lang/arrays/helpers/removeElement.ts) is a typed version from a snippet found in a gist made by the creator of rollup (i can't find the link right now)
- [The google material icons](https://material.io/resources/icons/) are as the name suggests, made by Google.
- The fonts are imported from [here](https://fonts.google.com/)
A list with all code dependencies can be found [here](../package.json)
## Why is there a second contributor?
This repo has a second contributor (about 150 lines of code) because an older version used a template made by him. The current version doesn't use anything from that template.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
docs/assets/components.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/assets/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/assets/gates.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

6
docs/main.md Normal file
View file

@ -0,0 +1,6 @@
# Table of content:
- [Installation / Running guide](./tutorials/installation.md)
- [Controls](./tutorials/controls.md)
- [The infoeducatie docs](./actual-docs.md)
- [Basic usage](./tutorials/sr-latch.md)

View 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

View file

@ -0,0 +1,28 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
# The controls
| Action | Keybinding | Gui |
| :---------------------------------: | :---------------------: | :--------------------------------------------------------------------------: | ------------------------------------------------------ |
| Save | `ctrl + s` | `Simulation` > `Save` |
| Add a gate | - | click `logic-gates` > the gate you want to add |
| Move a gate | - | drag & drop the gate using the `left mouse button` |
| Pan | - | click & move & release the background useing the `right mouse button` |
| Select multiple gates | - | click & move & release using the `left mouse button` |
| Add to selection | `shift` | select while holding `shift` |
| Select all gates | `ctrl + a` | `Simulation` > `Select all` |
| Delete selection | `delete` | `Simulation` > `Delete selection` |
| Undo _(to last save)_ | `ctrl + z` | `Simulation` > `Undo` |
| Refresh (reload) simulation | `ctrl + r` | `Simulation` > `Refresh` |
| Clean (delete unconnected gates) | `ctrl + delete` | `Simulation` > `Clean` |
| Delete simulation | `ctrl + shift + delete` | `Simulation` > `Delete simulation` |
| Open simulation | - | `Open simulation` > the simulation you want to open |
| Create simulation | - | `Create simulation` > `Project` | `Integrated circuit` > type the name of the simulation |
| Create project | - | `Create simulation` > `Project` > type the name of the simulation |
| Create integrated circuit | - | `Create simulation` > `Integrated circuit` > type the name of the simulation |
| Change language | - | Click on the `Language: \<language\>` button (bottom of the sidebar) |
| Get more info about a built in gate | - | `Logic gates` > <i class="material-icons">info</i> |
| Duplicate selection | `ctrl + d` | `Simulation` > `Duplicate selection` |
| Copy selection | `ctrl + c` | `Simulation` > `Copy` |
| Cut selection | `ctrl + x` | `Simulation` > `Cut` |
| Paste selection | `ctrl + v` | `Simulation` > `Paste` |

View file

@ -0,0 +1,48 @@
# Rularea simulatorului:
Exista 3 metode de a rula simulatorul:
1. Folosind [versiunea hostata pe heroku](https://logic-gate-simulator.herokuapp.com/):
Aceasta este cea mai usoara solutie - tot ce trebuie sa faceti este sa deschideti
[acest url](https://logic-gate-simulator.herokuapp.com/)
2. Folosind un server de dezvoltare:
Pentru inceput trebuie sa aveti [node.js & npm](https://nodejs.org/en/download/) si [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) instalate pe dizpozitiv.
Deschideti un terminal, si introduceti urmatoarele comenzi:
```sh
# Cloeaza repoul
git clone https://github.com/Mateiadrielrafael/logicGateSimulator
# Cd in folder
cd logicGateSimulator
# instaleaza librariile necesare
npm install
# ruleaza serverul de dezvoltare
npm run dev
```
Browserul va fi deschis automat la adresa serverului.
3. Prin compilarea locala a simulatorului
Pentru inceput, clonati repoul si instalati librariile dupa cum este explicat in optiunea 2.
Pentru a compila codul sursa, introduceti urmatoarea comanda:
```sh
npm run build
```
Pentru a rula simulatorul, rulati comanda:
```sh
npm start
```
Proiectul este acum accesibil sub portul `8080`

View file

@ -0,0 +1,29 @@
# Ghid de utilizare a aplicatiei
Pentru a explica utilizarea functiilor de baza ale aplicatiei, urmatoarea secventa va parcurge procesul de creeere a unui latch SR:
1. Creeerea unei noi simulatii:
- se apasa butonul „Create simulation” de pe bara din dreapta, se da click pe "project" si se tasteaza numele dorit.
2. Adaugarea componentelor neccesare:
- se utilizeaza butonul „logic gate” de pe bara din partea dreapta, si se adauga urmatoarele componente: 2 x „button” 2 x „light bulb” 2 x „nor gate”.
3. Conectarea componentelor:
- pentru a crea un cablu se apasa pe 2 pini (unul de input si unul de output)
- pentru a sterge un cablu se apasa pe pinul de output al cablului
- pentru a misca un component se utilizeaza butonul din stanga al mouseului
- se conecteaza componentele in cunoscuta configuratie a latchului SR:
![SR latch](../assets/SR.png)
- prin apasarea butoanelor se poate testa latchul, care ar trebui sa respecte cunosctul tablel de adevar:
| S | R | Q | not Q |
| :-: | :-: | :---: | ----- |
| 0 | 0 | latch | latch |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 0 |

61
flake.lock Normal file
View file

@ -0,0 +1,61 @@
{
"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": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"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
}

30
flake.nix Normal file
View file

@ -0,0 +1,30 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
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.override { stdenv = pkgs.stdenvNoCC; } {
name = "erratic-gate";
src = pkgs.lib.cleanSource ./.;
npmDepsHash = "sha256-f5mw6IjkhZgsIuzCz9d7DvoAdceY1y+yWXn1BOonsVI=";
installPhase = ''
mkdir $out
cp -r dist $out/www
'';
};
packages.default = packages.erratic-gate;
}
);
}

View file

@ -1,86 +0,0 @@
<html>
<head>.
<title>
Logic simulator
</title>
<!-- Scripts -->
<!-- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme --
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
!--Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body id="body">
<svg height="100%" width="100%" id="svg1" viewbox="0 0 700 700">
</svg>
<!--And-->
<script src="scripts/and_gate.js"></script>
<!--Pin-->
<script src="scripts/pins.js"></script>
<!--Edges-->
<script src="scripts/edges.js"></script>
<!--Button-->
<script src="scripts/but.js"></script>
<!--light bulb-->
<script src="scripts/light.js"></script>
<!--or-->
<script src="scripts/or.js"></script>
<!--or-->
<script src="scripts/text.js"></script>
<!--not-->
<script src="scripts/not.js"></script>
<!--buffer-->
<script src="scripts/buffer.js"></script>
<!--nand-->
<script src="scripts/nand.js"></script>
<!--nand with 3 inputs-->
<script src="scripts/nand3.js"></script>
<!--xor-->
<script src="scripts/xor.js"></script>
<!--zoom-->
<script src="scripts/events.js"></script>
<!--Main-->
<script src="scripts/main.js"></script>
<div class="modal fade" id="addel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<p>
Add a piece!
</p>
</div>
<div class="modal-body">
<select id="sel">
<option value = "not">Not gate</option>
<option value = "light">Light bulb</option>
<option value = "but">Button</option>
<option value = "buffer">Buffer</option>
<option value = "or">Or gate</option>
<option value = "and">And gate</option>
<option value = "nand">Nand gate</option>
<option value = "nand3">Nand gate (3 inputs)</option>
<option value = "xor">Xor gate</option>
</select> Quantity:
<input id="times" type="number">
</div>
<div class="modal-footer">
<button type="button" onclick='$("#addel").modal("hide"); addel();' class="btn btn-primmary" id="done">
Add!!!
</button>
</div>
</div>
</div>
</div>
<link href="st.css" rel="Stylesheet" type="text/css" />
</body>
</html>

2344
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

34
package.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "erratic-gate",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "NODE_ENV=development ESBUILD_SERVE=1 node ./build.js",
"build": "node ./build.js",
"check": "tsc"
},
"devDependencies": {
"@craftamap/esbuild-plugin-html": "^0.6.1",
"@types/deepmerge": "^2.2.0",
"@types/mainloop.js": "^1.0.5",
"@types/node": "^20.8.9",
"@types/react-dom": "^18.2.14",
"@types/react-router-dom": "^4.3.4",
"esbuild": "^0.19.5",
"esbuild-sass-plugin": "^2.16.0",
"typescript": "^5.0.2"
},
"dependencies": {
"@eix-js/utils": "0.0.6",
"@material-ui/core": "^4.2.1",
"deepmerge": "^4.0.0",
"keycode": "^2.2.0",
"mainloop.js": "^1.0.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-toastify": "^5.3.2",
"rxjs": "^6.5.2",
"rxjs-hooks": "^0.5.1"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

66
public/index.html Normal file
View 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>

View file

@ -1,84 +0,0 @@
class and {
constructor(id) {
this.id = id;
this.rep = add(80, 80, "blue", "black", this.id, false);
this.pin1 = new pin(0);
this.pin2 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
this.activation = function() {
if (this.pin1.val && this.pin2.val) {
return true;
}
return false;
};
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
};
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
};
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "72",
width: "72",
src: "textures/gates/and_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y);
this.pin2.set(x - 20, y + 60);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
};
pieces[pieces.length] = this;
addclk(this);
}
}
function addclk(ob) {
$((ob.img)).on("mousedown touchstart", function(e) {
e.preventDefault();
let svg = document.getElementById("svg1");
$(svg).append(ob.rep);
$(svg).append($(ob.pin1.rep));
$(svg).append($(ob.pin2.rep));
$(svg).append($(ob.o.rep));
$(svg).append($(ob.skin));
selected = ob.id;
});
$((ob.img)).on("mouseup touchend", function() {
selected = "yay";
});
}

View file

@ -1,71 +0,0 @@
function buffer(id) {
this.id = id;
this.name = "#" + this.id;
this.rep = add(80, 80, "green", "black", this.id, true);
this.pin1 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
//this.text = new text(this,"Or-gate");
this.activation = function() {
if (this.pin1.val) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "80",
width: "80",
src: "textures/gates/buffer_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y + 30);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
addclk11(this);
}

View file

@ -1,60 +0,0 @@
function but(id) {
this.wait = true;
this.id = id;
this.rep = add(80, 80, "orange", "black", this.id, true);
this.o = new pin(1);
this.val = false;
addevt(this);
$((this.rep)).attr("stroke-width", 4);
$((this.rep)).attr("stroke", "black");
this.o.nei = this;
this.activation = function() {
return this.val;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
this.init = function() {
return 0;
}
this.update = function() {
let x = this.x();
let y = this.y();
this.o.set(x + 80, y + 30);
}
pieces[pieces.length] = this;
}
function addevt(ob) {
$((ob.rep)).on("mousedown touchstart", function(e) {
let svg = document.getElementById("svg1");
$(svg).append(ob.rep);
$(svg).append($(ob.o.rep));
selected = ob.id;
e.preventDefault();
if (ob.wait) {
ob.val = (ob.val + 1) % 2;
if (ob.val) {
$((ob.rep)).attr("fill", "red");
} else {
$((ob.rep)).attr("fill", "orange");
}
ob.wait = false;
}
});
$((ob.rep)).on("mouseup touchend", function() {
selected = "yay";
ob.wait = true;
});
}

View file

@ -1,83 +0,0 @@
let l_count = 0;
let lines = [];
function edge(start, end) {
this.id = l_count.toString() + "line";
l_count++;
this.start = start;
this.end = end;
lines[lines.length] = this;
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let el = $(document.createElementNS('http://www.w3.org/2000/svg', 'line')).attr({
x1: "50",
y1: "50",
x2: "55",
y2: "55",
id: this.id,
fill: "yellow",
stroke: "black"
});
$(el).attr("stroke-width", "2");
$(g).append(el);
let elem = document.getElementById("svg1");
elem.appendChild(g);
this.rep = el;
this.name = "#" + this.id;
this.update = function() {
let adr = this.start.name;
let temp = $(adr).attr("x");
let n = this.name;
temp = (parseFloat(temp)).toString();
$(n).attr("x1", temp);
temp = $(adr).attr("y");
temp = (parseFloat(temp) + 10).toString();
$(n).attr("y1", temp);
adr = this.end.name;
temp = $(adr).attr("x");
temp = (parseFloat(temp) + 20).toString();
$(n).attr("x2", temp);
temp = $(adr).attr("y");
temp = (parseFloat(temp) + 10).toString();
$(n).attr("y2", temp);
//and the color based on the state
if (this.start.val) {
$((this.rep)).attr("stroke", "yellow");
} else {
$((this.rep)).attr("stroke", "black");
}
}
rem_edge(this);
}
function rem_edge(ob) {
$((ob.rep)).on("click touchstart", function(e) {
e.preventDefault();
//removing the edge from the array
for (let i = 0; i < lines.length; i++) {
if (lines[i] == (ob.id)) {
lines.splice(i, 1);
}
}
//removing the visual
$((ob.rep)).remove();
//fixing the actual start and end pins
ob.start.nei = "yay";
ob.start.val = false;
ob.start.state = true;
ob.end.val = false;
ob.end.state = true;
});
}

View file

@ -1,100 +0,0 @@
window.addEventListener("keydown", function(e) {
// space and arrow keys
if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
e.preventDefault();
}
}, false);
let xvb = 0;
let yvb = 0;
let zoomx = window.innerWidth;
let zoomy = window.innerHeight;
updatescr();
function updatescr() {
if (zoomx < 51) {
zoomx += 50;
}
if (zoomy < 51) {
zoomy += 50;
}
let newname = xvb.toString() + " " + yvb.toString() + " " + zoomx.toString() + " " + zoomy.toString();
$("#svg1").removeAttr("viewBox");
$("#svg1").each(function() { $(this)[0].setAttribute("viewBox", newname) });
}
document.addEventListener("keydown", e => {
if (e.keyCode == 40) {
yvb += 10;
updatescr();
} else if (e.keyCode == 39) {
xvb += 10;
updatescr();
} else if (e.keyCode == 38) {
yvb -= 10;
updatescr();
} else if (e.keyCode == 37) {
xvb -= 10;
updatescr();
} else if (e.keyCode == 187) {
if (!(zoomx < 101) && !(zoomy < 101)) {
xvb += 25;
yvb += 25;
}
zoomx -= 50;
zoomy -= 50;
updatescr();
} else if (e.keyCode == 189) {
xvb -= 25;
yvb -= 25;
zoomx += 50;
zoomy += 50;
updatescr();
}
}, false);
let zooming = false;
let xbeg = 0;
let ybeg = 0;
let moveing = false;
$("svg").on("mousemove touchmove", function(e) {
if (moveing && selected == "yay") {
if (!(zooming)) {
zooming = true;
//setting our first mouse poitions
xbeg = e.pageX * zoomx / window.innerWidth;
ybeg = e.pageY * zoomy / window.innerHeight;
xbeg += xvb;
ybeg += yvb;
//moveing = (moveing +1)%2;
}
let newx = e.pageX * zoomx / window.innerWidth;
let newy = e.pageY * zoomy / window.innerHeight;
newx += xvb;
newy += yvb;
xvb -= newx - xbeg;
yvb -= newy - ybeg;
updatescr();
}
});
$("svg").on("mousedown touchstart", function() {
//e.preventDefault();
moveing = true;
});
$("svg").on("mouseup touchend", function(e) {
e.preventDefault();
selected = "yay";
zooming = false;
moveing = false;
});

View file

@ -1,74 +0,0 @@
function light(id) {
this.id = id;
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let el = $(document.createElementNS('http://www.w3.org/2000/svg', 'circle')).attr({
cx: "50",
cy: "50",
r: "40",
id: this.id,
fill: "white",
stroke: "black"
});
$(el).attr("stroke-width", "2");
$(el).attr("class", "light");
$(g).append(el);
//$(el).attr("onmousedown","selected='"+this.id+"';");
$(el).attr("onmouseup", "selected='yay';");
let elem = document.getElementById("svg1");
elem.appendChild(g);
this.rep = el;
this.i = new pin(0);
this.val = false;
//addevt(this);
$((this.rep)).attr("stroke-width", 4);
this.activation = function() {
return this.i.val;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("cx"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("cy"));
}
//design
this.init = function() {
return 0;
}
this.update = function() {
if (this.i.val != "yay") {
if ((this.activation())) {
$((this.rep)).attr("fill", "yellow");
} else {
$((this.rep)).attr("fill", "white");
}
}
let x = this.x();
let y = this.y();
this.i.set(x - 60, y - 10);
}
pieces[pieces.length] = this;
addclk_light(this);
}
function addclk_light(ob) {
$(ob.rep).on("mousedown touchstart", function(e) {
e.preventDefault();
let svg = document.getElementById("svg1");
$(svg).append(ob.rep);
$(svg).append($(ob.i.rep));
selected = ob.id;
});
}

View file

@ -1,273 +0,0 @@
//the absolute start, adding the buttons
//adding the button for the modal
//in main.js i have some basic functions like adding rectangles
//and drag and drop
//====================first adding the modal=================================
//order is used to generate new id's for components
let order = 0;
//piece would contain all the components
//(without pins and edges)
//we will use that array for updating the value and the position
let pieces = [];
//this 'modal' letiable is used to keep the blue box
//basically when you click on it the modal for adding components will pop
let modal = add(40, 40, "blue", "black", "butt", false);
//adding the event for clicking
$(modal).on("mousedown touchstart", function() {
//showing the modal
//actually 'modal' is just the button
//and 'addel' is the true id of the modal from DOM
$("#addel").modal("show");
//being sure we wont move the components
moveing = false;
//activatng the drag and drop for the blue box
selected = "butt";
});
//the event for finishing the drag and drop on the blue box
$(modal).on("mouseup touchend", function() {
//telling that we dont want to drag it anymore
selected = "yay";
});
//changing the positon of the blue box
//we dont want it to be in the left top corner at 0 0
$(modal).attr("y", "500");
$(modal).attr("x", "500");
//let desc = new text(modal,"+")
//used for actually getting the new ids
function getname() {
//getting the 'order'
//than making it bigger by 1
//convert it to string
//than add 'piece' to it for avoiding confusion between pieces and pins
return ((order++).toString() + "piece")
}
//the function that fires when you tap 'add'
function addel() {
for (let i = 0; i < parseFloat($("#times").val()); i++) {
let added = eval("new " + $("#sel option:selected").attr("value") + "(getname())");
}
}
//================================preventing deafult actions==========================
//nothing to say here
$("img,rect,circle,p,foreignObject").on("mousedown touchstart", function(e) {
e.preventDefault();
});
$("*").on("mouseup touchend", function(e) {
e.preventDefault();
moveing = false;
});
$("img").on("click touchstart", function(e) {
e.preventDefault();
});
//===============================letiables============================================
//setting the drag and drop to our value
//'yay' means 'nothing selected'
let selected = "yay";
//the first positios of the clicks
let firstx = 0;
let firsty = 0;
//the first position of an element dragged
let fx = 0;
let fy = 0;
//snap settings
let snap = false;
//=====================================some events===================================
//nothing to say here...
//just some basic things
$("body").on("mousemove touchmove", function(e) {
//calling the drag function
drag(e, selected);
});
$("body").on("mouseup touchend", function(e) {
selected = "yay";
});
$("body").on("mousedown touchstart", function(e) {
//beeing sure that we actually want to drag something
if (selected != "yay") {
//setting our first mouse poitions
firstx = e.pageX * zoomx / window.innerWidth;
firsty = e.pageY * zoomy / window.innerHeight;
firstx += xvb;
firsty += yvb;
//conveerting the id to an actual thing
name = "#" + selected;
//beeing sure we get the corect attributes
//circle have 'cx' and 'cy'
//and rectangles have 'x' and 'y'
if ($(name).attr("class") == "light") {
fx = parseFloat($(name).attr("cx"));
fy = parseFloat($(name).attr("cy"));
} else {
fx = parseFloat($(name).attr("x"));
fy = parseFloat($(name).attr("y"));
}
}
});
//======================================funcctions for actual draging===========================
//thefunction that tranfers the data from the event to our set_position function
function drag(e, selected) {
//the name
let name = "#" + selected;
//the positions
let x = e.pageX;
let y = e.pageY;
x *= zoomx / window.innerWidth;
y *= zoomy / window.innerHeight;
//updating positions
set_position(name, x, y);
}
//our main place to change things
function set_position(name, x, y) {
let obj, objx, objy;
obj = "#" + selected;
x = parseFloat(x);
y = parseFloat(y);
x += xvb;
y += yvb;
if ($(name).attr("class") != "light") {
//getting the letiables
obj = "#" + selected;
objx = parseFloat($(obj).attr("x"));
objy = parseFloat($(obj).attr("y"));
x = parseFloat(x);
y = parseFloat(y);
xdif = fx - firstx;
ydif = fy - firsty;
x += xdif;
y += ydif;
//x -= parseFloat($(obj).attr("width"));
//y -= parseFloat($(obj).attr("height"));
if (snap) {
x = Math.floor(x / 80) * 80;
y = Math.floor(y / 80) * 80;
}
//setting the new positions
$(obj).attr("x", (x).toString());
$(obj).attr("y", (y).toString());
} else {
//for circles
//getting the letiables
obj = "#" + selected;
objx = parseFloat($(obj).attr("cx"));
objy = parseFloat($(obj).attr("cy"));
x = parseFloat(x);
y = parseFloat(y);
xdif = fx - firstx;
ydif = fy - firsty;
x += xdif;
y += ydif;
//x -= parseFloat($(obj).attr("width"));
//y -= parseFloat($(obj).attr("height"));
if (snap) {
x = Math.floor(x / 80) * 80 + 40;
y = Math.floor(y / 80) * 80 + 40;
}
//setting the new positions
$(obj).attr("cx", (x).toString());
$(obj).attr("cy", (y).toString());
}
}
function add(h, w, color, stroke, id, on) {
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let el = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect')).attr({
x: "50",
y: "50",
id: id,
fill: color,
height: h.toString() + "px",
width: w.toString() + "px",
stroke: stroke,
rx: "20",
ry: "20"
});
if (on) {
$(el).attr("onmousedown", "selected='" + id + "';");
$(el).attr("onmouseup", "selected='yay';");
}
$(el).attr("stroke-width", "4");
$(g).append(el);
let elem = document.getElementById("svg1");
elem.appendChild(g);
return el;
}
setInterval(function() {
for (let i = 0; i < pieces.length; i++) {
pieces[i].update();
}
for (let i = 0; i < lines.length; i++) {
lines[i].update();
}
for (let i = 0; i < pins.length; i++) {
pins[i].update();
}
}, 0.001);
//objects are made in other files

View file

@ -1,73 +0,0 @@
function nand(id) {
this.id = id;
this.name = "#" + this.id;
this.rep = add(80, 80, "green", "black", this.id, true);
this.pin1 = new pin(0);
this.pin2 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
//this.text = new text(this,"Or-gate");
this.activation = function() {
if (!(this.pin1.val && this.pin2.val)) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "80",
width: "80",
src: "textures/gates/nand_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y);
this.pin2.set(x - 20, y + 60);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
addclk(this);
}

View file

@ -1,91 +0,0 @@
function nand3(id) {
this.id = id;
this.rep = add(80, 80, "blue", "black", this.id, false);
this.pin1 = new pin(0);
this.pin2 = new pin(0);
this.pin3 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
this.activation = function() {
if (!((this.pin1.val && this.pin2.val) && this.pin3.val)) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "72",
width: "72",
src: "textures/gates/nand_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y);
this.pin2.set(x - 20, y + 60);
this.pin3.set(x - 20, y + 30);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
clk(this, [this.pin1, this.pin2, this.pin3, this.o]);
}
function clk(ob, arr) {
$((ob.img)).on("mousedown touchstart", function(e) {
e.preventDefault();
let svg = document.getElementById("svg1");
$(svg).append(ob.rep);
$(svg).append($(ob.pin1.rep));
for (let i = 0; i < arr.length; i++) {
$(svg).append($(i.rep));
}
$(svg).append($(ob.skin));
selected = ob.id;
});
$((ob.img)).on("mouseup touchend", function() {
selected = "yay";
});
}

View file

@ -1,86 +0,0 @@
function not(id) {
this.id = id;
this.name = "#" + this.id;
this.rep = add(80, 80, "green", "black", this.id, true);
this.pin1 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
//this.text = new text(this,"Or-gate");
this.activation = function() {
if (!this.pin1.val) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "80",
width: "80",
src: "textures/gates/not_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y + 30);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
addclk11(this);
}
function addclk11(ob) {
$((ob.img)).on("mousedown touchstart", function(e) {
e.preventDefault();
let svg = document.getElementById("svg1");
$(svg).append(ob.rep);
$(svg).append($(ob.pin1.rep));
$(svg).append($(ob.o.rep));
$(svg).append($(ob.skin));
selected = ob.id;
});
$((ob.img)).on("mouseup touchend", function() {
selected = "yay";
});
}

View file

@ -1,73 +0,0 @@
function or(id) {
this.id = id;
this.name = "#" + this.id;
this.rep = add(80, 80, "green", "black", this.id, true);
this.pin1 = new pin(0);
this.pin2 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
//this.text = new text(this,"Or-gate");
this.activation = function() {
if (this.pin1.val || this.pin2.val) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "80",
width: "80",
src: "textures/gates/or_gate.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y);
this.pin2.set(x - 20, y + 60);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
addclk(this);
}

View file

@ -1,65 +0,0 @@
let count = 0;
let pins = [];
let sels = "yay";
let sele = "yay";
let num;
function pin(type) {
this.state = true;
this.nei = "yay";
this.val = false;
if (type == 0) {
this.type = true;
color = "black";
} else {
this.type = false;
color = "red";
}
this.update = function() {
if (this.nei != "yay") {
if (!(this.type)) {
this.val = this.nei.activation();
} else {
this.val = this.nei.val;
}
}
if (this.val) {
$((this.rep)).attr("fill", "red");
} else {
$((this.rep)).attr("fill", "yellow");
}
}
this.id = count.toString();
this.name = "#" + this.id;
this.rep = add(20, 20, "yellow", "black", this.id, false);
clicked(this);
count++;
this.name = "#" + this.id;
this.set = function(x, y) {
$(this.name).attr("x", x.toString());
$(this.name).attr("y", y.toString());
}
pins[pins.length] = this;
this.num = pins.length - 1;
}
function clicked(ob) {
$(ob.rep).on("click touchstart", function(e) {
e.preventDefault();
if (ob.type == true) {
sels = ob;
} else {
sele = ob;
}
if ((sels != "yay") && (sele != "yay")) {
if ((sels.state)) {
sels.nei = sele;
sels.state = false;
sele.state = false;
a = new edge(sels, sele);
sels = "yay";
sele = "yay";
}
}
});
}

View file

@ -1,42 +0,0 @@
let t_count = 0;
function text(parent, value) {
//letiables
this.parent = parent;
this.val = value;
this.id = t_count.toString() + "text";
t_count++;
this.name = "#" + this.id;
//adding the text to the SVG
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let el = $(document.createElementNS('http://www.w3.org/2000/svg', 'text')).attr({
x: "50",
y: "50",
id: this.id,
fill: "white",
stroke: "black"
});
$(el).text(value);
$(el).attr("class", "heavy");
$(g).append(el);
let elem = document.getElementById("svg1");
elem.appendChild(g);
this.rep = el;
$((this.rep)).on("click touchstart", function(e) {
e.preventDefault();
});
//updating
this.update = function() {
$((this.name)).attr("x", (parseFloat($((this.parent)).attr("x")) + 15).toString());
$((this.name)).attr("y", (parseFloat($((this.parent)).attr("y")) + 25).toString());
}
//beeing sure it would be updated
pieces[pieces.length] = this;
}

View file

@ -1,72 +0,0 @@
function xor(id) {
this.id = id;
this.name = "#" + this.id;
this.rep = add(80, 80, "green", "black", this.id, true);
this.pin1 = new pin(0);
this.pin2 = new pin(0);
this.o = new pin(1);
this.o.nei = this;
//this.text = new text(this,"Or-gate");
this.activation = function() {
if (!(this.pin1.val && this.pin2.val) && (this.pin1.val || this.pin2.val)) {
return true;
}
return false;
}
this.x = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("x"));
}
this.y = function() {
let name = "#" + this.id;
return parseFloat($(name).attr("y"));
}
//design
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
$(g).attr({
width: "100%",
height: "100%"
});
let skin = $(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')).attr({
x: "50",
y: "50",
id: (id + "-skin"),
width: "80",
height: "80"
});
let img = $(document.createElement('img')).attr({
height: "80",
width: "80",
src: "textures/gates/xor.jpg"
});
let iDiv = document.createElement("div");
$(iDiv).append(img);
this.skin = skin;
this.img = img;
$(skin).append(iDiv);
//noname(this);
$(g).append(skin);
let elem = document.getElementById("svg1");
elem.appendChild(g);
//updating
this.update = function() {
//the main object and his pins
let x = this.x();
let y = this.y();
this.pin1.set(x - 20, y);
this.pin2.set(x - 20, y + 60);
this.o.set(x + 80, y + 30);
//and the skin
let name = "#" + this.id + "-skin";
let skin = $(name);
skin.attr("x", (parseFloat($((this.rep)).attr("x")) + 4).toString());
skin.attr("y", (parseFloat($((this.rep)).attr("y")) + 4).toString());
}
pieces[pieces.length] = this;
clk(this, [this.pin1, this.pin2, this.output]);
}

7
src/assets/4decoder.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#DB00FF"/>
<path d="M386.932 308H517L448.214 400L517 492H386.932C386.932 492 306.265 492 305.014 400C303.763 308 386.932 308 386.932 308Z" stroke="white" stroke-width="10"/>
<path d="M555.089 435.379H477M555.089 365.621H477H555.089ZM572 333H493.911H572ZM572 468H493.911H572Z" stroke="white" stroke-width="10"/>
<path d="M555.089 435.379H477M555.089 365.621H477H555.089ZM572 333H493.911H572ZM572 468H493.911H572Z" stroke="white" stroke-width="10"/>
<path d="M555 435.379H476.5M306.5 396.234H228H306.5ZM572 333H493.5H572ZM572 468H493.5H572Z" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 718 B

7
src/assets/4encoder.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#DB00FF"/>
<path d="M424.682 308H294L363.11 400L294 492H424.682C424.682 492 505.729 492 506.986 400C508.243 308 424.682 308 424.682 308Z" stroke="white" stroke-width="10"/>
<path d="M256 365.5H334.5M239 333H317.5M239 467.5H317.5M256 435H334.5" stroke="white" stroke-width="10"/>
<path d="M256 365.5H334.5M239 333H317.5M239 467.5H317.5M256 435H334.5" stroke="white" stroke-width="10"/>
<path d="M504.5 396H583M239 333H317.5M239 467.5H317.5M256 435H334.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 633 B

7
src/assets/and.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#0277BD"/>
<path d="M412.471 285H290.034V515H412.471C536.599 515 540.406 285 412.471 285Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M510 401L569 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M231 471H290" stroke="#EEEEEE" stroke-width="10"/>
<path d="M230 323H289" stroke="#EEEEEE" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 468 B

10
src/assets/comparator.svg Normal file
View file

@ -0,0 +1,10 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#00B512"/>
<path d="M314 438H336.331M383.435 377L356.045 438H336.331M383.435 377H411M383.435 377H364.768L336.331 438" stroke="white" stroke-width="10"/>
<path d="M288 519.5V281L512 401L288 519.5Z" stroke="white" stroke-width="10"/>
<path d="M512.5 403H584.5" stroke="white" stroke-width="10"/>
<path d="M429 356H501" stroke="white" stroke-width="10"/>
<path d="M429 446H501" stroke="white" stroke-width="10"/>
<path d="M216 316H288" stroke="white" stroke-width="10"/>
<path d="M216 482H288" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 670 B

11
src/assets/full-adder.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#00B512"/>
<path d="M398 295L396.522 504C322.104 504 289 469.556 289 401.327C289 333.097 324.731 295 398 295Z" fill="white" stroke="white" stroke-width="10"/>
<path d="M400 295L401.142 504C458.653 504 511 471.73 511 403.5C511 335.27 456.624 295 400 295Z" fill="white" stroke="white" stroke-width="10"/>
<path d="M416 333H386V390H315V416.5H386V466H416V416.5H481.5V390H416V333Z" fill="#00B512" stroke="white" stroke-width="10"/>
<path d="M482 466H568.5" stroke="white" stroke-width="10"/>
<path d="M482 333H568.5" stroke="white" stroke-width="10"/>
<path d="M228 333H314.5" stroke="white" stroke-width="10"/>
<path d="M202 400H288.5" stroke="white" stroke-width="10"/>
<path d="M227 464H313.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

10
src/assets/half-adder.svg Normal file
View file

@ -0,0 +1,10 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#00B512"/>
<path d="M398 295L396.522 504C322.104 504 289 469.556 289 401.327C289 333.097 324.731 295 398 295Z" fill="white" stroke="white" stroke-width="10"/>
<path d="M400 295L401.142 504C458.653 504 511 471.73 511 403.5C511 335.27 456.624 295 400 295Z" stroke="white" stroke-width="10"/>
<path d="M416 333H386V390H315V416.5H386V466H416V416.5H481.5V390H416V333Z" fill="#00B512" stroke="white" stroke-width="10"/>
<path d="M482 464H568.5" stroke="white" stroke-width="10"/>
<path d="M482 333H568.5" stroke="white" stroke-width="10"/>
<path d="M228 333H314.5" stroke="white" stroke-width="10"/>
<path d="M227 464H313.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 798 B

22
src/assets/ic.svg Normal file
View file

@ -0,0 +1,22 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#211E33"/>
<rect x="330" y="331" width="140" height="140" fill="#211E33" stroke="white" stroke-width="10"/>
<path d="M360 365V439.5" stroke="white" stroke-width="10"/>
<path d="M440 377.5H360.5" stroke="white" stroke-width="10"/>
<path d="M439.5 426H360" stroke="white" stroke-width="10"/>
<path d="M400 364V438.5" stroke="white" stroke-width="10"/>
<path d="M441 365V439.5" stroke="white" stroke-width="10"/>
<path d="M466.5 331.5L507.208 288H564.5" stroke="white" stroke-width="10"/>
<path d="M466 464L506.708 507.5H564" stroke="white" stroke-width="10"/>
<path d="M333 335.5L292.292 292H235" stroke="white" stroke-width="10"/>
<path d="M333 468.5L292.292 512H235" stroke="white" stroke-width="10"/>
<path d="M467.5 361H558.5" stroke="white" stroke-width="10"/>
<path d="M466 402H557" stroke="white" stroke-width="10"/>
<path d="M464.5 443H555.5" stroke="white" stroke-width="10"/>
<path d="M240 360H331" stroke="white" stroke-width="10"/>
<path d="M239 401H330" stroke="white" stroke-width="10"/>
<path d="M237 442H328" stroke="white" stroke-width="10"/>
<path d="M329 401H358.5" stroke="white" stroke-width="10"/>
<path d="M329 401H358.5" stroke="white" stroke-width="10"/>
<path d="M438 402H467.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,9 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#00B512"/>
<circle cx="400" cy="400" r="100" fill="white"/>
<path d="M289 300H397V500H289L322.48 399.497L289 300Z" fill="white"/>
<path d="M370.643 370H358.571V395.714H330V407.669H358.571V430H370.643V407.669H397V395.714H370.643V370Z" fill="#00B512" stroke="white" stroke-width="5"/>
<path d="M449.643 370H437.571V395.714H409V407.669H437.571V430H449.643V407.669H476V395.714H449.643V370Z" fill="#00B512" stroke="white" stroke-width="5"/>
<path d="M500 400H576" stroke="white" stroke-width="10"/>
<path d="M225 400H300" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 695 B

7
src/assets/merger.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#DB00FF"/>
<path d="M352.11 400L283 492H312L386.5 400L312 308H283L352.11 400Z" fill="white"/>
<path d="M312 308H283L352.11 400L283 492H312M312 308H413.682C413.682 308 497.243 308 495.986 400C494.729 492 413.682 492 413.682 492H312M312 308L386.5 400L312 492" stroke="white" stroke-width="10"/>
<path d="M228 333H306.5M228 467.5H306.5" stroke="white" stroke-width="10"/>
<path d="M493.5 396H572M228 333H306.5M228 467.5H306.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 603 B

8
src/assets/nand.svg Normal file
View file

@ -0,0 +1,8 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#0277BD"/>
<path d="M412.471 285H290.034V515H412.471C536.599 515 540.406 285 412.471 285Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M510 401L583 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M231 471H290" stroke="#EEEEEE" stroke-width="10"/>
<path d="M230 323H289" stroke="#EEEEEE" stroke-width="10"/>
<path d="M538 400C538 409.587 531.484 416 525 416C518.516 416 512 409.587 512 400C512 390.413 518.516 384 525 384C531.484 384 538 390.413 538 400Z" fill="#0277BD" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 667 B

8
src/assets/nor.svg Normal file
View file

@ -0,0 +1,8 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#0277BD"/>
<path d="M400.437 286H278C278 286 331.5 302 329.5 401C327.5 500 278 516 278 516H400.437C524.565 516 528.372 286 400.437 286Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M495 401L576 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M261 471H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M261 323H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M529 400.5C529 406.908 523.031 413 514.5 413C505.969 413 500 406.908 500 400.5C500 394.092 505.969 388 514.5 388C523.031 388 529 394.092 529 400.5Z" fill="#0277BD" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 722 B

15
src/assets/not.svg Normal file
View file

@ -0,0 +1,15 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M800 1H0V801H800V1Z" fill="#0277BD"/>
<path d="M495 401L576 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M266 471H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M266 324L327 323" stroke="#EEEEEE" stroke-width="10"/>
<path d="M529 400.5C529 406.908 523.031 413 514.5 413C505.969 413 500 406.908 500 400.5C500 394.092 505.969 388 514.5 388C523.031 388 529 394.092 529 400.5Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M324.5 514.5V282L498 402L324.5 514.5Z" stroke="white" stroke-width="10"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="800" height="800" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 761 B

7
src/assets/or.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#0277BD"/>
<path d="M400.437 281H278C278 281 331.5 297 329.5 396C327.5 495 278 511 278 511H400.437C524.565 511 528.372 281 400.437 281Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M495 401L554 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M261 471H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M261 323H320" stroke="#EEEEEE" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

6
src/assets/parallel.svg Normal file
View file

@ -0,0 +1,6 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#FF7700"/>
<path d="M181 284H273.673L342.933 367H424.875L503.241 284H619" stroke="white" stroke-width="10"/>
<path d="M181 516H273.461L342.563 438H424.318L502.505 516H618" stroke="white" stroke-width="10"/>
<path d="M432.5 400C432.5 429.426 410.144 452.5 383.5 452.5C356.856 452.5 334.5 429.426 334.5 400C334.5 370.574 356.856 347.5 383.5 347.5C410.144 347.5 432.5 370.574 432.5 400Z" fill="#FF7700" stroke="white" stroke-width="15"/>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" fill="#FF7700"/>
<path d="M171 274H274.688L352.182 362.006H443.864L533 274V404" stroke="white" stroke-width="10"/>
<path d="M171 526H274.674L352.157 444.355H443.827L582 526V409" stroke="white" stroke-width="10"/>
<circle cx="398.5" cy="403.5" r="56" fill="#FF7700" stroke="white" stroke-width="15"/>
<path d="M457.693 407.304L629.998 411.373" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 516 B

7
src/assets/splitter.svg Normal file
View file

@ -0,0 +1,7 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="800" transform="translate(800 800) rotate(-180)" fill="#DB00FF"/>
<path d="M447.89 400L517 308H488L413.5 400L488 492H517L447.89 400Z" fill="white"/>
<path d="M488 492H517L447.89 400L517 308H488M488 492H386.318C386.318 492 302.757 492 304.014 400C305.271 308 386.318 308 386.318 308H488M488 492L413.5 400L488 308" stroke="white" stroke-width="10"/>
<path d="M572 467H493.5M572 332.5H493.5" stroke="white" stroke-width="10"/>
<path d="M306.5 404H228M572 467H493.5M572 332.5H493.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

9
src/assets/xnor.svg Normal file
View file

@ -0,0 +1,9 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#0277BD"/>
<path d="M400.437 286H278C278 286 331.5 302 329.5 401C327.5 500 278 516 278 516H400.437C524.565 516 528.372 286 400.437 286Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M495 401L576 400" stroke="#EEEEEE" stroke-width="10"/>
<path d="M235 471H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M235 323H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M529 400.5C529 406.908 523.031 413 514.5 413C505.969 413 500 406.908 500 400.5C500 394.092 505.969 388 514.5 388C523.031 388 529 394.092 529 400.5Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M255.5 282.5C324.548 359.386 327.672 417.167 255.5 515.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 824 B

8
src/assets/xor.svg Normal file
View file

@ -0,0 +1,8 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M800 0H0V800H800V0Z" fill="#0277BD"/>
<path d="M400.437 286H278C278 286 331.5 302 329.5 401C327.5 500 278 516 278 516H400.437C524.565 516 528.372 286 400.437 286Z" fill="#0277BD" stroke="white" stroke-width="10"/>
<path d="M495 401H551.5" stroke="#EEEEEE" stroke-width="10"/>
<path d="M235 471H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M235 323H320" stroke="#EEEEEE" stroke-width="10"/>
<path d="M255.5 282.5C324.548 359.386 327.672 417.167 255.5 515.5" stroke="white" stroke-width="10"/>
</svg>

After

Width:  |  Height:  |  Size: 614 B

View file

@ -0,0 +1,10 @@
import { Screen } from '../../../modules/screen/helpers/Screen'
/**
* Clears the used portion of the canvas
*
* @param ctx the context to clear
*/
export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
ctx.clearRect(0, 0, Screen.width, Screen.height)
}

View file

@ -0,0 +1,30 @@
import { vector2 } from '../../math/types/vector2'
/**
*
* @param ctx The context to draw on
* @param points an array of points to draw
* @param fill if true the polygon will be filled
* @param stroke if true the polygno will be stroked
*/
export const drawPolygon = (
ctx: CanvasRenderingContext2D,
points: vector2[],
fill = true,
stroke = false
) => {
ctx.beginPath()
for (const point of points) {
ctx.lineTo(...point)
}
ctx.closePath()
if (fill) {
ctx.fill()
}
if (stroke) {
ctx.stroke()
}
}

View file

@ -0,0 +1,34 @@
import { Transform } from '../../math/classes/Transform'
import { Shape } from '../../../modules/simulation/types/GateTemplate'
import { roundRect } from './drawRoundedSquare'
import { useTransform } from './useTransform'
/**
* Draws a square from a transform
*
* @param ctx The context to draw on
* @param transform -The transform to use
* @param shape - The shae object to use
*/
export const drawRotatedSquare = (
ctx: CanvasRenderingContext2D,
transform: Transform,
shape: Shape
) => {
ctx.save()
const relative = useTransform(ctx, transform)
roundRect(
ctx,
relative.x,
relative.y,
relative.width,
relative.height,
shape.radius ? shape.radius : 0
)
ctx.fill()
ctx.restore()
}

View file

@ -0,0 +1,35 @@
/**
*
* @param ctx The context to draw on
* @param x the x of the rect
* @param y the y of the rect
* @param width the width of the rect
* @param height the height of the rect
* @param radius the radius of the corners
*/
export function roundImage(
ctx: CanvasRenderingContext2D,
image: HTMLImageElement,
x: number = 0,
y: number = 0,
width: number = 100,
height: number = 100,
radius: number = 5
) {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius)
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
ctx.lineTo(x + radius, y + height)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
ctx.closePath()
ctx.save()
ctx.clip()
ctx.drawImage(image, x, y, width, height)
ctx.restore()
}

View file

@ -0,0 +1,23 @@
/**
* Credit: https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
*/
export function roundRect(
ctx: CanvasRenderingContext2D,
x: number = 0,
y: number = 0,
width: number = 100,
height: number = 100,
radius: number = 5
) {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius)
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
ctx.lineTo(x + radius, y + height)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
ctx.closePath()
}

View file

@ -0,0 +1,19 @@
import { Transform } from '../../math/classes/Transform'
import { multiply } from '../../../modules/vector2/helpers/basic'
/**
*
* @param ctx The context to use
* @param transform The transform to move relative to
*/
export const useTransform = (
ctx: CanvasRenderingContext2D,
{ position, rotation, scale }: Transform
) => {
ctx.translate(...position)
ctx.translate(scale[0] / 2, scale[1] / 2)
ctx.rotate(rotation)
return new Transform(multiply(scale, -0.5), scale, 0)
}

View file

@ -0,0 +1,15 @@
/**
* A type-safe querySelector function, which throws if the given element was not found
*
* @credit https://gitlab.com/wavedistrict/web-client/blob/master/src/common/dom/helpers/querySelector.ts
*/
export function querySelector<E extends Element>(
selector: string,
parent: Element = document.body
) {
const element = parent.querySelector(selector)
if (!element) {
throw `Could not find element with selector "${selector}"`
}
return element as E
}

View file

@ -0,0 +1,7 @@
/**
* Remoes al lduplicates from array
*
* @param array The array to remove duplicates from
*/
export const removeDuplicates = <T>(array: T[]): T[] =>
Array.from(new Set<T>(array).values())

View file

@ -0,0 +1,12 @@
/**
* Removes element from array efficiently
* Based on a gist by the creator of rollup.
*
* @param arr The array to remove the element from
* @param element The element to remove
*/
export const removeElement = <T>(arr: T[], element: T) => {
const index = arr.indexOf(element)
arr[index] = arr[arr.length - 1]
return arr.pop()
}

View file

@ -0,0 +1,6 @@
/**
* Transforms a set into an array
*
* @param set The set to convert
*/
export const setToArray = <T>(set: Set<T>) => Array.from(set.values())

View file

@ -0,0 +1,26 @@
/**
* Gets safe error stack from error
*
* @param error The error to get the safe erro stack from
*/
export const getSafeErrorStack = (error: any) => {
const errorString: string = error.toString()
const stackString: string = error.stack
if (stackString) {
const safeStackString =
stackString.replace(errorString + '\n', '') || stackString
const stackItems = safeStackString.split('\n')
const safeStackItems = stackItems
.map(item => item.replace(' at ', ''))
.filter(item => item !== '')
.map(item => ` at ${item}`)
const safeStack = safeStackItems.join('\n')
return `${errorString}\n${safeStack}`
}
return errorString
}

View file

@ -0,0 +1,11 @@
// Like Array.map but for records
export const mapRecord = <T extends keyof object, A, B>(
record: Record<T, A>,
mapper: (a: A) => B
): Record<T, B> =>
Object.fromEntries(
Object.entries(record).map(([key, value]: [T, A]) => [
key,
mapper(value)
])
)

View file

@ -0,0 +1 @@
export type ValueOf<T> = T[keyof T]

View file

@ -0,0 +1,7 @@
/**
* Makes the first char of a string uppercase
*
* @param name The string to convert
*/
export const firstCharUpperCase = (name: string) =>
`${name[0].toUpperCase()}${name.substr(1)}`

View file

@ -0,0 +1,17 @@
export class Lazy<T> {
private value: T | null = null
/**
* SImple encoding of lazy values
* @param getter a function o get the lazy value
*/
public constructor(private getter: () => T) {}
public get(value) {
if (this.value === null) {
this.value = this.getter()
}
return this.value
}
}

View file

@ -0,0 +1,177 @@
import { allCombinations } from '../../../modules/simulation/helpers/allCombinations'
import { BehaviorSubject } from 'rxjs'
import { vector2 } from '../types/vector2'
import { vector4 } from '../types/vector4'
export class Transform {
/**
* Gets the position as a subject
*/
public positionSubject = new BehaviorSubject<vector2>([0, 0])
/**
* Class used to represend the position scale and rotation
* of a body in 2d space
*
* @param _position The initial position
* @param scale The initial scale
* @param rotation The initial scale of the object
*/
public constructor(
public _position: vector2 = [0, 0],
public scale: vector2 = [1, 1],
public rotation = 0
) {
this.updatePositionSubject()
}
/**
* Gets the boundng box of the transform
*/
public getBoundingBox() {
const result = [...this.position, ...this.scale] as vector4
return result
}
/**
* Gets an array of all points in the transform
*/
public getPoints() {
const combinations = Array.from(allCombinations([0, 1], [0, 1]))
// those are not in the right order
const points = combinations.map(combination => [
this.x + this.height * combination[0],
this.y + this.width * combination[1]
])
return points as vector2[]
}
/**
* Pushes the current position trough the position subject
*/
private updatePositionSubject() {
this.positionSubject.next(this.position)
}
/**
* getter for the position
*/
get position() {
return this._position
}
/**
* setter for the position
*/
set position(value: vector2) {
this._position = value
this.updatePositionSubject()
}
/**
* The first element of the position vector
*/
get x() {
return this.position[0]
}
/**
* The second element of the position vector
*/
get y() {
return this.position[1]
}
/**
* The first element of the scale vector
*/
get width() {
return this.scale[0]
}
/**
* The second element of the scale vector
*/
get height() {
return this.scale[1]
}
/**
* The minimum x position of the buonding box
*/
get minX() {
return Math.min(this.x, this.x + this.width)
}
/**
* The maximum x position of the bounding box
*/
get maxX() {
return Math.max(this.x, this.x + this.width)
}
/**
* The minimum y position of the buonding box
*/
get minY() {
return Math.min(this.y, this.y + this.height)
}
/**
* The maximum y position of the buonding box
*/
get maxY() {
return Math.max(this.y, this.y + this.height)
}
/**
* The center of the bounding box
*/
get center() {
return [this.x + this.width / 2, this.y + this.height / 2] as vector2
}
/**
* Sets the first element of the position vector
*
* @param value The value to set x to
*/
set x(value: number) {
this.position = [value, this.y]
this.updatePositionSubject()
}
/**
* Sets the second element of the position vector
*
* @param value The value to set y to
*/
set y(value: number) {
this.position = [this.x, value]
this.updatePositionSubject()
}
/**
* Sets the first element of the scale vector
*
* @param value The value to set the width to
*/
set width(value: number) {
this.scale = [value, this.height]
}
/**
* Sets the second element of the scale vector
*
* @param value The value to set the height to
*/
set height(value: number) {
this.scale = [this.width, value]
}
}

View file

@ -0,0 +1,10 @@
import { vector2 } from '../types/vector2'
import { length, relativeTo } from '../../../modules/vector2/helpers/basic'
export const pointInCircle = (
point: vector2,
center: vector2,
radius: number
) => {
return length(relativeTo(point, center)) < radius
}

View file

@ -0,0 +1,11 @@
import { Transform } from '../classes/Transform'
import { vector2 } from '../types/vector2'
export const pointInSquare = (point: vector2, square: Transform) => {
return (
point[0] >= square.minX &&
point[0] <= square.maxX &&
point[1] >= square.minY &&
point[1] <= square.maxY
)
}

View file

@ -0,0 +1 @@
export type vector2 = [number, number]

View file

@ -0,0 +1 @@
export type vector3 = [number, number, number]

View file

@ -0,0 +1 @@
export type vector4 = [number, number, number, number]

View file

@ -0,0 +1,10 @@
export type vector8 = [
number,
number,
number,
number,
number,
number,
number,
number
]

46
src/index.ts Normal file
View file

@ -0,0 +1,46 @@
import { Splash } from './modules/splash/classes/Splash'
/**
* The function wich is run when the app is loaded
*/
async function main() {
// Create splash screen variable
let splash: Splash | undefined = undefined
try {
// instantiate splash screen
splash = new Splash()
} catch {}
try {
// import main app
const app = await import('./main')
// wait for app to start
await app.start()
} catch (error) {
// show the error to the client
if (splash) splash.setError(error)
// log the error to the console
console.error(error.stack || error)
return
}
// hide splash screen if it exists
if (splash) {
splash.fade()
}
}
if (process.env.BASEURL === 'development') {
new EventSource('/esbuild').addEventListener('change', () =>
location.reload()
)
}
// Call entry
main().catch((error) => {
// if the error handling error has an error, log that error
console.error('Error loading app', error)
})

52
src/main.tsx Normal file
View file

@ -0,0 +1,52 @@
import App from './modules/core/components/App'
import { render } from 'react-dom'
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, 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'
import { initAutoSave } from './modules/simulation-actions/helpers/initAutoSave'
/**
* The function wich does the heavy lifting for starting the app
*/
export const start = async () => {
// This will resolve at the first render
const result = loadSubject
.pipe(
filter((a) => a),
take(1)
)
.toPromise()
// Handle possible errors
handleErrors()
// Create main renderer for the app
initRenderer()
// Register key bindings
initKeyBindings()
// Update base templates
initBaseTemplates()
// Easter egg
logWelcome()
// Update the logic gates in local storage
updateLogicGateList()
// start the autosaving stuff
initAutoSave()
// Render app component
render(<App />, document.getElementById('app'))
// wait for the first render
await result
}

View file

@ -0,0 +1,72 @@
import { Subject } from 'rxjs'
import { filter, take } from 'rxjs/operators'
/**
* Keeps track of what a task should do and where the output should be delivered
*/
export interface Task<T> {
output: Subject<T>
execute: () => Promise<T>
}
/**
* Used to execute a number of async tasks
*/
export class ExecutionQueue<T> {
/**
* An array of all the tasks wich need to be executed
*/
private tasks: Task<T>[] = []
/**
* Keeps track of the current task
*/
private current: Promise<T> | null
/**
* Whether the tasks should continue being executed
*/
public active = true
/**
* Adds a new task to the queue
*
* @param task The task to add
*/
public push(task: () => Promise<T>) {
const executionSubject = new Subject<T>()
const executionPromise = executionSubject.pipe(take(1)).toPromise()
this.tasks.push({
output: executionSubject,
execute: task
})
if (!this.current) {
this.next()
}
return executionPromise
}
/**
* Executes the next task in the queue
*/
private next() {
const task = this.tasks.shift()
if (task) {
this.current = task.execute()
this.current.then((result) => {
task.output.next(result)
if (this.active) {
this.next()
}
})
} else {
this.current = null
}
}
}

View file

@ -0,0 +1,16 @@
/**
* Transforms js code into a function
*
* @param source tThe js code
* @param args The name of arguments to pass to the function
*/
export const toFunction = <T extends unknown[]>(
source: string,
...args: string[]
): ((...args: T) => void) => {
const raw = `return (${args.join(',')}) => {
${source}
}`
return new Function(raw)()
}

View file

@ -0,0 +1,29 @@
import { SimulationEnv } from '../../simulation/classes/Simulation'
import { PinState } from '../../simulation/classes/Pin'
export interface Context {
getProperty: (name: string) => unknown
setProperty: (name: string, value: unknown) => void
get: (index: number) => PinState
set: (index: number, state: PinState) => void
getOutput: (index: number) => PinState
getBinary: (index: number) => number
printBinary: (value: number, bits?: number) => string
printHex: (value: number, length?: number) => string
setBinary: (index: number, value: number, bits?: number) => void
getOutputBinary: (index: number) => number
displayBinary: (value: number) => void
invertBinary: (value: number) => number
color: (color: string) => void
innerText: (value: string) => void
update: () => void
toLength: (value: number | PinState, length: number) => string
maxLength: number
enviroment: SimulationEnv
colors: Record<string, string>
memory: Record<string, unknown>
}
export interface InitialisationContext {
memory: Record<string, unknown>
}

View file

@ -0,0 +1,30 @@
/**
* Gets the 3 chunks frmo a hex color string
*
* @param color The color string
*/
export const fromHexColorString = (color: string) => {
const actual = color.slice(1)
const chunks = [
actual.substr(0, 2),
actual.substr(2, 2),
actual.substr(4, 2)
]
const numbers = chunks.map(chunk => parseInt(chunk, 16))
return numbers
}
/**
* Rebuilds a color from its chunks
*/
export const fromChunks = (chunks: number[]) => {
return `#${chunks.reduce((acc, curr) => {
const stringified = curr.toString(16)
return (
acc + (stringified.length === 1 ? `0${stringified}` : stringified)
)
}, '')}`
}

View file

@ -0,0 +1,20 @@
@import '../styles/global-styles/global-styles.scss';
@import '../../core/styles/mixins/full-screen.scss';
@import '../styles/colors.scss';
html,
body {
height: 100%;
width: 100%;
display: block;
overflow: hidden;
}
.page {
@include page-width();
background-color: $bg;
overflow-y: auto;
overflow-x: hidden;
color: white;
}

View file

@ -0,0 +1,52 @@
import '../styles/reset.scss'
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, Route } from 'react-router-dom'
import { useEffect } from 'react'
import CssBaseline from '@material-ui/core/CssBaseline'
import Theme from '@material-ui/styles/ThemeProvider'
import Sidebar from './Sidebar'
import Root from './Root'
import LogicGatePage from '../../logic-gates/components/LogicGatesPage'
import { loadSubject } from '../subjects/loadedSubject'
import LogicGateInfoPage from '../../logic-gate-info/components/LogicGateInfoPage'
const App = () => {
useEffect(() => {
loadSubject.next(true)
})
return (
<>
<CssBaseline />
<Theme theme={muiTheme}>
<BrowserRouter basename={process.env.BASEURL}>
<Sidebar />
<Route path="/" component={Root} exact />
<Route path="/gates" component={LogicGatePage} />
<Route path="/info/:name" component={LogicGateInfoPage} />
</BrowserRouter>
</Theme>
<ToastContainer
position="top-left"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
draggable
pauseOnHover
/>
</>
)
}
export default App

View file

@ -0,0 +1,45 @@
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, Route } from 'react-router-dom'
const linkButton = (to: string, text: string, contained = true) => {
const a = 'arrow_back_ios'
return (
<Link to={to}>
<ListItem button className={contained ? 'contained' : ''}>
<ListItemIcon>
<Icon>
{contained ? 'device_hub' : 'keyboard_arrow_left'}
</Icon>
</ListItemIcon>
<ListItemText>{text}</ListItemText>
</ListItem>
</Link>
)
}
const BackToSimulation = () => {
const translation = useTranslation()
return (
<>
{linkButton('/', translation.sidebar.backToSimulation)}
<Route
path="/info/:name"
component={() => {
return linkButton(
'/gates',
translation.sidebar.backToGates,
false
)
}}
/>
</>
)
}
export default BackToSimulation

View file

@ -0,0 +1,53 @@
import React, { Component, createRef, RefObject } from 'react'
import FluidCanvas from './FluidCanvas'
import loop from 'mainloop.js'
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
class Canvas extends Component {
private canvasRef: RefObject<HTMLCanvasElement> = createRef()
private renderingContext: CanvasRenderingContext2D | null
public constructor(props: {}) {
super(props)
loop.setDraw(() => {
if (this.renderingContext) {
renderSimulation(this.renderingContext, getRendererSafely())
}
})
}
public componentDidMount() {
if (this.canvasRef.current) {
this.renderingContext = this.canvasRef.current.getContext('2d')
if (this.renderingContext) {
this.renderingContext.textAlign = 'center'
}
getRendererSafely().updateWheelListener(this.canvasRef)
}
loop.start()
}
public componentWillUnmount() {
loop.stop()
}
public render() {
const renderer = getRendererSafely()
return (
<FluidCanvas
ref={this.canvasRef}
mouseDownOuput={renderer.mouseDownOutput}
mouseUpOutput={renderer.mouseUpOutput}
mouseMoveOutput={renderer.mouseMoveOutput}
/>
)
}
}
export default Canvas

View file

@ -0,0 +1,32 @@
import React from 'react'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Icon from '@material-ui/core/Icon'
import { handleCreating } from '../../create-simulation/helpers/handleCreating'
import { useTranslation } from '../../internalisation/helpers/useLanguage'
/**
* The component for the 'Create simulation' button from the top of the sidebar.
*
* The only way i found to apply a different color to the ListItem button was
* by using !important in the scss.
*/
const CreateSimulationButton = () => {
const translation = useTranslation()
return (
<ListItem
button
className="contained create-simulation"
onClick={handleCreating}
>
<ListItemIcon>
<Icon>note_add</Icon>
</ListItemIcon>
<ListItemText>{translation.sidebar.createSimulation}</ListItemText>
</ListItem>
)
}
export default CreateSimulationButton

View file

@ -0,0 +1,39 @@
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
import { useObservable } from 'rxjs-hooks'
import { Subject } from 'rxjs'
import { MouseEventInfo } from '../types/MouseEventInfo'
import { width, height } from '../../screen/helpers/Screen'
import { getEventInfo } from '../helpers/getEventInfo'
export interface FluidCanvasProps {
mouseDownOuput: Subject<MouseEventInfo>
mouseUpOutput: Subject<MouseEventInfo>
mouseMoveOutput: Subject<MouseEventInfo>
}
export const mouseEventHandler = (output: Subject<MouseEventInfo>) => (
e: MouseEvent<HTMLCanvasElement>
) => {
output.next(getEventInfo(e))
}
const FluidCanvas = forwardRef(
(props: FluidCanvasProps, ref: RefObject<HTMLCanvasElement>) => {
const currentWidth = useObservable(() => width, 0)
const currentHeight = useObservable(() => height, 0)
return (
<canvas
className="page"
ref={ref}
width={currentWidth}
height={currentHeight}
onMouseDown={mouseEventHandler(props.mouseDownOuput)}
onMouseUp={mouseEventHandler(props.mouseUpOutput)}
onMouseMove={mouseEventHandler(props.mouseMoveOutput)}
/>
)
}
)
export default FluidCanvas

View file

@ -0,0 +1,5 @@
@import '../styles/colors.scss';
#language-button {
border: 1px solid white;
}

View file

@ -0,0 +1,31 @@
import React from 'react'
import Icon from '@material-ui/core/Icon'
import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemIconText from '@material-ui/core/ListItemText'
import { useTranslation } from '../../internalisation/helpers/useLanguage'
import { nextLanguage } from '../../internalisation/helpers/nextLanguage'
import './Language.scss'
/**
* The language component from the sidebar
*/
const Language = () => {
const translation = useTranslation()
return (
<List>
<ListItem button onClick={nextLanguage} id="language-button">
<ListItemIcon>
<Icon>language</Icon>
</ListItemIcon>
<ListItemIconText>
{translation.sidebar.language}: {translation.language}
</ListItemIconText>
</ListItem>
</List>
)
}
export default Language

View file

@ -0,0 +1,35 @@
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 { updateLogicGateList } from '../../logic-gates/subjects/LogicGateList'
import { Link } from 'react-router-dom'
/**
* Component wich contains the sidebar 'Open simulation' button
*
* @throws SimulationError if the data about a simulation cant be found in localStorage
*/
const LogicGates = () => {
const translation = useTranslation()
return (
<Link to="/gates">
<ListItem
button
onClick={() => {
updateLogicGateList()
}}
>
<ListItemIcon>
<Icon>memory</Icon>
</ListItemIcon>
<ListItemText>{translation.sidebar.logicGates}</ListItemText>
</ListItem>
</Link>
)
}
export default LogicGates

View file

@ -0,0 +1,122 @@
import React, { useState } from 'react'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import Icon from '@material-ui/core/Icon'
import Typography from '@material-ui/core/Typography'
import { saveStore } from '../../saving/stores/saveStore'
import { BehaviorSubject } from 'rxjs'
import { useObservable } from 'rxjs-hooks'
import { switchTo } from '../../saving/helpers/switchTo'
import { SimulationError } from '../../errors/classes/SimulationError'
import { icons } from '../constants'
import { useTranslation } from '../../internalisation/helpers/useLanguage'
import { getTemplateSafely } from '../../logic-gates/helpers/getTemplateSafely'
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
/**
* Returns a list with the names of all saved simulations
*/
const allSimulations = () => {
return saveStore.ls()
}
/**
* Subject to make React update the dom when new simulations are stored
*/
const allSimulationSubject = new BehaviorSubject<string[]>([])
/**
* Triggers a dom update by pushing a new value to the
* useObservable hook inside the React component.
*
* It also has the side effect of sorting the simulation names.
*/
export const updateSimulationList = () => {
allSimulationSubject.next(allSimulations().sort())
}
/**
* Component wich contains the sidebar 'Open simulation' button
*
* @throws SimulationError if the data about a simulation cant be found in localStorage
*/
const OpenSimulation = () => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const simulations = useObservable(() => allSimulationSubject, [])
const translation = useTranslation()
const handleClose = () => {
setAnchorEl(null)
}
return (
<>
<ListItem
button
onClick={event => {
updateSimulationList()
setAnchorEl(event.currentTarget)
}}
>
<ListItemIcon>
<Icon>folder_open</Icon>
</ListItemIcon>
<ListItemText>
{translation.sidebar.openSimulation}
</ListItemText>
</ListItem>
<Menu
keepMounted
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
>
{simulations
.filter(
name =>
simulations.length < 2 ||
name !== getRendererSafely().simulation.name
)
.map((simulationName, index) => {
const simulationData = saveStore.get(simulationName)
if (!simulationData) {
throw new SimulationError(
`Cannot get data for simulation ${simulationName}`
)
}
return (
<MenuItem
key={index}
onClick={() => {
switchTo(simulationName)
handleClose()
}}
>
<ListItemIcon>
<Icon>
{
icons.simulationMode[
simulationData.simulation.mode
]
}
</Icon>
</ListItemIcon>
<Typography style={{ flexGrow: 1 }}>
{simulationName}
</Typography>
</MenuItem>
)
})}
</Menu>
</>
)
}
export default OpenSimulation

View file

@ -0,0 +1,18 @@
import React from 'react'
import Canvas from './Canvas'
import CreateSimulation from '../../create-simulation/components/CreateSimulation'
import Input from '../../input/components/Input'
import GateProperties from '../../logic-gates/components/GatePropertiesModal'
const Root = () => {
return (
<>
<Canvas />
<CreateSimulation />
<GateProperties />
<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

@ -0,0 +1,92 @@
import React from 'react'
import Drawer from '@material-ui/core/Drawer'
import List from '@material-ui/core/List'
import OpenSimulation from './OpenSimulation'
import CreateSimulationButton from './CreateSimulationButton'
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'
import { sidebarWidth } from '../constants'
/**
* The z-index of the sidebar.
*/
const sidebarZIndex = 5
/**
* The styles for the sidebar component
*/
const useStyles = makeStyles(
createStyles({
// This class is applied on the sidebar container
root: {
display: 'flex',
zIndex: sidebarZIndex
},
// This is the class of the actual sidebar
drawer: {
width: sidebarWidth,
zIndex: sidebarZIndex,
flexShrink: 0
},
// This is the class for the surface of the sidebar
drawerPaper: {
background: `#111111`,
padding: '4px',
width: sidebarWidth,
zIndex: sidebarZIndex
},
// This is the class for the main button list
list: {
flexGrow: 1
}
})
)
/**
* The sidebar component
*/
const Sidebar = () => {
const classes = useStyles()
const rootSidebarContent = () => {
return (
<>
<CreateSimulationButton />
<OpenSimulation />
<LogicGates />
<SimulationActions />
</>
)
}
return (
<div className={classes.root}>
<Drawer
className={classes.drawer}
variant={'persistent'}
anchor="right"
open={true}
classes={{
paper: classes.drawerPaper
}}
>
<List component="nav" className={classes.list}>
<Switch>
<Route path="/" exact component={rootSidebarContent} />
<Route path="*" component={BackToSimulation} />
</Switch>
</List>
<Language />
</Drawer>
</div>
)
}
export default Sidebar

Some files were not shown because too many files have changed in this diff Show more