curves for wires & importing logic gates
This commit is contained in:
parent
443d049db6
commit
b06a4c7441
7
.prettierrc.js
Normal file
7
.prettierrc.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
semi: false,
|
||||||
|
trailingComma: 'none',
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 80,
|
||||||
|
tabWidth: 4
|
||||||
|
}
|
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"eslint.enable": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"prettier.eslintIntegration": true,
|
||||||
|
"eslint.validate": ["typescript", "typescriptreact"]
|
||||||
|
}
|
2
README.txt
Normal file
2
README.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Pentru a rula aplicatia, deschideti dist/index.html.
|
||||||
|
Codul sursa se afla in folderul src.
|
42
deploy.ts
42
deploy.ts
|
@ -1,53 +1,47 @@
|
||||||
const { publish } = require("gh-pages")
|
const { publish } = require('gh-pages')
|
||||||
const { exec } = require("child_process")
|
const { exec } = require('child_process')
|
||||||
const { random } = require("random-emoji")
|
const { random } = require('random-emoji')
|
||||||
|
|
||||||
// const { publish } = require("gh-pages")
|
// const { publish } = require("gh-pages")
|
||||||
|
|
||||||
const args = process.argv.splice(2)
|
const args = process.argv.splice(2)
|
||||||
const randomEmoji = () => random({ count: 1 })[0].character
|
const randomEmoji = () => random({ count: 1 })[0].character
|
||||||
|
|
||||||
const mFlag = ((args.indexOf("--message") + 1) || (args.indexOf("-m") + 1)) - 1
|
const mFlag = (args.indexOf('--message') + 1 || args.indexOf('-m') + 1) - 1
|
||||||
const message = `${randomEmoji()} ${(mFlag >= 0) ? args[mFlag + 1] : "automated update"} ${randomEmoji()}`
|
const message = `${mFlag >= 0 ? args[mFlag + 1] : 'automated update'}`
|
||||||
|
|
||||||
console.log("Deploying...");
|
console.log('Deploying...')
|
||||||
|
|
||||||
const run = (command: string): Promise<string> => {
|
const run = (command: string): Promise<string> => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
console.log(`🏃 Running: '${command}'`)
|
console.log(`🏃 Running: '${command}'`)
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
exec(command, (err, stdout, stderr) => {
|
exec(command, (err, stdout, stderr) => {
|
||||||
if (err != null)
|
if (err != null) rej(err)
|
||||||
rej(err)
|
else if (typeof stderr != 'string') rej(new Error(stderr))
|
||||||
else if (typeof (stderr) != "string")
|
else res(stdout)
|
||||||
rej(new Error(stderr))
|
|
||||||
else
|
|
||||||
res(stdout)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
(async () => {
|
|
||||||
try {
|
try {
|
||||||
if (!args.includes("--skipBuild") && !args.includes("-sb"))
|
if (!args.includes('--skipBuild') && !args.includes('-sb'))
|
||||||
await run("npm run build")
|
await run('npm run build')
|
||||||
await run("git add .")
|
await run('git add .')
|
||||||
await run(`git commit -m " ${message} "`)
|
await run(`git commit -m " ${message} "`)
|
||||||
await run("git push origin master")
|
await run('git push origin master')
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
console.log("🏃 Updating github pages")
|
console.log('🏃 Updating github pages')
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
publish("dist", (err) => {
|
publish('dist', err => {
|
||||||
if (err)
|
if (err) rej(err)
|
||||||
rej(err)
|
|
||||||
|
|
||||||
console.log(`😄 Succesfully published to github pages`)
|
console.log(`😄 Succesfully published to github pages`)
|
||||||
res(true)
|
res(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
console.log(`😭 Something went wrong: ${err}`)
|
console.log(`😭 Something went wrong: ${err}`)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
BIN
docs/assets/gist.png
Normal file
BIN
docs/assets/gist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
docs/assets/gist_url.png
Normal file
BIN
docs/assets/gist_url.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
93
docs/controls.md
Normal file
93
docs/controls.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# Controls
|
||||||
|
|
||||||
|
## Moving around
|
||||||
|
|
||||||
|
- To move around, just click anywhere in the enviroment, drag in the oposite of the direction you want to moe in.
|
||||||
|
- Release when you finished the desired movement.
|
||||||
|
|
||||||
|
## Zooming
|
||||||
|
|
||||||
|
- To zoom in, scroll upwards.
|
||||||
|
- To zoom out, scroll downwards.
|
||||||
|
- The zoom will be applied in the position pointed by the mouse.
|
||||||
|
|
||||||
|
## Moving a component
|
||||||
|
|
||||||
|
- To move a component, left click on it.
|
||||||
|
- The gate will follow your mouse
|
||||||
|
- Release when the gate got in the desired position
|
||||||
|
|
||||||
|
## Deleting a component
|
||||||
|
|
||||||
|
- To remove a component, right click on it.
|
||||||
|
|
||||||
|
## Connection 2 pins
|
||||||
|
|
||||||
|
- To connect 2 pins, first click on one of them.
|
||||||
|
- Click on the other pin
|
||||||
|
|
||||||
|
> Note: You cannot connect 2 pins of the same type.
|
||||||
|
|
||||||
|
## Deleting a wire
|
||||||
|
|
||||||
|
- To delete a wire, click on it
|
||||||
|
|
||||||
|
## Opening the command palette
|
||||||
|
|
||||||
|
- To open the command palette, press ctrl + shift + p
|
||||||
|
|
||||||
|
## Creating a simulation
|
||||||
|
|
||||||
|
- To create a simulation, click the first button from the top of the sidebar, then type the desired name.
|
||||||
|
|
||||||
|
## Saving a simulation
|
||||||
|
|
||||||
|
- To save a simulation, follow one of the following actions:
|
||||||
|
1. Press ctrl + s
|
||||||
|
2. Open the command palette and type save, then press enter
|
||||||
|
3. Click on the 'simulation' button, then click 'save'
|
||||||
|
|
||||||
|
## Opening a simulation
|
||||||
|
|
||||||
|
- To open a simulation, click 'open simulation', then click the name of the simulation
|
||||||
|
|
||||||
|
## Deleting a simulation
|
||||||
|
|
||||||
|
- To delete a simulation, click 'open simulation', and then click the 'delete' icon on the row of your desired simulation.
|
||||||
|
|
||||||
|
## Rewind to the latest save (undo)
|
||||||
|
|
||||||
|
- To rewind to the latest save, follow one of the following actions:
|
||||||
|
1. Press ctrl + z
|
||||||
|
2. Click 'simulation' and then click 'undo'
|
||||||
|
|
||||||
|
## Downloading a simulation
|
||||||
|
|
||||||
|
- To download a simulation, follow one of the following actions:
|
||||||
|
1. Click 'simulation' and then type 'download'
|
||||||
|
2. Open the command palette, type 'download' and then press enter
|
||||||
|
|
||||||
|
> Note: You can also type 'download --save' or 'download -s' in the command palette to also save the simulation before downloading it
|
||||||
|
|
||||||
|
## Deleting a simulation
|
||||||
|
|
||||||
|
- To delete a simulation, press 'simulation' and then press 'delete'
|
||||||
|
- Press 'yes'
|
||||||
|
|
||||||
|
## Refreshing the enviroment
|
||||||
|
|
||||||
|
- To refresh the enviroment (reload all components), follow one of the following actions:
|
||||||
|
1. Click 'simulation' and then click 'refresh'
|
||||||
|
2. Press sfhit + delete
|
||||||
|
|
||||||
|
> Note: this won't refresh the whole window. To refresh the whole window, use the ui built in your browser
|
||||||
|
|
||||||
|
> Note 2: this can be useful if you just edited a custom logic gate and you want to see the changes without refreshing the whole window
|
||||||
|
|
||||||
|
## Clearing a simulation
|
||||||
|
|
||||||
|
> Note: cleaning = deleting all logic gates wich are not connected to anything
|
||||||
|
|
||||||
|
- To clear a simulation follow one of the following actions:
|
||||||
|
1. Click 'simulation'and then click 'clean'
|
||||||
|
2. Press shift + delete
|
13
docs/import.md
Normal file
13
docs/import.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# importing a logic gate
|
||||||
|
|
||||||
|
## Opening the import palette
|
||||||
|
|
||||||
|
- To open the import palette, follow one of the following actions:
|
||||||
|
|
||||||
|
1. Press ctrl + g
|
||||||
|
2. Press 'custom gates' and then press 'import new gate'
|
||||||
|
|
||||||
|
## Importing a logic gate
|
||||||
|
|
||||||
|
- Open the import palette
|
||||||
|
- Type a valid command (see **[the url parser](./url.md)**)
|
10
docs/main.md
Normal file
10
docs/main.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Welcome
|
||||||
|
|
||||||
|
Hello, and welcome to my logic gate simulator! I know it can look kinda scary at first, so that's why i wrote these docs!
|
||||||
|
|
||||||
|
I recomand reading the first 3 chapters before you start, and then only dig deeper into the others when you feel you mastered the basics!
|
||||||
|
|
||||||
|
# Table of contents
|
||||||
|
|
||||||
|
1. [Basic controls](./controls.md)
|
||||||
|
2. [Importing a custom logic gate](import.md)
|
13
docs/url.md
Normal file
13
docs/url.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# The url parser
|
||||||
|
|
||||||
|
If the first word is 'gist', the parser will automatcally try to fetch the github gist with the id equl to the second word:
|
||||||
|
|
||||||
|
**_Eg_**:
|
||||||
|
|
||||||
|
```
|
||||||
|
gist 8886faa6f99a7d2667ea8aa2f81ace04
|
||||||
|
```
|
||||||
|
|
||||||
|
![example of a gist id](./assets/gist_url.png)
|
||||||
|
|
||||||
|
Else, the parser will just try to fetch directly from the full string
|
41
package-lock.json
generated
41
package-lock.json
generated
|
@ -2891,7 +2891,8 @@
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
@ -2912,12 +2913,14 @@
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -2932,17 +2935,20 @@
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -3059,7 +3065,8 @@
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -3071,6 +3078,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -3085,6 +3093,7 @@
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
@ -3092,12 +3101,14 @@
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
@ -3116,6 +3127,7 @@
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -3196,7 +3208,8 @@
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -3208,6 +3221,7 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -3293,7 +3307,8 @@
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
@ -3329,6 +3344,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -3348,6 +3364,7 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -3391,12 +3408,14 @@
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Game</title>
|
<title>Logic gate simulator</title>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0">
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0"
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
|
@ -130,11 +132,18 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link
|
||||||
<link rel='stylesheet' href='style.css'>
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body ondragstart="return false;" ondrop="return false;" oncontextmenu="return false">
|
<body
|
||||||
|
ondragstart="return false;"
|
||||||
|
ondrop="return false;"
|
||||||
|
oncontextmenu="return false"
|
||||||
|
>
|
||||||
<div class="sk-folding-cube">
|
<div class="sk-folding-cube">
|
||||||
<div class="sk-cube1 sk-cube"></div>
|
<div class="sk-cube1 sk-cube"></div>
|
||||||
<div class="sk-cube2 sk-cube"></div>
|
<div class="sk-cube2 sk-cube"></div>
|
||||||
|
@ -142,5 +151,4 @@
|
||||||
<div class="sk-cube3 sk-cube"></div>
|
<div class="sk-cube3 sk-cube"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,4 +1,4 @@
|
||||||
import { Pin } from "../pin";
|
import { Pin } from '../pin'
|
||||||
|
|
||||||
export interface ComponentState {
|
export interface ComponentState {
|
||||||
position: [number, number]
|
position: [number, number]
|
||||||
|
@ -15,4 +15,4 @@ export interface activationContext {
|
||||||
color: (color: string) => void
|
color: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type materialMode = "standard_image" | "color"
|
export type materialMode = 'standard_image' | 'color' | 'url'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { svg, Part } from "lit-html";
|
import { svg, Part } from 'lit-html'
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from 'rxjs'
|
||||||
import { materialMode } from "./interfaces";
|
import { materialMode } from './interfaces'
|
||||||
|
|
||||||
declare function require<T>(path: string): T
|
declare function require<T>(path: string): T
|
||||||
|
|
||||||
|
@ -10,24 +10,27 @@ export class Material {
|
||||||
private static images: {
|
private static images: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
} = {
|
} = {
|
||||||
and: require("../../../assets/and_gate.jpg"),
|
and: require('../../../assets/and_gate.jpg'),
|
||||||
or: require("../../../assets/or_gate.png"),
|
or: require('../../../assets/or_gate.png'),
|
||||||
xor: require("../../../assets/xor_gate.png"),
|
xor: require('../../../assets/xor_gate.png'),
|
||||||
nor: require("../../../assets/nor_gate.png")
|
nor: require('../../../assets/nor_gate.png')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public color = new BehaviorSubject<string>('rgba(0,0,0,0)')
|
||||||
|
|
||||||
public color = new BehaviorSubject<string>("rgba(0,0,0,0)")
|
constructor(public mode: materialMode, public data: string) {
|
||||||
|
if (this.mode === 'color') this.color.next(data)
|
||||||
constructor(public mode: string, public name: materialMode | string) {
|
|
||||||
if (this.mode === "color")
|
|
||||||
this.color.next(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
innerHTML(x: partFactory, y: partFactory, w: partFactory, h: partFactory) {
|
innerHTML(x: partFactory, y: partFactory, w: partFactory, h: partFactory) {
|
||||||
|
const src =
|
||||||
|
this.mode === 'standard_image'
|
||||||
|
? Material.images[this.data]
|
||||||
|
: this.data
|
||||||
|
|
||||||
return svg`<foreignobject x=${x} y=${y} width=${w} height=${h}>
|
return svg`<foreignobject x=${x} y=${y} width=${w} height=${h}>
|
||||||
<div class="component-container">
|
<div class="component-container">
|
||||||
<img src=${Material.images[this.name]} height="97%" width="97%" draggable=false class="component">
|
<img src=${src} height="97%" width="97%" draggable=false class="component">
|
||||||
</div>
|
</div>
|
||||||
</foreignobject>`
|
</foreignobject>`
|
||||||
}
|
}
|
||||||
|
|
23
src/ts/common/componentImporter/evalImport.ts
Normal file
23
src/ts/common/componentImporter/evalImport.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { fecthAsJson } from './fetchJson'
|
||||||
|
import { getFirstFileFromGist, getGist } from './getGist'
|
||||||
|
|
||||||
|
export const evalImport = async <T>(
|
||||||
|
command: string,
|
||||||
|
extension = 'json'
|
||||||
|
): Promise<T> => {
|
||||||
|
const words = command.split(' ')
|
||||||
|
|
||||||
|
let final: T
|
||||||
|
|
||||||
|
if (words.length === 1) {
|
||||||
|
if (extension === 'json') {
|
||||||
|
final = await fecthAsJson<T>(command)
|
||||||
|
} else {
|
||||||
|
final = ((await (await fetch(command)).text()) as unknown) as T
|
||||||
|
}
|
||||||
|
} else if (words[0] === 'gist') {
|
||||||
|
final = getFirstFileFromGist(await getGist(words[1]), extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
return final
|
||||||
|
}
|
6
src/ts/common/componentImporter/fetchJson.ts
Normal file
6
src/ts/common/componentImporter/fetchJson.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export const fecthAsJson = async <T>(url: string) => {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const json = await res.json()
|
||||||
|
|
||||||
|
return json as T
|
||||||
|
}
|
27
src/ts/common/componentImporter/getGist.ts
Normal file
27
src/ts/common/componentImporter/getGist.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { fecthAsJson } from './fetchJson'
|
||||||
|
|
||||||
|
export interface Gist {
|
||||||
|
files: Record<string, { content: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGist = async (id: string) => {
|
||||||
|
const url = `https://api.github.com/gists/${id}`
|
||||||
|
const json = await fecthAsJson<Gist>(url)
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getFirstFileFromGist = (gist: Gist, extension = 'json') => {
|
||||||
|
const content =
|
||||||
|
gist.files[
|
||||||
|
Object.keys(gist.files).find(
|
||||||
|
name => name.indexOf(`.${extension}`) !== -1
|
||||||
|
)
|
||||||
|
].content
|
||||||
|
|
||||||
|
if (extension === 'json') {
|
||||||
|
return JSON.parse(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
}
|
62
src/ts/common/componentImporter/importComponent.ts
Normal file
62
src/ts/common/componentImporter/importComponent.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { ComponentTemplate } from '../componentManager/interfaces'
|
||||||
|
import { ComponentManager } from '../componentManager'
|
||||||
|
import { materialMode } from '../component/interfaces'
|
||||||
|
import { fecthAsJson } from './fetchJson'
|
||||||
|
import { getGist, getFirstFileFromGist } from './getGist'
|
||||||
|
import { evalImport } from './evalImport'
|
||||||
|
|
||||||
|
export interface Importable {
|
||||||
|
name: string
|
||||||
|
activation: string
|
||||||
|
onClick?: string
|
||||||
|
inputs: number
|
||||||
|
outputs: number
|
||||||
|
material: {
|
||||||
|
mode: materialMode
|
||||||
|
data: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults: Importable = {
|
||||||
|
activation: 'ctx.outputs[0].value = ctx.inputs[0].value',
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
material: {
|
||||||
|
mode: 'color',
|
||||||
|
data: 'red'
|
||||||
|
},
|
||||||
|
name: 'Imported component'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseActivation(activaton: string) {
|
||||||
|
const words = activaton.split(' ')
|
||||||
|
|
||||||
|
if (words[0] === 'url') {
|
||||||
|
return await evalImport<string>(words.slice(1).join(' '), 'js')
|
||||||
|
} else {
|
||||||
|
return activaton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function importComponent(
|
||||||
|
manager: ComponentManager,
|
||||||
|
command: string
|
||||||
|
): Promise<ComponentTemplate> {
|
||||||
|
const final: Importable = await evalImport(command)
|
||||||
|
|
||||||
|
const template: ComponentTemplate = {
|
||||||
|
...defaults,
|
||||||
|
...final,
|
||||||
|
editable: false,
|
||||||
|
version: '1.0.0',
|
||||||
|
imported: true,
|
||||||
|
importCommand: command
|
||||||
|
}
|
||||||
|
|
||||||
|
template.activation = await parseActivation(template.activation)
|
||||||
|
|
||||||
|
manager.templateStore.store.set(template.name, template)
|
||||||
|
manager.succes(`Succesfully imported component ${template.name}`)
|
||||||
|
|
||||||
|
return template
|
||||||
|
}
|
|
@ -1,38 +1,38 @@
|
||||||
import { Singleton } from "@eix/utils";
|
import { Singleton } from '@eix/utils'
|
||||||
import { Component } from "../component";
|
import { Component } from '../component'
|
||||||
import { Subject, BehaviorSubject, fromEvent } from "rxjs";
|
import { Subject, BehaviorSubject, fromEvent } from 'rxjs'
|
||||||
import { svg, SVGTemplateResult, html } from "lit-html";
|
import { svg, SVGTemplateResult, html } from 'lit-html'
|
||||||
import { subscribe } from "lit-rx";
|
import { subscribe } from 'lit-rx'
|
||||||
import { Screen } from "../screen.ts";
|
import { Screen } from '../screen.ts'
|
||||||
import { ManagerState, ComponentTemplate } from "./interfaces";
|
import { ManagerState, ComponentTemplate } from './interfaces'
|
||||||
import { Store } from "../store";
|
import { Store } from '../store'
|
||||||
import { KeyboardInput } from "@eix/input"
|
import { KeyboardInput } from '@eix/input'
|
||||||
import { success, error } from "toastr"
|
import { success, error } from 'toastr'
|
||||||
import { ComponentTemplateStore } from "./componentTemplateStore";
|
import { ComponentTemplateStore } from './componentTemplateStore'
|
||||||
import { alertOptions } from "./alertOptions";
|
import { alertOptions } from './alertOptions'
|
||||||
import { WireManager } from "../wires";
|
import { WireManager } from '../wires'
|
||||||
import { runCounter } from "../component/runCounter";
|
import { runCounter } from '../component/runCounter'
|
||||||
import { Settings } from "../store/settings";
|
import { Settings } from '../store/settings'
|
||||||
import { download } from "./download";
|
import { download } from './download'
|
||||||
import { modal } from "../modals";
|
import { modal } from '../modals'
|
||||||
import { map } from "rxjs/operators";
|
import { map } from 'rxjs/operators'
|
||||||
import { persistent } from "../store/persistent";
|
import { persistent } from '../store/persistent'
|
||||||
import { MDCTextField } from '@material/textfield';
|
import { MDCTextField } from '@material/textfield'
|
||||||
|
import { importComponent } from '../componentImporter/importComponent'
|
||||||
const defaultName = "default"
|
|
||||||
|
|
||||||
|
const defaultName = 'default'
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
export class ComponentManager {
|
export class ComponentManager {
|
||||||
public components: Component[] = []
|
public components: Component[] = []
|
||||||
public svgs = new Subject<SVGTemplateResult>()
|
public svgs = new Subject<SVGTemplateResult>()
|
||||||
public placeholder = new BehaviorSubject("Create simulation")
|
public placeholder = new BehaviorSubject('Create simulation')
|
||||||
public barAlpha = new BehaviorSubject<string>("0");
|
public barAlpha = new BehaviorSubject<string>('0')
|
||||||
public wireManager = new WireManager()
|
public wireManager = new WireManager()
|
||||||
public onTop: Component
|
public onTop: Component
|
||||||
public templateStore = new ComponentTemplateStore()
|
public templateStore = new ComponentTemplateStore()
|
||||||
|
|
||||||
private temporaryCommnad = ""
|
private temporaryCommnad = ''
|
||||||
private clicked = false
|
private clicked = false
|
||||||
private ignoreKeyDowns = false
|
private ignoreKeyDowns = false
|
||||||
|
|
||||||
|
@ -46,28 +46,33 @@ export class ComponentManager {
|
||||||
scale: [100, 100]
|
scale: [100, 100]
|
||||||
}
|
}
|
||||||
|
|
||||||
private commandHistoryStore = new Store<string>("commandHistory")
|
private commandHistoryStore = new Store<string>('commandHistory')
|
||||||
private store = new Store<ManagerState>("simulationStates")
|
private store = new Store<ManagerState>('simulationStates')
|
||||||
|
|
||||||
private saveEvent = new KeyboardInput("s")
|
private saveEvent = new KeyboardInput('s')
|
||||||
private createEvent = new KeyboardInput("m")
|
private createEvent = new KeyboardInput('m')
|
||||||
private closeInputEvent = new KeyboardInput("enter")
|
private closeInputEvent = new KeyboardInput('enter')
|
||||||
private ctrlEvent = new KeyboardInput("ctrl")
|
private ctrlEvent = new KeyboardInput('ctrl')
|
||||||
private palleteEvent = new KeyboardInput("p")
|
private palleteEvent = new KeyboardInput('p')
|
||||||
private undoEvent = new KeyboardInput("z")
|
private undoEvent = new KeyboardInput('z')
|
||||||
private shiftEvent = new KeyboardInput("shift")
|
private shiftEvent = new KeyboardInput('shift')
|
||||||
private refreshEvent = new KeyboardInput("r")
|
private refreshEvent = new KeyboardInput('r')
|
||||||
private clearEvent = new KeyboardInput("delete")
|
private gEvent = new KeyboardInput('g')
|
||||||
private upEvent = new KeyboardInput("up")
|
private clearEvent = new KeyboardInput('delete')
|
||||||
private downEvent = new KeyboardInput("down")
|
private upEvent = new KeyboardInput('up')
|
||||||
|
private downEvent = new KeyboardInput('down')
|
||||||
|
|
||||||
@persistent<ComponentManager, string>(defaultName, "main")
|
@persistent<ComponentManager, string>(defaultName, 'main')
|
||||||
public name: string
|
public name: string
|
||||||
public alertOptions = alertOptions
|
public alertOptions = alertOptions
|
||||||
|
|
||||||
private commandHistory: string[] = []
|
private commandHistory: string[] = []
|
||||||
private commands: {
|
private commands: {
|
||||||
[key: string]: (ctx: ComponentManager, args: string[], flags: string[]) => any
|
[key: string]: (
|
||||||
|
ctx: ComponentManager,
|
||||||
|
args: string[],
|
||||||
|
flags: string[]
|
||||||
|
) => any
|
||||||
} = {
|
} = {
|
||||||
clear(ctx: ComponentManager) {
|
clear(ctx: ComponentManager) {
|
||||||
ctx.clear()
|
ctx.clear()
|
||||||
|
@ -77,26 +82,34 @@ export class ComponentManager {
|
||||||
},
|
},
|
||||||
ls(ctx: ComponentManager) {
|
ls(ctx: ComponentManager) {
|
||||||
const data = ctx.store.ls()
|
const data = ctx.store.ls()
|
||||||
const message = data.join("\n")
|
const message = data.join('\n')
|
||||||
|
|
||||||
success(message, "", ctx.alertOptions)
|
success(message, '', ctx.alertOptions)
|
||||||
},
|
},
|
||||||
help(ctx: ComponentManager) {
|
help(ctx: ComponentManager) {
|
||||||
success(`Usage: <command> <br>
|
success(
|
||||||
|
`Usage: <command> <br>
|
||||||
Where <command> is one of:
|
Where <command> is one of:
|
||||||
<ul>
|
<ul>
|
||||||
${Object.keys(ctx.commands).map(val => `
|
${Object.keys(ctx.commands)
|
||||||
|
.map(
|
||||||
|
val => `
|
||||||
<li>${val}</li>
|
<li>${val}</li>
|
||||||
`).join("")}
|
`
|
||||||
|
)
|
||||||
|
.join('')}
|
||||||
</ul>
|
</ul>
|
||||||
`, "", ctx.alertOptions)
|
`,
|
||||||
|
'',
|
||||||
|
ctx.alertOptions
|
||||||
|
)
|
||||||
},
|
},
|
||||||
refresh(ctx: ComponentManager) {
|
refresh(ctx: ComponentManager) {
|
||||||
ctx.refresh()
|
ctx.refresh()
|
||||||
},
|
},
|
||||||
rewind(ctx: ComponentManager) {
|
rewind(ctx: ComponentManager) {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
success("Succesfully cleared localStorage!","",ctx.alertOptions)
|
success('Succesfully cleared localStorage!', '', ctx.alertOptions)
|
||||||
},
|
},
|
||||||
ctp: this.templateStore.commands.template,
|
ctp: this.templateStore.commands.template,
|
||||||
settings: this.settings.commands,
|
settings: this.settings.commands,
|
||||||
|
@ -122,11 +135,11 @@ export class ComponentManager {
|
||||||
public shortcuts: {
|
public shortcuts: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
} = {
|
} = {
|
||||||
clear: "shift delete",
|
clear: 'shift delete',
|
||||||
clean: "delete",
|
clean: 'delete',
|
||||||
save: "ctrl s",
|
save: 'ctrl s',
|
||||||
undo: "ctrl z",
|
undo: 'ctrl z',
|
||||||
refresh: "ctrl r"
|
refresh: 'ctrl r'
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -136,22 +149,22 @@ export class ComponentManager {
|
||||||
|
|
||||||
this.refresh()
|
this.refresh()
|
||||||
|
|
||||||
fromEvent(document.body, "keydown").subscribe((e: KeyboardEvent) => {
|
fromEvent(document.body, 'keydown').subscribe((e: KeyboardEvent) => {
|
||||||
if (this.barAlpha.value == "1") {
|
if (this.barAlpha.value == '1') {
|
||||||
const elem = document.getElementById("nameInput")
|
const elem = document.getElementById('nameInput')
|
||||||
elem.focus()
|
elem.focus()
|
||||||
}
|
} else if (!this.ignoreKeyDowns) {
|
||||||
else if (!this.ignoreKeyDowns) {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
fromEvent(document.body, "keyup").subscribe((e: MouseEvent) => {
|
fromEvent(document.body, 'keyup').subscribe((e: MouseEvent) => {
|
||||||
if (this.barAlpha.value === "1") {
|
if (this.barAlpha.value === '1') {
|
||||||
if (this.closeInputEvent.value)
|
if (this.closeInputEvent.value) this.create()
|
||||||
this.create()
|
else if (this.inputMode === 'command') {
|
||||||
else if (this.inputMode === "command") {
|
const elem = <HTMLInputElement>(
|
||||||
const elem = <HTMLInputElement>document.getElementById("nameInput")
|
document.getElementById('nameInput')
|
||||||
|
)
|
||||||
if (this.upEvent.value) {
|
if (this.upEvent.value) {
|
||||||
document.body.focus()
|
document.body.focus()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -159,10 +172,12 @@ export class ComponentManager {
|
||||||
|
|
||||||
if (index) {
|
if (index) {
|
||||||
//save drafts
|
//save drafts
|
||||||
if (index === -1)
|
if (index === -1) this.temporaryCommnad = elem.value
|
||||||
this.temporaryCommnad = elem.value
|
|
||||||
|
|
||||||
const newIndex = (index === -1) ? this.commandHistory.length - 1 : index - 1
|
const newIndex =
|
||||||
|
index === -1
|
||||||
|
? this.commandHistory.length - 1
|
||||||
|
: index - 1
|
||||||
elem.value = this.commandHistory[newIndex]
|
elem.value = this.commandHistory[newIndex]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,36 +188,36 @@ export class ComponentManager {
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
const maxIndex = this.commandHistory.length - 1
|
const maxIndex = this.commandHistory.length - 1
|
||||||
elem.value = (index === maxIndex) ? this.temporaryCommnad : this.commandHistory[index + 1]
|
elem.value =
|
||||||
|
index === maxIndex
|
||||||
|
? this.temporaryCommnad
|
||||||
|
: this.commandHistory[index + 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (this.ctrlEvent.value) {
|
if (this.ctrlEvent.value) {
|
||||||
if (this.createEvent.value) {
|
if (this.createEvent.value) {
|
||||||
this.prepareNewSimulation()
|
this.prepareNewSimulation()
|
||||||
}
|
} else if (
|
||||||
else if (this.shiftEvent.value && this.palleteEvent.value) {
|
this.shiftEvent.value &&
|
||||||
|
this.palleteEvent.value
|
||||||
|
) {
|
||||||
this.preInput()
|
this.preInput()
|
||||||
this.inputMode = "command"
|
this.inputMode = 'command'
|
||||||
this.placeholder.next("Command palette")
|
this.placeholder.next('Command palette')
|
||||||
}
|
} else if (this.gEvent.value) {
|
||||||
else if (this.saveEvent.value) {
|
this.importGate()
|
||||||
|
} else if (this.saveEvent.value) {
|
||||||
this.save()
|
this.save()
|
||||||
}
|
} else if (this.undoEvent.value) {
|
||||||
else if (this.undoEvent.value) {
|
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
} else if (this.refreshEvent.value) {
|
||||||
else if (this.refreshEvent.value) {
|
|
||||||
this.silentRefresh(true)
|
this.silentRefresh(true)
|
||||||
}
|
}
|
||||||
}
|
} else if (this.clearEvent.value) {
|
||||||
else if (this.clearEvent.value) {
|
if (this.shiftEvent.value) this.clear()
|
||||||
if (this.shiftEvent.value)
|
else this.smartClear()
|
||||||
this.clear()
|
|
||||||
else
|
|
||||||
this.smartClear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -212,22 +227,20 @@ export class ComponentManager {
|
||||||
this.update()
|
this.update()
|
||||||
// this.save()
|
// this.save()
|
||||||
})
|
})
|
||||||
if (this.saves.value.length === 0)
|
if (this.saves.value.length === 0) this.save()
|
||||||
this.save()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initEmptyGate(name: string) {
|
private initEmptyGate(name: string) {
|
||||||
const obj: ComponentTemplate = {
|
const obj: ComponentTemplate = {
|
||||||
inputs: 1,
|
inputs: 1,
|
||||||
name,
|
name,
|
||||||
version: "1.0.0",
|
version: '1.0.0',
|
||||||
outputs: 1,
|
outputs: 1,
|
||||||
activation: "",
|
activation: '',
|
||||||
editable: true,
|
editable: true,
|
||||||
material: {
|
material: {
|
||||||
mode: "color",
|
mode: 'color',
|
||||||
data: "blue"
|
data: 'blue'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,44 +251,57 @@ export class ComponentManager {
|
||||||
|
|
||||||
public newGate() {
|
public newGate() {
|
||||||
this.preInput()
|
this.preInput()
|
||||||
this.inputMode = "gate"
|
this.inputMode = 'gate'
|
||||||
this.placeholder.next("Gate name")
|
this.placeholder.next('Gate name')
|
||||||
|
}
|
||||||
|
|
||||||
|
public importGate() {
|
||||||
|
this.preInput()
|
||||||
|
this.inputMode = 'importGate'
|
||||||
|
this.placeholder.next('Gate url')
|
||||||
}
|
}
|
||||||
|
|
||||||
public prepareNewSimulation() {
|
public prepareNewSimulation() {
|
||||||
this.preInput()
|
this.preInput()
|
||||||
this.inputMode = "create"
|
this.inputMode = 'create'
|
||||||
this.placeholder.next("Create simulation")
|
this.placeholder.next('Create simulation')
|
||||||
}
|
}
|
||||||
|
|
||||||
private preInput() {
|
private preInput() {
|
||||||
const elem = <HTMLInputElement>document.getElementById("nameInput")
|
const elem = <HTMLInputElement>document.getElementById('nameInput')
|
||||||
elem.value = ""
|
elem.value = ''
|
||||||
this.barAlpha.next("1")
|
this.barAlpha.next('1')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async create() {
|
private async create() {
|
||||||
const elem = <HTMLInputElement>document.getElementById("nameInput")
|
const elem = <HTMLInputElement>document.getElementById('nameInput')
|
||||||
this.barAlpha.next("0")
|
this.barAlpha.next('0')
|
||||||
|
|
||||||
if (this.inputMode === "create") {
|
if (this.inputMode === 'create') {
|
||||||
await this.createEmptySimulation(elem.value)
|
await this.createEmptySimulation(elem.value)
|
||||||
success(`Succesfully created simulation ${elem.value}`, "", this.alertOptions)
|
success(
|
||||||
|
`Succesfully created simulation ${elem.value}`,
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
|
} else if (this.inputMode === 'command') this.eval(elem.value)
|
||||||
|
else if (this.inputMode === 'gate') this.initEmptyGate(elem.value)
|
||||||
|
else if (this.inputMode === 'importGate') {
|
||||||
|
importComponent(this, elem.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this.inputMode === "command")
|
public succes(message: string) {
|
||||||
this.eval(elem.value)
|
success(message, '', this.alertOptions)
|
||||||
|
|
||||||
else if (this.inputMode === "gate")
|
|
||||||
this.initEmptyGate(elem.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDuplicateModal(name: string) {
|
private async handleDuplicateModal(name: string) {
|
||||||
const result = await modal({
|
const result = await modal({
|
||||||
title: "Warning",
|
title: 'Warning',
|
||||||
content: html`There was already a simulation called ${name},
|
content: html`
|
||||||
are you sure you want to override it?
|
There was already a simulation called ${name}, are you sure you
|
||||||
All your work will be lost!`
|
want to override it? All your work will be lost!
|
||||||
|
`
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -286,46 +312,80 @@ export class ComponentManager {
|
||||||
const gate = this.templateStore.store.get(name)
|
const gate = this.templateStore.store.get(name)
|
||||||
|
|
||||||
modal({
|
modal({
|
||||||
no: "",
|
no: '',
|
||||||
yes: "save",
|
yes: 'save',
|
||||||
title: `Edit ${name}`,
|
title: `Edit ${name}`,
|
||||||
content: html`${html`
|
content: html`
|
||||||
<br>
|
${html`
|
||||||
|
<br />
|
||||||
<div class="mdc-text-field mdc-text-field--textarea">
|
<div class="mdc-text-field mdc-text-field--textarea">
|
||||||
<textarea id="codeArea" class="mdc-text-field__input js" rows="8" cols="40">${
|
<textarea
|
||||||
gate.activation
|
id="codeArea"
|
||||||
}</textarea>
|
class="mdc-text-field__input js"
|
||||||
|
rows="8"
|
||||||
|
cols="40"
|
||||||
|
>
|
||||||
|
${gate.activation}</textarea
|
||||||
|
>
|
||||||
<div class="mdc-notched-outline">
|
<div class="mdc-notched-outline">
|
||||||
<div class="mdc-notched-outline__leading"></div>
|
<div class="mdc-notched-outline__leading"></div>
|
||||||
<div class="mdc-notched-outline__notch">
|
<div class="mdc-notched-outline__notch">
|
||||||
<label for="textarea" class="mdc-floating-label">Activation function</label>
|
<label for="textarea" class="mdc-floating-label"
|
||||||
|
>Activation function</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdc-notched-outline__trailing"></div>
|
<div class="mdc-notched-outline__trailing"></div>
|
||||||
</div>
|
</div>
|
||||||
</div><br><br>
|
</div>
|
||||||
|
<br /><br />
|
||||||
<div class="mdc-text-field" id="inputCount">
|
<div class="mdc-text-field" id="inputCount">
|
||||||
<input type="number" id="my-text-field" class="mdc-text-field__input inputCount-i" value=${gate.inputs}>
|
<input
|
||||||
<label class="mdc-floating-label" for="my-text-field">Inputs</label>
|
type="number"
|
||||||
|
id="my-text-field"
|
||||||
|
class="mdc-text-field__input inputCount-i"
|
||||||
|
value=${gate.inputs}
|
||||||
|
/>
|
||||||
|
<label class="mdc-floating-label" for="my-text-field"
|
||||||
|
>Inputs</label
|
||||||
|
>
|
||||||
<div class="mdc-line-ripple"></div>
|
<div class="mdc-line-ripple"></div>
|
||||||
</div><br><br>
|
</div>
|
||||||
|
<br /><br />
|
||||||
<div class="mdc-text-field" id="outputCount">
|
<div class="mdc-text-field" id="outputCount">
|
||||||
<input type="number" id="my-text-field" class="mdc-text-field__input outputCount-i" value=${gate.outputs}>
|
<input
|
||||||
<label class="mdc-floating-label" for="my-text-field">Outputs</label>
|
type="number"
|
||||||
|
id="my-text-field"
|
||||||
|
class="mdc-text-field__input outputCount-i"
|
||||||
|
value=${gate.outputs}
|
||||||
|
/>
|
||||||
|
<label class="mdc-floating-label" for="my-text-field"
|
||||||
|
>Outputs</label
|
||||||
|
>
|
||||||
<div class="mdc-line-ripple"></div>
|
<div class="mdc-line-ripple"></div>
|
||||||
</div><br><br>
|
</div>
|
||||||
|
<br /><br />
|
||||||
<div class="mdc-text-field" id="color">
|
<div class="mdc-text-field" id="color">
|
||||||
<input type="string" id="my-text-field" class="mdc-text-field__input color-i" value=${gate.material.data}>
|
<input
|
||||||
<label class="mdc-floating-label" for="my-text-field">Outputs</label>
|
type="string"
|
||||||
|
id="my-text-field"
|
||||||
|
class="mdc-text-field__input color-i"
|
||||||
|
value=${gate.material.data}
|
||||||
|
/>
|
||||||
|
<label class="mdc-floating-label" for="my-text-field"
|
||||||
|
>Color</label
|
||||||
|
>
|
||||||
<div class="mdc-line-ripple"></div>
|
<div class="mdc-line-ripple"></div>
|
||||||
</div><br>
|
</div>
|
||||||
`}`
|
<br />
|
||||||
|
`}
|
||||||
|
`
|
||||||
}).then(val => {
|
}).then(val => {
|
||||||
this.ignoreKeyDowns = false
|
this.ignoreKeyDowns = false
|
||||||
const elems: (HTMLInputElement | HTMLTextAreaElement)[] = [
|
const elems: (HTMLInputElement | HTMLTextAreaElement)[] = [
|
||||||
document.querySelector("#codeArea"),
|
document.querySelector('#codeArea'),
|
||||||
document.querySelector(".inputCount-i"),
|
document.querySelector('.inputCount-i'),
|
||||||
document.querySelector(".outputCount-i"),
|
document.querySelector('.outputCount-i'),
|
||||||
document.querySelector(".color-i")
|
document.querySelector('.color-i')
|
||||||
]
|
]
|
||||||
const data = elems.map(val => val.value)
|
const data = elems.map(val => val.value)
|
||||||
|
|
||||||
|
@ -335,20 +395,24 @@ export class ComponentManager {
|
||||||
inputs: Number(data[1]),
|
inputs: Number(data[1]),
|
||||||
outputs: Number(data[2]),
|
outputs: Number(data[2]),
|
||||||
material: {
|
material: {
|
||||||
mode: "color",
|
mode: 'color',
|
||||||
data: data[3]
|
data: data[3]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
new MDCTextField(document.querySelector('.mdc-text-field'));
|
new MDCTextField(document.querySelector('.mdc-text-field'))
|
||||||
new MDCTextField(document.querySelector('#outputCount'));
|
new MDCTextField(document.querySelector('#outputCount'))
|
||||||
new MDCTextField(document.querySelector('#inputCount'));
|
new MDCTextField(document.querySelector('#inputCount'))
|
||||||
new MDCTextField(document.querySelector('#color'));
|
new MDCTextField(document.querySelector('#color'))
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(template: string, position?: [number, number]) {
|
public add(template: string, position?: [number, number]) {
|
||||||
const pos = position ? position : [...Array(2)].fill(this.standard.offset * this.components.length) as [number, number]
|
const pos = position
|
||||||
|
? position
|
||||||
|
: ([...Array(2)].fill(
|
||||||
|
this.standard.offset * this.components.length
|
||||||
|
) as [number, number])
|
||||||
|
|
||||||
this.components.push(new Component(template, pos, this.standard.scale))
|
this.components.push(new Component(template, pos, this.standard.scale))
|
||||||
this.update()
|
this.update()
|
||||||
|
@ -356,17 +420,20 @@ export class ComponentManager {
|
||||||
|
|
||||||
public async delete(name: string) {
|
public async delete(name: string) {
|
||||||
const res = await modal({
|
const res = await modal({
|
||||||
title: "Are you sure?",
|
title: 'Are you sure?',
|
||||||
content: html`Deleting a simulations is ireversible, and all work will be lost!`
|
content: html`
|
||||||
|
Deleting a simulations is ireversible, and all work will be
|
||||||
|
lost!
|
||||||
|
`
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
if (this.name === name) {
|
if (this.name === name) {
|
||||||
if (this.saves.value.length > 1) {
|
if (this.saves.value.length > 1) {
|
||||||
this.switchTo(this.saves.value.find(val => val !== name))
|
this.switchTo(this.saves.value.find(val => val !== name))
|
||||||
}
|
} else {
|
||||||
else {
|
let newName =
|
||||||
let newName = (name === defaultName) ? `${defaultName}(1)` : defaultName
|
name === defaultName ? `${defaultName}(1)` : defaultName
|
||||||
await this.createEmptySimulation(newName)
|
await this.createEmptySimulation(newName)
|
||||||
this.switchTo(newName)
|
this.switchTo(newName)
|
||||||
}
|
}
|
||||||
|
@ -384,15 +451,18 @@ export class ComponentManager {
|
||||||
scale: [1, 1]
|
scale: [1, 1]
|
||||||
})
|
})
|
||||||
|
|
||||||
if (name !== this.name)
|
if (name !== this.name) this.save()
|
||||||
this.save()
|
|
||||||
this.name = name
|
this.name = name
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(async (res) => {//get wheater theres already a simulation with that name
|
return new Promise(async res => {
|
||||||
if (this.store.get(name) && await this.handleDuplicateModal(name) ||
|
//get wheater theres already a simulation with that name
|
||||||
!this.store.get(name)) {
|
if (
|
||||||
|
(this.store.get(name) &&
|
||||||
|
(await this.handleDuplicateModal(name))) ||
|
||||||
|
!this.store.get(name)
|
||||||
|
) {
|
||||||
create()
|
create()
|
||||||
res(true)
|
res(true)
|
||||||
}
|
}
|
||||||
|
@ -402,38 +472,53 @@ export class ComponentManager {
|
||||||
public switchTo(name: string) {
|
public switchTo(name: string) {
|
||||||
const data = this.store.get(name)
|
const data = this.store.get(name)
|
||||||
if (!data)
|
if (!data)
|
||||||
error(`An error occured when trying to load ${name}`, "", this.alertOptions)
|
error(
|
||||||
|
`An error occured when trying to load ${name}`,
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
|
|
||||||
this.name = name
|
this.name = name
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
eval(command: string) {
|
eval(command: string) {
|
||||||
if (!this.commandHistory.includes(command)) // no duplicates
|
if (!this.commandHistory.includes(command))
|
||||||
|
// no duplicates
|
||||||
this.commandHistory.push(command)
|
this.commandHistory.push(command)
|
||||||
|
|
||||||
while (this.commandHistory.length > 10) // max of 10 elements
|
while (
|
||||||
|
this.commandHistory.length > 10 // max of 10 elements
|
||||||
|
)
|
||||||
this.commandHistory.shift()
|
this.commandHistory.shift()
|
||||||
|
|
||||||
const words = command.split(" ")
|
const words = command.split(' ')
|
||||||
|
|
||||||
if (words[0] in this.commands) {
|
if (words[0] in this.commands) {
|
||||||
const remaining = words.slice(1)
|
const remaining = words.slice(1)
|
||||||
const flags = remaining.filter(val => val[0] == "-")
|
const flags = remaining.filter(val => val[0] == '-')
|
||||||
const args = remaining.filter(val => val[0] != "-")
|
const args = remaining.filter(val => val[0] != '-')
|
||||||
this.commands[words[0]](this, args, flags)
|
this.commands[words[0]](this, args, flags)
|
||||||
}
|
} else
|
||||||
else
|
error(
|
||||||
error(`Command ${words} doesn't exist. Run help to get a list of all commands.`,
|
`Command ${words} doesn't exist. Run help to get a list of all commands.`,
|
||||||
"", this.alertOptions)
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public smartClear() {
|
public smartClear() {
|
||||||
this.components = this.components.filter(({ id }) =>
|
this.components = this.components.filter(({ id }) =>
|
||||||
this.wireManager.wires.find(val => val.input.of.id == id || val.output.of.id == id)
|
this.wireManager.wires.find(
|
||||||
|
val => val.input.of.id == id || val.output.of.id == id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
this.update()
|
this.update()
|
||||||
success("Succesfully cleared all unconnected components", "", this.alertOptions)
|
success(
|
||||||
|
'Succesfully cleared all unconnected components',
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear() {
|
public clear() {
|
||||||
|
@ -441,7 +526,7 @@ export class ComponentManager {
|
||||||
this.wireManager.dispose()
|
this.wireManager.dispose()
|
||||||
this.update()
|
this.update()
|
||||||
|
|
||||||
success("Succesfully cleared all components", "", this.alertOptions)
|
success('Succesfully cleared all components', '', this.alertOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
public refresh() {
|
public refresh() {
|
||||||
|
@ -454,7 +539,11 @@ export class ComponentManager {
|
||||||
|
|
||||||
this.update()
|
this.update()
|
||||||
|
|
||||||
success("Succesfully refreshed to the latest save", "", this.alertOptions)
|
success(
|
||||||
|
'Succesfully refreshed to the latest save',
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
@ -488,12 +577,10 @@ export class ComponentManager {
|
||||||
// if (false) { }
|
// if (false) { }
|
||||||
if (toAddOnTop >= 0) {
|
if (toAddOnTop >= 0) {
|
||||||
this.top(this.components[toAddOnTop])
|
this.top(this.components[toAddOnTop])
|
||||||
}
|
} else if (outsideComponents && this.clicked) {
|
||||||
|
|
||||||
else if (outsideComponents && this.clicked) {
|
|
||||||
const mousePosition = [e.clientX, e.clientY]
|
const mousePosition = [e.clientX, e.clientY]
|
||||||
const delta = mousePosition.map((value, index) =>
|
const delta = mousePosition.map(
|
||||||
this.screen.mousePosition[index] - value
|
(value, index) => this.screen.mousePosition[index] - value
|
||||||
) as [number, number]
|
) as [number, number]
|
||||||
this.screen.move(...delta)
|
this.screen.move(...delta)
|
||||||
}
|
}
|
||||||
|
@ -503,7 +590,11 @@ export class ComponentManager {
|
||||||
public silentRefresh(verboose = false) {
|
public silentRefresh(verboose = false) {
|
||||||
this.loadState(this.state)
|
this.loadState(this.state)
|
||||||
if (verboose)
|
if (verboose)
|
||||||
success("Succesfully reloaded all components", "", this.alertOptions)
|
success(
|
||||||
|
'Succesfully reloaded all components',
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public top(component: Component) {
|
public top(component: Component) {
|
||||||
|
@ -523,14 +614,16 @@ export class ComponentManager {
|
||||||
toRemoveDuplicatesFor = component
|
toRemoveDuplicatesFor = component
|
||||||
}
|
}
|
||||||
|
|
||||||
const stroke = subscribe(component.clickedChanges.pipe(map(
|
const stroke = subscribe(
|
||||||
val => val ? "yellow" : "black"
|
component.clickedChanges.pipe(
|
||||||
)))
|
map(val => (val ? 'yellow' : 'black'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return svg`
|
return svg`
|
||||||
<g>
|
<g>
|
||||||
${component.pinsSvg(10, 20)}
|
${component.pinsSvg(10, 20)}
|
||||||
${component.pinsSvg(10, 20, "output")}
|
${component.pinsSvg(10, 20, 'output')}
|
||||||
|
|
||||||
<g @mousedown=${(e: MouseEvent) => component.handleClick(e)}
|
<g @mousedown=${(e: MouseEvent) => component.handleClick(e)}
|
||||||
@touchstart=${(e: MouseEvent) => component.handleClick(e)}
|
@touchstart=${(e: MouseEvent) => component.handleClick(e)}
|
||||||
|
@ -541,39 +634,47 @@ export class ComponentManager {
|
||||||
x=${subscribe(component.x)}
|
x=${subscribe(component.x)}
|
||||||
y=${subscribe(component.y)}
|
y=${subscribe(component.y)}
|
||||||
stroke=${stroke}
|
stroke=${stroke}
|
||||||
fill=${(component.material.mode === "standard_image") ?
|
fill=${
|
||||||
"rgba(0,0,0,0)" :
|
component.material.mode !== 'color'
|
||||||
subscribe(component.material.color)}
|
? 'rgba(0,0,0,0)'
|
||||||
|
: subscribe(component.material.color)
|
||||||
|
}
|
||||||
rx=20
|
rx=20
|
||||||
ry=20>
|
ry=20>
|
||||||
</rect>
|
</rect>
|
||||||
${(component.material.mode === "standard_image") ? component.material.innerHTML(
|
${
|
||||||
|
component.material.mode !== 'color'
|
||||||
|
? component.material.innerHTML(
|
||||||
subscribe(component.x),
|
subscribe(component.x),
|
||||||
subscribe(component.y),
|
subscribe(component.y),
|
||||||
subscribe(component.width),
|
subscribe(component.width),
|
||||||
subscribe(component.height)) : ""}
|
subscribe(component.height)
|
||||||
|
)
|
||||||
|
: ''
|
||||||
|
}
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
`});
|
`
|
||||||
|
})
|
||||||
|
|
||||||
if (toRemoveDuplicatesFor)
|
if (toRemoveDuplicatesFor) this.removeDuplicates(toRemoveDuplicatesFor)
|
||||||
this.removeDuplicates(toRemoveDuplicatesFor)
|
|
||||||
|
|
||||||
return svg`${this.wireManager.svg} ${result}`
|
return svg`${this.wireManager.svg} ${result}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeDuplicates(component: Component) {
|
private removeDuplicates(component: Component) {
|
||||||
let instances = this.components
|
let instances = this.components
|
||||||
.map((value, index) => (value == component) ? index : null)
|
.map((value, index) => (value == component ? index : null))
|
||||||
.filter(value => value)
|
.filter(value => value)
|
||||||
instances.pop()
|
instances.pop()
|
||||||
|
|
||||||
this.components = this.components
|
this.components = this.components.filter(
|
||||||
.filter((_, index) => instances.indexOf(index) != -1)
|
(_, index) => instances.indexOf(index) != -1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get state(): ManagerState {
|
get state(): ManagerState {
|
||||||
const components = Array.from((new Set(this.components)).values())
|
const components = Array.from(new Set(this.components).values())
|
||||||
return {
|
return {
|
||||||
components: components.map(value => value.state),
|
components: components.map(value => value.state),
|
||||||
position: this.screen.position as [number, number],
|
position: this.screen.position as [number, number],
|
||||||
|
@ -591,17 +692,24 @@ export class ComponentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadState(state: ManagerState) {
|
private loadState(state: ManagerState) {
|
||||||
if (!state.wires) //old state
|
if (!state.wires)
|
||||||
|
//old state
|
||||||
return
|
return
|
||||||
|
|
||||||
this.wireManager.dispose()
|
this.wireManager.dispose()
|
||||||
this.clicked = false
|
this.clicked = false
|
||||||
this.components = state.components.map(value => Component.fromState(value))
|
this.components = state.components.map(value =>
|
||||||
|
Component.fromState(value)
|
||||||
|
)
|
||||||
this.onTop = null
|
this.onTop = null
|
||||||
|
|
||||||
state.wires.forEach(val => {
|
state.wires.forEach(val => {
|
||||||
this.wireManager.start = this.getComponentById(val.from.owner).outputPins[val.from.index]
|
this.wireManager.start = this.getComponentById(
|
||||||
this.wireManager.end = this.getComponentById(val.to.owner).inputPins[val.to.index]
|
val.from.owner
|
||||||
|
).outputPins[val.from.index]
|
||||||
|
this.wireManager.end = this.getComponentById(
|
||||||
|
val.to.owner
|
||||||
|
).inputPins[val.to.index]
|
||||||
this.wireManager.tryResolving()
|
this.wireManager.tryResolving()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -614,10 +722,14 @@ export class ComponentManager {
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
for (let i = 0; i < this.commandHistory.length; i++) {
|
for (let i = 0; i < this.commandHistory.length; i++) {
|
||||||
const element = this.commandHistory[i];
|
const element = this.commandHistory[i]
|
||||||
this.commandHistoryStore.set(i.toString(), element)
|
this.commandHistoryStore.set(i.toString(), element)
|
||||||
}
|
}
|
||||||
this.store.set(this.name, this.state)
|
this.store.set(this.name, this.state)
|
||||||
success(`Saved the simulation ${this.name} succesfully!`, "", this.alertOptions)
|
success(
|
||||||
|
`Saved the simulation ${this.name} succesfully!`,
|
||||||
|
'',
|
||||||
|
this.alertOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { ComponentState, materialMode } from "../component/interfaces";
|
import { ComponentState, materialMode } from '../component/interfaces'
|
||||||
import { WireState } from "../wires/interface";
|
import { WireState } from '../wires/interface'
|
||||||
|
|
||||||
export interface ManagerState {
|
export interface ManagerState {
|
||||||
components: ComponentState[]
|
components: ComponentState[]
|
||||||
|
@ -20,4 +20,6 @@ export interface ComponentTemplate {
|
||||||
data: string
|
data: string
|
||||||
}
|
}
|
||||||
editable?: boolean
|
editable?: boolean
|
||||||
|
imported?: boolean
|
||||||
|
importCommand?: string
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import { Singleton } from "@eix/utils";
|
import { Singleton } from '@eix/utils'
|
||||||
import { Pin } from "../pin";
|
import { Pin } from '../pin'
|
||||||
import { Wire } from "./wire";
|
import { Wire } from './wire'
|
||||||
import { svg } from "lit-html";
|
import { svg } from 'lit-html'
|
||||||
import { subscribe } from "lit-rx";
|
import { subscribe } from 'lit-rx'
|
||||||
import { Subject } from "rxjs";
|
import { Subject, combineLatest } from 'rxjs'
|
||||||
import { WireStateVal } from "./interface";
|
import { WireStateVal } from './interface'
|
||||||
|
import { merge, map } from 'rxjs/operators'
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
export class WireManager {
|
export class WireManager {
|
||||||
|
@ -18,17 +19,16 @@ export class WireManager {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
public add(data: Pin) {
|
public add(data: Pin) {
|
||||||
if (data.allowWrite) //output
|
if (data.allowWrite)
|
||||||
|
//output
|
||||||
this.start = data
|
this.start = data
|
||||||
else
|
else this.end = data
|
||||||
this.end = data
|
|
||||||
|
|
||||||
this.tryResolving()
|
this.tryResolving()
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
for (let i of this.wires)
|
for (let i of this.wires) i.dispose()
|
||||||
i.dispose()
|
|
||||||
|
|
||||||
this.wires = []
|
this.wires = []
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,7 @@ export class WireManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private canBind(end: Pin) {
|
private canBind(end: Pin) {
|
||||||
if (this.wires.find(val => val.output === end))
|
if (this.wires.find(val => val.output === end)) return false
|
||||||
return false
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,27 +56,51 @@ export class WireManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
get svg() {
|
get svg() {
|
||||||
return svg`${this.wires.map((val) => {
|
return svg`${this.wires.map(val => {
|
||||||
const i = val.input.of
|
const i = val.input.of
|
||||||
const o = val.output.of
|
const o = val.output.of
|
||||||
const inputIndex = i.outputPins.indexOf(val.input)
|
const inputIndex = i.outputPins.indexOf(val.input)
|
||||||
const input = i.piny(false, inputIndex)
|
const inputY = i.piny(false, inputIndex)
|
||||||
const output = o.piny(true, o.inputPins.indexOf(val.output))
|
const outputY = o.piny(true, o.inputPins.indexOf(val.output))
|
||||||
|
|
||||||
|
const output = [o.pinx(true, 20), outputY]
|
||||||
|
const input = [i.pinx(false, 20), inputY]
|
||||||
|
const midX = combineLatest(output[0], input[0]).pipe(
|
||||||
|
map(values => {
|
||||||
|
return (values[0] + values[1]) / 2
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const mid1 = [midX, outputY]
|
||||||
|
const mid2 = [midX, inputY]
|
||||||
|
|
||||||
|
const d = combineLatest<number[]>(
|
||||||
|
...output,
|
||||||
|
...mid1,
|
||||||
|
...mid2,
|
||||||
|
...input
|
||||||
|
).pipe(
|
||||||
|
map(
|
||||||
|
points =>
|
||||||
|
`M ${points.slice(0, 2).join(' ')} C ${points
|
||||||
|
.slice(2)
|
||||||
|
.join(' ')}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return svg`
|
return svg`
|
||||||
<line x2=${subscribe(i.pinx(false, 20))}
|
<path d=${subscribe(d)}
|
||||||
x1=${subscribe(o.pinx(true, 20))}
|
|
||||||
y2=${subscribe(input)}
|
|
||||||
y1=${subscribe(output)}
|
|
||||||
stroke=${subscribe(val.input.svgColor)}
|
stroke=${subscribe(val.input.svgColor)}
|
||||||
stroke-width=10
|
stroke-width=10
|
||||||
@click=${() => this.remove(val)}
|
fill="rgba(0,0,0,0)"
|
||||||
>
|
@click=${() => this.remove(val)} />
|
||||||
</line>
|
`
|
||||||
`})}`
|
})}`
|
||||||
}
|
}
|
||||||
|
|
||||||
get state() {
|
get state() {
|
||||||
return this.wires.map((val): WireStateVal => ({
|
return this.wires.map(
|
||||||
|
(val): WireStateVal => ({
|
||||||
from: {
|
from: {
|
||||||
owner: val.input.of.id,
|
owner: val.input.of.id,
|
||||||
index: val.input.of.outputPins.indexOf(val.input)
|
index: val.input.of.outputPins.indexOf(val.input)
|
||||||
|
@ -86,6 +109,7 @@ export class WireManager {
|
||||||
owner: val.output.of.id,
|
owner: val.output.of.id,
|
||||||
index: val.output.of.inputPins.indexOf(val.output)
|
index: val.output.of.inputPins.indexOf(val.output)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
265
src/ts/main.ts
265
src/ts/main.ts
|
@ -1,11 +1,12 @@
|
||||||
import { render, html } from "lit-html"
|
import { render, html } from 'lit-html'
|
||||||
import { subscribe } from "lit-rx"
|
import { subscribe } from 'lit-rx'
|
||||||
import { Screen } from "./common/screen.ts";
|
import { Screen } from './common/screen.ts'
|
||||||
import { ComponentManager } from "./common/componentManager";
|
import { ComponentManager } from './common/componentManager'
|
||||||
import { map } from "rxjs/operators";
|
import { map } from 'rxjs/operators'
|
||||||
import { MDCMenu } from '@material/menu';
|
import { MDCMenu } from '@material/menu'
|
||||||
import { error } from "toastr"
|
import { error } from 'toastr'
|
||||||
import { modal } from "./common/modals";
|
import { modal } from './common/modals'
|
||||||
|
import { importComponent } from './common/componentImporter/importComponent'
|
||||||
|
|
||||||
const screen = new Screen()
|
const screen = new Screen()
|
||||||
|
|
||||||
|
@ -13,13 +14,18 @@ export const manager = new ComponentManager()
|
||||||
manager.save()
|
manager.save()
|
||||||
manager.update()
|
manager.update()
|
||||||
|
|
||||||
window.onerror = (message: string, url: string, lineNumber: number): boolean => {
|
window.onerror = (
|
||||||
error(message, "", {
|
message: string,
|
||||||
|
url: string,
|
||||||
|
lineNumber: number
|
||||||
|
): boolean => {
|
||||||
|
error(message, '', {
|
||||||
...manager.alertOptions,
|
...manager.alertOptions,
|
||||||
onclick: () => modal({
|
onclick: () =>
|
||||||
no: "",
|
modal({
|
||||||
yes: "close",
|
no: '',
|
||||||
title: "Error",
|
yes: 'close',
|
||||||
|
title: 'Error',
|
||||||
content: html`
|
content: html`
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -39,45 +45,44 @@ window.onerror = (message: string, url: string, lineNumber: number): boolean =>
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
};
|
|
||||||
|
|
||||||
const handleEvent = <T>(e: T, func: (e: T) => any) => {
|
|
||||||
if (manager.barAlpha.value == "0")
|
|
||||||
func(e)
|
|
||||||
else if (manager.barAlpha.value == "1"
|
|
||||||
&& (e as unknown as MouseEvent).type == "mousedown"
|
|
||||||
&& (e as unknown as MouseEvent).target != document.getElementById("nameInput"))
|
|
||||||
manager.barAlpha.next("0")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveHandler = (e: MouseEvent) => handleEvent(e, (e: MouseEvent) => {
|
const handleEvent = <T>(e: T, func: (e: T) => any) => {
|
||||||
|
if (manager.barAlpha.value == '0') func(e)
|
||||||
|
else if (
|
||||||
|
manager.barAlpha.value == '1' &&
|
||||||
|
((e as unknown) as MouseEvent).type == 'mousedown' &&
|
||||||
|
((e as unknown) as MouseEvent).target !=
|
||||||
|
document.getElementById('nameInput')
|
||||||
|
)
|
||||||
|
manager.barAlpha.next('0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveHandler = (e: MouseEvent) =>
|
||||||
|
handleEvent(e, (e: MouseEvent) => {
|
||||||
manager.handleMouseMove(e)
|
manager.handleMouseMove(e)
|
||||||
screen.updateMouse(e)
|
screen.updateMouse(e)
|
||||||
})
|
})
|
||||||
|
|
||||||
render(html`
|
render(
|
||||||
|
html`
|
||||||
<div @mousemove=${moveHandler}
|
<div @mousemove=${moveHandler}
|
||||||
@touchmove=${moveHandler}
|
@touchmove=${moveHandler}
|
||||||
@mousedown=${(e: MouseEvent) => handleEvent(e, () =>
|
@mousedown=${(e: MouseEvent) =>
|
||||||
manager.handleMouseDown()
|
handleEvent(e, () => manager.handleMouseDown())}
|
||||||
)}
|
@touchdown=${(e: MouseEvent) =>
|
||||||
@touchdown=${(e: MouseEvent) => handleEvent(e, () =>
|
handleEvent(e, () => manager.handleMouseDown())}
|
||||||
manager.handleMouseDown()
|
@mouseup=${(e: MouseEvent) =>
|
||||||
)}
|
handleEvent(e, () => manager.handleMouseUp())}
|
||||||
@mouseup=${(e: MouseEvent) => handleEvent(e, () =>
|
@touchup=${(e: MouseEvent) =>
|
||||||
manager.handleMouseUp()
|
handleEvent(e, () => manager.handleMouseUp())}
|
||||||
)}
|
@wheel=${(e: MouseEvent) =>
|
||||||
@touchup=${(e: MouseEvent) => handleEvent(e, () =>
|
handleEvent(e, (e: WheelEvent) => screen.handleScroll(e))}>
|
||||||
manager.handleMouseUp()
|
|
||||||
)}
|
|
||||||
@wheel=${(e: MouseEvent) => handleEvent(e, (e: WheelEvent) =>
|
|
||||||
screen.handleScroll(e)
|
|
||||||
)}>
|
|
||||||
|
|
||||||
<div id=${subscribe(manager.barAlpha.pipe(map(val =>
|
<div id=${subscribe(
|
||||||
(val == "1") ? "shown" : ""
|
manager.barAlpha.pipe(map(val => (val == '1' ? 'shown' : '')))
|
||||||
)))}
|
)}
|
||||||
class=createBar>
|
class=createBar>
|
||||||
<div class="topContainer">
|
<div class="topContainer">
|
||||||
<div>
|
<div>
|
||||||
|
@ -97,7 +102,8 @@ render(html`
|
||||||
<aside class="mdc-drawer main-sidebar">
|
<aside class="mdc-drawer main-sidebar">
|
||||||
<div class="mdc-drawer__content">
|
<div class="mdc-drawer__content">
|
||||||
<nav class="mdc-list">
|
<nav class="mdc-list">
|
||||||
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" @click=${() => manager.prepareNewSimulation()}>
|
<a class="mdc-list-item mdc-list-item--activated" href="#" aria-current="page" @click=${() =>
|
||||||
|
manager.prepareNewSimulation()}>
|
||||||
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">note_add</i>
|
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">note_add</i>
|
||||||
<span class="mdc-list-item__text">Create new simulation</span>
|
<span class="mdc-list-item__text">Create new simulation</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -132,61 +138,160 @@ render(html`
|
||||||
|
|
||||||
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="saveMenu">
|
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="saveMenu">
|
||||||
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
||||||
${subscribe(manager.saves.pipe(map(_ => _.map(val => html`
|
${subscribe(
|
||||||
<li class= "mdc-list-item" role = "menuitem" @click=${() => manager.switchTo(val)}>
|
manager.saves.pipe(
|
||||||
<span class="mdc-list-item__text"> ${val} </span>
|
map(_ =>
|
||||||
<span class="material-icons mdc-list-item__meta" @click=${() => manager.delete(val)}> delete </span>
|
_.map(
|
||||||
</li>`
|
val => html`
|
||||||
))))}
|
<li
|
||||||
|
class="mdc-list-item"
|
||||||
|
role="menuitem"
|
||||||
|
@click=${() => manager.switchTo(val)}
|
||||||
|
>
|
||||||
|
<span class="mdc-list-item__text">
|
||||||
|
${val}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="material-icons mdc-list-item__meta"
|
||||||
|
@click=${() => manager.delete(val)}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="gateMenu">
|
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="gateMenu">
|
||||||
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
||||||
${subscribe(manager.gates.pipe(map(_ => [..._].sort().map(val => html`
|
${subscribe(
|
||||||
<li class= "mdc-list-item" role = "menuitem" @click=${() => manager.add(val)}>
|
manager.gates.pipe(
|
||||||
<span class="mdc-list-item__text"> ${val} </span>
|
map(gates =>
|
||||||
${(manager.templateStore.store.get(val).editable ?
|
[...gates].sort().map(name => {
|
||||||
html`      <span class="material-icons mdc-list-item__meta" @click=${
|
const gate = manager.templateStore.store.get(name)
|
||||||
() => manager.templateStore.store.delete(val)
|
return html`
|
||||||
}> delete </span>` :
|
<li
|
||||||
""
|
class="mdc-list-item"
|
||||||
|
role="menuitem"
|
||||||
|
@click=${() => manager.add(name)}
|
||||||
|
>
|
||||||
|
<span class="mdc-list-item__text">
|
||||||
|
${name}
|
||||||
|
</span>
|
||||||
|
${gate.imported || gate.editable
|
||||||
|
? html`
|
||||||
|
     
|
||||||
|
<span
|
||||||
|
class="material-icons mdc-list-item__meta"
|
||||||
|
@click=${(e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
manager.templateStore.store.delete(
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</span>
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</li>`
|
|
||||||
))))}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="fileMenu">
|
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="fileMenu">
|
||||||
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
||||||
${[...Object.keys(manager.file)].sort().map(key => html`
|
${[...Object.keys(manager.file)].sort().map(
|
||||||
<li class= "mdc-list-item" role = "menuitem" @click=${() => manager.file[key]()}>
|
key => html`
|
||||||
|
<li
|
||||||
|
class="mdc-list-item"
|
||||||
|
role="menuitem"
|
||||||
|
@click=${() => manager.file[key]()}
|
||||||
|
>
|
||||||
<span class="mdc-list-item__text">${key}</span>
|
<span class="mdc-list-item__text">${key}</span>
|
||||||
${manager.shortcuts[key] ? html`
|
${manager.shortcuts[key]
|
||||||
<span class="mdc-list-item__meta">      ${manager.shortcuts[key]}</span>
|
? html`
|
||||||
` : ""}
|
<span class="mdc-list-item__meta"
|
||||||
</li>`
|
>     
|
||||||
|
${manager.shortcuts[key]}</span
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="customGateMenu">
|
<div class="mdc-menu mdc-menu-surface mdc-theme--primary-bg mdc-theme--on-primary" id="customGateMenu">
|
||||||
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
|
||||||
${subscribe(manager.gates.pipe(map(_ => _
|
${subscribe(
|
||||||
.filter(val => manager.templateStore.store.get(val).editable)
|
manager.gates.pipe(
|
||||||
.map(val => html`
|
map(gates =>
|
||||||
<li class= "mdc-list-item" role = "menuitem" @click=${() => manager.edit(val)}>
|
gates
|
||||||
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">edit</i>
|
.map(name => manager.templateStore.store.get(name))
|
||||||
<span class="mdc-list-item__text"> ${val} </span>
|
.filter(gate => gate.editable || gate.imported)
|
||||||
</li>`
|
.map(
|
||||||
))))}
|
gate => html`
|
||||||
<li class= "mdc-list-item" role = "menuitem" @click=${() => manager.newGate()}>
|
<li
|
||||||
|
class="mdc-list-item"
|
||||||
|
role="menuitem"
|
||||||
|
@click=${(e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (gate.editable) {
|
||||||
|
manager.edit(gate.name)
|
||||||
|
} else {
|
||||||
|
importComponent(
|
||||||
|
manager,
|
||||||
|
gate.importCommand
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="material-icons mdc-list-item__graphic"
|
||||||
|
aria-hidden="true"
|
||||||
|
>${gate.imported
|
||||||
|
? 'refresh'
|
||||||
|
: 'edit'}</i
|
||||||
|
>
|
||||||
|
<span class="mdc-list-item__text">
|
||||||
|
${gate.name}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<li class= "mdc-list-item" role = "menuitem" @click=${() =>
|
||||||
|
manager.newGate()}>
|
||||||
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">add</i>
|
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">add</i>
|
||||||
<span class="mdc-list-item__text"> New custom gate </span>
|
<span class="mdc-list-item__text"> New custom gate </span>
|
||||||
</li>
|
</li>
|
||||||
|
<li class= "mdc-list-item" role = "menuitem" @click=${() =>
|
||||||
|
manager.importGate()}>
|
||||||
|
<i class="material-icons mdc-list-item__graphic" aria-hidden="true">add</i>
|
||||||
|
<span class="mdc-list-item__text"> Import new gate </span>
|
||||||
|
<span class="mdc-list-item__meta">      ctrl + g</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
`, document.body)
|
`,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
new MDCMenu(document.querySelector('#saveMenu')),
|
new MDCMenu(document.querySelector('#saveMenu')),
|
||||||
|
@ -196,8 +301,8 @@ const menus = [
|
||||||
]
|
]
|
||||||
menus.forEach(menu => menu.hoistMenuToBody())
|
menus.forEach(menu => menu.hoistMenuToBody())
|
||||||
menus[0].setAnchorElement(document.querySelector(`#openSimulation`))
|
menus[0].setAnchorElement(document.querySelector(`#openSimulation`))
|
||||||
menus[1].setAnchorElement(document.querySelector("#openGates"))
|
menus[1].setAnchorElement(document.querySelector('#openGates'))
|
||||||
menus[2].setAnchorElement(document.querySelector("#openFile"))
|
menus[2].setAnchorElement(document.querySelector('#openFile'))
|
||||||
menus[3].setAnchorElement(document.querySelector("#openCustomGates"))
|
menus[3].setAnchorElement(document.querySelector('#openCustomGates'))
|
||||||
|
|
||||||
manager.update()
|
manager.update()
|
Loading…
Reference in a new issue