From b06a4c744161615c2235b6d3ff7d3a4d6d934962 Mon Sep 17 00:00:00 2001 From: Matei Adriel <rafaeladriel11@gmail.com> Date: Thu, 11 Jul 2019 20:51:14 +0300 Subject: [PATCH] curves for wires & importing logic gates --- .prettierrc.js | 7 + .vscode/settings.json | 6 + README.txt | 2 + deploy.ts | 44 +- docs/assets/gist.png | Bin 0 -> 7262 bytes docs/assets/gist_url.png | Bin 0 -> 3103 bytes docs/controls.md | 93 +++ docs/import.md | 13 + docs/main.md | 10 + docs/url.md | 13 + package-lock.json | 41 +- src/index.html | 264 ++++---- src/ts/common/component/interfaces.ts | 12 +- src/ts/common/component/material.ts | 33 +- src/ts/common/componentImporter/evalImport.ts | 23 + src/ts/common/componentImporter/fetchJson.ts | 6 + src/ts/common/componentImporter/getGist.ts | 27 + .../componentImporter/importComponent.ts | 62 ++ .../componentManager/componentManager.ts | 606 +++++++++++------- src/ts/common/componentManager/interfaces.ts | 8 +- src/ts/common/wires/wireManager.ts | 100 +-- src/ts/main.ts | 335 ++++++---- 22 files changed, 1117 insertions(+), 588 deletions(-) create mode 100644 .prettierrc.js create mode 100644 .vscode/settings.json create mode 100644 README.txt create mode 100644 docs/assets/gist.png create mode 100644 docs/assets/gist_url.png create mode 100644 docs/controls.md create mode 100644 docs/import.md create mode 100644 docs/main.md create mode 100644 docs/url.md create mode 100644 src/ts/common/componentImporter/evalImport.ts create mode 100644 src/ts/common/componentImporter/fetchJson.ts create mode 100644 src/ts/common/componentImporter/getGist.ts create mode 100644 src/ts/common/componentImporter/importComponent.ts diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..8347fc4 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: false, + trailingComma: 'none', + singleQuote: true, + printWidth: 80, + tabWidth: 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..106395f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "eslint.enable": true, + "editor.formatOnSave": true, + "prettier.eslintIntegration": true, + "eslint.validate": ["typescript", "typescriptreact"] +} \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..2214bae --- /dev/null +++ b/README.txt @@ -0,0 +1,2 @@ +Pentru a rula aplicatia, deschideti dist/index.html. +Codul sursa se afla in folderul src. \ No newline at end of file diff --git a/deploy.ts b/deploy.ts index 077a072..811d559 100644 --- a/deploy.ts +++ b/deploy.ts @@ -1,53 +1,47 @@ -const { publish } = require("gh-pages") -const { exec } = require("child_process") -const { random } = require("random-emoji") +const { publish } = require('gh-pages') +const { exec } = require('child_process') +const { random } = require('random-emoji') // const { publish } = require("gh-pages") const args = process.argv.splice(2) const randomEmoji = () => random({ count: 1 })[0].character -const mFlag = ((args.indexOf("--message") + 1) || (args.indexOf("-m") + 1)) - 1 -const message = `${randomEmoji()} ${(mFlag >= 0) ? args[mFlag + 1] : "automated update"} ${randomEmoji()}` +const mFlag = (args.indexOf('--message') + 1 || args.indexOf('-m') + 1) - 1 +const message = `${mFlag >= 0 ? args[mFlag + 1] : 'automated update'}` -console.log("Deploying..."); +console.log('Deploying...') const run = (command: string): Promise<string> => { return new Promise((res, rej) => { console.log(`🏃 Running: '${command}'`) //@ts-ignore exec(command, (err, stdout, stderr) => { - if (err != null) - rej(err) - else if (typeof (stderr) != "string") - rej(new Error(stderr)) - else - res(stdout) + if (err != null) rej(err) + else if (typeof stderr != 'string') rej(new Error(stderr)) + else res(stdout) }) }) } - -(async () => { +;(async () => { try { - if (!args.includes("--skipBuild") && !args.includes("-sb")) - await run("npm run build") - await run("git add .") + if (!args.includes('--skipBuild') && !args.includes('-sb')) + await run('npm run build') + await run('git add .') await run(`git commit -m " ${message} "`) - await run("git push origin master") + await run('git push origin master') await new Promise((res, rej) => { - console.log("🏃 Updating github pages") + console.log('🏃 Updating github pages') //@ts-ignore - publish("dist", (err) => { - if (err) - rej(err) + publish('dist', err => { + if (err) rej(err) console.log(`😄 Succesfully published to github pages`) res(true) }) }) - } - catch (err) { + } catch (err) { console.log(`😭 Something went wrong: ${err}`) } -})() \ No newline at end of file +})() diff --git a/docs/assets/gist.png b/docs/assets/gist.png new file mode 100644 index 0000000000000000000000000000000000000000..2a8c226b9e0f4c546e35af55e4fd6057ed63dd44 GIT binary patch literal 7262 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 GGywqUP1NlG literal 0 HcmV?d00001 diff --git a/docs/assets/gist_url.png b/docs/assets/gist_url.png new file mode 100644 index 0000000000000000000000000000000000000000..f067e15923df551e3e69af98435c9c8d438c52d4 GIT binary patch literal 3103 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 literal 0 HcmV?d00001 diff --git a/docs/controls.md b/docs/controls.md new file mode 100644 index 0000000..b815ce9 --- /dev/null +++ b/docs/controls.md @@ -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 diff --git a/docs/import.md b/docs/import.md new file mode 100644 index 0000000..ccdd87b --- /dev/null +++ b/docs/import.md @@ -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)**) diff --git a/docs/main.md b/docs/main.md new file mode 100644 index 0000000..ddf3f74 --- /dev/null +++ b/docs/main.md @@ -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) diff --git a/docs/url.md b/docs/url.md new file mode 100644 index 0000000..dac88a2 --- /dev/null +++ b/docs/url.md @@ -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 +``` + + + +Else, the parser will just try to fetch directly from the full string diff --git a/package-lock.json b/package-lock.json index 450faa4..a83d554 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2891,7 +2891,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2912,12 +2913,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2932,17 +2935,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3059,7 +3065,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3071,6 +3078,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3085,6 +3093,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3092,12 +3101,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3116,6 +3127,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3196,7 +3208,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3208,6 +3221,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3293,7 +3307,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3329,6 +3344,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3348,6 +3364,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3391,12 +3408,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/index.html b/src/index.html index 1e19d1d..e4abec8 100644 --- a/src/index.html +++ b/src/index.html @@ -1,146 +1,154 @@ <!DOCTYPE html> <html lang="en"> + <head> + <title>Logic gate simulator</title> -<head> - <title>Game</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> + html, + body { + padding: 0; + margin: 0; + height: 100%; + width: 100%; + } - <style> - html, - body { - padding: 0; - margin: 0; - height: 100%; - width: 100%; - } + body { + background: #222; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } - body { - background: #222; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - } + .sk-folding-cube { + margin: 20px auto; + width: 40px; + height: 40px; + position: relative; + -webkit-transform: rotateZ(45deg); + transform: rotateZ(45deg); + } - .sk-folding-cube { - margin: 20px auto; - width: 40px; - height: 40px; - position: relative; - -webkit-transform: rotateZ(45deg); - transform: rotateZ(45deg); - } + .sk-folding-cube .sk-cube { + float: left; + width: 50%; + height: 50%; + position: relative; + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } - .sk-folding-cube .sk-cube { - float: left; - width: 50%; - height: 50%; - position: relative; - -webkit-transform: scale(1.1); - -ms-transform: scale(1.1); - transform: scale(1.1); - } + .sk-folding-cube .sk-cube:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #bbb; + -webkit-animation: sk-foldCubeAngle 2.4s infinite linear both; + animation: sk-foldCubeAngle 2.4s infinite linear both; + -webkit-transform-origin: 100% 100%; + -ms-transform-origin: 100% 100%; + transform-origin: 100% 100%; + } - .sk-folding-cube .sk-cube:before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #bbb; - -webkit-animation: sk-foldCubeAngle 2.4s infinite linear both; - animation: sk-foldCubeAngle 2.4s infinite linear both; - -webkit-transform-origin: 100% 100%; - -ms-transform-origin: 100% 100%; - transform-origin: 100% 100%; - } + .sk-folding-cube .sk-cube2 { + -webkit-transform: scale(1.1) rotateZ(90deg); + transform: scale(1.1) rotateZ(90deg); + } - .sk-folding-cube .sk-cube2 { - -webkit-transform: scale(1.1) rotateZ(90deg); - transform: scale(1.1) rotateZ(90deg); - } + .sk-folding-cube .sk-cube3 { + -webkit-transform: scale(1.1) rotateZ(180deg); + transform: scale(1.1) rotateZ(180deg); + } - .sk-folding-cube .sk-cube3 { - -webkit-transform: scale(1.1) rotateZ(180deg); - transform: scale(1.1) rotateZ(180deg); - } + .sk-folding-cube .sk-cube4 { + -webkit-transform: scale(1.1) rotateZ(270deg); + transform: scale(1.1) rotateZ(270deg); + } - .sk-folding-cube .sk-cube4 { - -webkit-transform: scale(1.1) rotateZ(270deg); - transform: scale(1.1) rotateZ(270deg); - } + .sk-folding-cube .sk-cube2:before { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; + } - .sk-folding-cube .sk-cube2:before { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; - } + .sk-folding-cube .sk-cube3:before { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; + } - .sk-folding-cube .sk-cube3:before { - -webkit-animation-delay: 0.6s; - animation-delay: 0.6s; - } + .sk-folding-cube .sk-cube4:before { + -webkit-animation-delay: 0.9s; + animation-delay: 0.9s; + } - .sk-folding-cube .sk-cube4:before { - -webkit-animation-delay: 0.9s; - animation-delay: 0.9s; - } + @-webkit-keyframes sk-foldCubeAngle { + 0%, + 10% { + -webkit-transform: perspective(140px) rotateX(-180deg); + transform: perspective(140px) rotateX(-180deg); + opacity: 0; + } + 25%, + 75% { + -webkit-transform: perspective(140px) rotateX(0deg); + transform: perspective(140px) rotateX(0deg); + opacity: 1; + } + 90%, + 100% { + -webkit-transform: perspective(140px) rotateY(180deg); + transform: perspective(140px) rotateY(180deg); + opacity: 0; + } + } - @-webkit-keyframes sk-foldCubeAngle { - 0%, - 10% { - -webkit-transform: perspective(140px) rotateX(-180deg); - transform: perspective(140px) rotateX(-180deg); - opacity: 0; - } - 25%, - 75% { - -webkit-transform: perspective(140px) rotateX(0deg); - transform: perspective(140px) rotateX(0deg); - opacity: 1; - } - 90%, - 100% { - -webkit-transform: perspective(140px) rotateY(180deg); - transform: perspective(140px) rotateY(180deg); - opacity: 0; - } - } + @keyframes sk-foldCubeAngle { + 0%, + 10% { + -webkit-transform: perspective(140px) rotateX(-180deg); + transform: perspective(140px) rotateX(-180deg); + opacity: 0; + } + 25%, + 75% { + -webkit-transform: perspective(140px) rotateX(0deg); + transform: perspective(140px) rotateX(0deg); + opacity: 1; + } + 90%, + 100% { + -webkit-transform: perspective(140px) rotateY(180deg); + transform: perspective(140px) rotateY(180deg); + opacity: 0; + } + } + </style> - @keyframes sk-foldCubeAngle { - 0%, - 10% { - -webkit-transform: perspective(140px) rotateX(-180deg); - transform: perspective(140px) rotateX(-180deg); - opacity: 0; - } - 25%, - 75% { - -webkit-transform: perspective(140px) rotateX(0deg); - transform: perspective(140px) rotateX(0deg); - opacity: 1; - } - 90%, - 100% { - -webkit-transform: perspective(140px) rotateY(180deg); - transform: perspective(140px) rotateY(180deg); - opacity: 0; - } - } - </style> + <link + rel="stylesheet" + href="https://fonts.googleapis.com/icon?family=Material+Icons" + /> + <link rel="stylesheet" href="style.css" /> + </head> - <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> - <link rel='stylesheet' href='style.css'> -</head> - -<body ondragstart="return false;" ondrop="return false;" oncontextmenu="return false"> - <div class="sk-folding-cube"> - <div class="sk-cube1 sk-cube"></div> - <div class="sk-cube2 sk-cube"></div> - <div class="sk-cube4 sk-cube"></div> - <div class="sk-cube3 sk-cube"></div> - </div> -</body> - -</html> \ No newline at end of file + <body + ondragstart="return false;" + ondrop="return false;" + oncontextmenu="return false" + > + <div class="sk-folding-cube"> + <div class="sk-cube1 sk-cube"></div> + <div class="sk-cube2 sk-cube"></div> + <div class="sk-cube4 sk-cube"></div> + <div class="sk-cube3 sk-cube"></div> + </div> + </body> +</html> diff --git a/src/ts/common/component/interfaces.ts b/src/ts/common/component/interfaces.ts index fd8e63d..45a2fcf 100644 --- a/src/ts/common/component/interfaces.ts +++ b/src/ts/common/component/interfaces.ts @@ -1,8 +1,8 @@ -import { Pin } from "../pin"; +import { Pin } from '../pin' export interface ComponentState { - position: [number,number] - scale: [number,number] + position: [number, number] + scale: [number, number] template: string id: number } @@ -11,8 +11,8 @@ export interface activationContext { inputs: Pin[] outputs: Pin[] succes: (mes: string) => any - error: (mes:string) => any - color: (color:string) => void + error: (mes: string) => any + color: (color: string) => void } -export type materialMode = "standard_image" | "color" \ No newline at end of file +export type materialMode = 'standard_image' | 'color' | 'url' diff --git a/src/ts/common/component/material.ts b/src/ts/common/component/material.ts index 21f33f5..2de609f 100644 --- a/src/ts/common/component/material.ts +++ b/src/ts/common/component/material.ts @@ -1,6 +1,6 @@ -import { svg, Part } from "lit-html"; -import { BehaviorSubject } from "rxjs"; -import { materialMode } from "./interfaces"; +import { svg, Part } from 'lit-html' +import { BehaviorSubject } from 'rxjs' +import { materialMode } from './interfaces' declare function require<T>(path: string): T @@ -10,25 +10,28 @@ export class Material { private static images: { [key: string]: string } = { - and: require("../../../assets/and_gate.jpg"), - or: require("../../../assets/or_gate.png"), - xor: require("../../../assets/xor_gate.png"), - nor: require("../../../assets/nor_gate.png") - } + and: require('../../../assets/and_gate.jpg'), + or: require('../../../assets/or_gate.png'), + xor: require('../../../assets/xor_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: string, public name: materialMode | string) { - if (this.mode === "color") - this.color.next(name) + constructor(public mode: materialMode, public data: string) { + if (this.mode === 'color') this.color.next(data) } 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}> <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> </foreignobject>` } -} \ No newline at end of file +} diff --git a/src/ts/common/componentImporter/evalImport.ts b/src/ts/common/componentImporter/evalImport.ts new file mode 100644 index 0000000..1d4ddf0 --- /dev/null +++ b/src/ts/common/componentImporter/evalImport.ts @@ -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 +} diff --git a/src/ts/common/componentImporter/fetchJson.ts b/src/ts/common/componentImporter/fetchJson.ts new file mode 100644 index 0000000..e091245 --- /dev/null +++ b/src/ts/common/componentImporter/fetchJson.ts @@ -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 +} diff --git a/src/ts/common/componentImporter/getGist.ts b/src/ts/common/componentImporter/getGist.ts new file mode 100644 index 0000000..98bb3d3 --- /dev/null +++ b/src/ts/common/componentImporter/getGist.ts @@ -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 +} diff --git a/src/ts/common/componentImporter/importComponent.ts b/src/ts/common/componentImporter/importComponent.ts new file mode 100644 index 0000000..8faf643 --- /dev/null +++ b/src/ts/common/componentImporter/importComponent.ts @@ -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 +} diff --git a/src/ts/common/componentManager/componentManager.ts b/src/ts/common/componentManager/componentManager.ts index 9d9d63a..9979636 100644 --- a/src/ts/common/componentManager/componentManager.ts +++ b/src/ts/common/componentManager/componentManager.ts @@ -1,38 +1,38 @@ -import { Singleton } from "@eix/utils"; -import { Component } from "../component"; -import { Subject, BehaviorSubject, fromEvent } from "rxjs"; -import { svg, SVGTemplateResult, html } from "lit-html"; -import { subscribe } from "lit-rx"; -import { Screen } from "../screen.ts"; -import { ManagerState, ComponentTemplate } from "./interfaces"; -import { Store } from "../store"; -import { KeyboardInput } from "@eix/input" -import { success, error } from "toastr" -import { ComponentTemplateStore } from "./componentTemplateStore"; -import { alertOptions } from "./alertOptions"; -import { WireManager } from "../wires"; -import { runCounter } from "../component/runCounter"; -import { Settings } from "../store/settings"; -import { download } from "./download"; -import { modal } from "../modals"; -import { map } from "rxjs/operators"; -import { persistent } from "../store/persistent"; -import { MDCTextField } from '@material/textfield'; - -const defaultName = "default" +import { Singleton } from '@eix/utils' +import { Component } from '../component' +import { Subject, BehaviorSubject, fromEvent } from 'rxjs' +import { svg, SVGTemplateResult, html } from 'lit-html' +import { subscribe } from 'lit-rx' +import { Screen } from '../screen.ts' +import { ManagerState, ComponentTemplate } from './interfaces' +import { Store } from '../store' +import { KeyboardInput } from '@eix/input' +import { success, error } from 'toastr' +import { ComponentTemplateStore } from './componentTemplateStore' +import { alertOptions } from './alertOptions' +import { WireManager } from '../wires' +import { runCounter } from '../component/runCounter' +import { Settings } from '../store/settings' +import { download } from './download' +import { modal } from '../modals' +import { map } from 'rxjs/operators' +import { persistent } from '../store/persistent' +import { MDCTextField } from '@material/textfield' +import { importComponent } from '../componentImporter/importComponent' +const defaultName = 'default' @Singleton export class ComponentManager { public components: Component[] = [] public svgs = new Subject<SVGTemplateResult>() - public placeholder = new BehaviorSubject("Create simulation") - public barAlpha = new BehaviorSubject<string>("0"); + public placeholder = new BehaviorSubject('Create simulation') + public barAlpha = new BehaviorSubject<string>('0') public wireManager = new WireManager() public onTop: Component public templateStore = new ComponentTemplateStore() - private temporaryCommnad = "" + private temporaryCommnad = '' private clicked = false private ignoreKeyDowns = false @@ -42,66 +42,79 @@ export class ComponentManager { offset: number scale: [number, number] } = { - offset: 50, - scale: [100, 100] - } + offset: 50, + scale: [100, 100] + } - private commandHistoryStore = new Store<string>("commandHistory") - private store = new Store<ManagerState>("simulationStates") + private commandHistoryStore = new Store<string>('commandHistory') + private store = new Store<ManagerState>('simulationStates') - private saveEvent = new KeyboardInput("s") - private createEvent = new KeyboardInput("m") - private closeInputEvent = new KeyboardInput("enter") - private ctrlEvent = new KeyboardInput("ctrl") - private palleteEvent = new KeyboardInput("p") - private undoEvent = new KeyboardInput("z") - private shiftEvent = new KeyboardInput("shift") - private refreshEvent = new KeyboardInput("r") - private clearEvent = new KeyboardInput("delete") - private upEvent = new KeyboardInput("up") - private downEvent = new KeyboardInput("down") + private saveEvent = new KeyboardInput('s') + private createEvent = new KeyboardInput('m') + private closeInputEvent = new KeyboardInput('enter') + private ctrlEvent = new KeyboardInput('ctrl') + private palleteEvent = new KeyboardInput('p') + private undoEvent = new KeyboardInput('z') + private shiftEvent = new KeyboardInput('shift') + private refreshEvent = new KeyboardInput('r') + private gEvent = new KeyboardInput('g') + private clearEvent = new KeyboardInput('delete') + private upEvent = new KeyboardInput('up') + private downEvent = new KeyboardInput('down') - @persistent<ComponentManager, string>(defaultName, "main") + @persistent<ComponentManager, string>(defaultName, 'main') public name: string public alertOptions = alertOptions private commandHistory: string[] = [] private commands: { - [key: string]: (ctx: ComponentManager, args: string[], flags: string[]) => any + [key: string]: ( + ctx: ComponentManager, + args: string[], + flags: string[] + ) => any } = { - clear(ctx: ComponentManager) { - ctx.clear() - }, - save(ctx: ComponentManager) { - ctx.save() - }, - ls(ctx: ComponentManager) { - const data = ctx.store.ls() - const message = data.join("\n") + clear(ctx: ComponentManager) { + ctx.clear() + }, + save(ctx: ComponentManager) { + ctx.save() + }, + ls(ctx: ComponentManager) { + const data = ctx.store.ls() + const message = data.join('\n') - success(message, "", ctx.alertOptions) - }, - help(ctx: ComponentManager) { - success(`Usage: <command> <br> + success(message, '', ctx.alertOptions) + }, + help(ctx: ComponentManager) { + success( + `Usage: <command> <br> Where <command> is one of: <ul> - ${Object.keys(ctx.commands).map(val => ` + ${Object.keys(ctx.commands) + .map( + val => ` <li>${val}</li> - `).join("")} + ` + ) + .join('')} </ul> - `, "", ctx.alertOptions) - }, - refresh(ctx: ComponentManager) { - ctx.refresh() - }, - rewind(ctx:ComponentManager){ - localStorage.clear() - success("Succesfully cleared localStorage!","",ctx.alertOptions) - }, - ctp: this.templateStore.commands.template, - settings: this.settings.commands, - download - } + `, + '', + ctx.alertOptions + ) + }, + refresh(ctx: ComponentManager) { + ctx.refresh() + }, + rewind(ctx: ComponentManager) { + localStorage.clear() + success('Succesfully cleared localStorage!', '', ctx.alertOptions) + }, + ctp: this.templateStore.commands.template, + settings: this.settings.commands, + download + } private inputMode: string public gates = this.templateStore.store.lsChanges @@ -110,24 +123,24 @@ export class ComponentManager { public file: { [key: string]: () => void } = { - clear: () => this.clear(), - clean: () => this.smartClear(), - save: () => this.save(), - undo: () => this.refresh(), - download: () => download(this, [], []), - delete: () => this.delete(this.name), - refresh: () => this.silentRefresh(true) - } + clear: () => this.clear(), + clean: () => this.smartClear(), + save: () => this.save(), + undo: () => this.refresh(), + download: () => download(this, [], []), + delete: () => this.delete(this.name), + refresh: () => this.silentRefresh(true) + } public shortcuts: { [key: string]: string } = { - clear: "shift delete", - clean: "delete", - save: "ctrl s", - undo: "ctrl z", - refresh: "ctrl r" - } + clear: 'shift delete', + clean: 'delete', + save: 'ctrl s', + undo: 'ctrl z', + refresh: 'ctrl r' + } constructor() { runCounter.increase() @@ -136,22 +149,22 @@ export class ComponentManager { this.refresh() - fromEvent(document.body, "keydown").subscribe((e: KeyboardEvent) => { - if (this.barAlpha.value == "1") { - const elem = document.getElementById("nameInput") + fromEvent(document.body, 'keydown').subscribe((e: KeyboardEvent) => { + if (this.barAlpha.value == '1') { + const elem = document.getElementById('nameInput') elem.focus() - } - else if (!this.ignoreKeyDowns) { + } else if (!this.ignoreKeyDowns) { e.preventDefault() } }) - fromEvent(document.body, "keyup").subscribe((e: MouseEvent) => { - if (this.barAlpha.value === "1") { - if (this.closeInputEvent.value) - this.create() - else if (this.inputMode === "command") { - const elem = <HTMLInputElement>document.getElementById("nameInput") + fromEvent(document.body, 'keyup').subscribe((e: MouseEvent) => { + if (this.barAlpha.value === '1') { + if (this.closeInputEvent.value) this.create() + else if (this.inputMode === 'command') { + const elem = <HTMLInputElement>( + document.getElementById('nameInput') + ) if (this.upEvent.value) { document.body.focus() e.preventDefault() @@ -159,10 +172,12 @@ export class ComponentManager { if (index) { //save drafts - if (index === -1) - this.temporaryCommnad = elem.value + if (index === -1) 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] } } @@ -173,36 +188,36 @@ export class ComponentManager { if (index > -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.createEvent.value) { this.prepareNewSimulation() - } - else if (this.shiftEvent.value && this.palleteEvent.value) { + } else if ( + this.shiftEvent.value && + this.palleteEvent.value + ) { this.preInput() - this.inputMode = "command" - this.placeholder.next("Command palette") - } - else if (this.saveEvent.value) { + this.inputMode = 'command' + this.placeholder.next('Command palette') + } else if (this.gEvent.value) { + this.importGate() + } else if (this.saveEvent.value) { this.save() - } - else if (this.undoEvent.value) { + } else if (this.undoEvent.value) { this.refresh() - } - else if (this.refreshEvent.value) { + } else if (this.refreshEvent.value) { this.silentRefresh(true) } - } - else if (this.clearEvent.value) { - if (this.shiftEvent.value) - this.clear() - else - this.smartClear() + } else if (this.clearEvent.value) { + if (this.shiftEvent.value) this.clear() + else this.smartClear() } } }) @@ -212,22 +227,20 @@ export class ComponentManager { this.update() // this.save() }) - if (this.saves.value.length === 0) - this.save() - + if (this.saves.value.length === 0) this.save() } private initEmptyGate(name: string) { const obj: ComponentTemplate = { inputs: 1, name, - version: "1.0.0", + version: '1.0.0', outputs: 1, - activation: "", + activation: '', editable: true, material: { - mode: "color", - data: "blue" + mode: 'color', + data: 'blue' } } @@ -238,44 +251,57 @@ export class ComponentManager { public newGate() { this.preInput() - this.inputMode = "gate" - this.placeholder.next("Gate name") + this.inputMode = 'gate' + this.placeholder.next('Gate name') + } + + public importGate() { + this.preInput() + this.inputMode = 'importGate' + this.placeholder.next('Gate url') } public prepareNewSimulation() { this.preInput() - this.inputMode = "create" - this.placeholder.next("Create simulation") + this.inputMode = 'create' + this.placeholder.next('Create simulation') } private preInput() { - const elem = <HTMLInputElement>document.getElementById("nameInput") - elem.value = "" - this.barAlpha.next("1") + const elem = <HTMLInputElement>document.getElementById('nameInput') + elem.value = '' + this.barAlpha.next('1') } private async create() { - const elem = <HTMLInputElement>document.getElementById("nameInput") - this.barAlpha.next("0") + const elem = <HTMLInputElement>document.getElementById('nameInput') + this.barAlpha.next('0') - if (this.inputMode === "create") { + if (this.inputMode === 'create') { 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") - this.eval(elem.value) - - else if (this.inputMode === "gate") - this.initEmptyGate(elem.value) + public succes(message: string) { + success(message, '', this.alertOptions) } private async handleDuplicateModal(name: string) { const result = await modal({ - title: "Warning", - content: html`There was already a simulation called ${name}, - are you sure you want to override it? - All your work will be lost!` + title: 'Warning', + content: html` + There was already a simulation called ${name}, are you sure you + want to override it? All your work will be lost! + ` }) return result @@ -286,46 +312,80 @@ export class ComponentManager { const gate = this.templateStore.store.get(name) modal({ - no: "", - yes: "save", + no: '', + yes: 'save', title: `Edit ${name}`, - content: html`${html` - <br> - <div class="mdc-text-field mdc-text-field--textarea"> - <textarea id="codeArea" class="mdc-text-field__input js" rows="8" cols="40">${ - gate.activation - }</textarea> - <div class="mdc-notched-outline"> - <div class="mdc-notched-outline__leading"></div> - <div class="mdc-notched-outline__notch"> - <label for="textarea" class="mdc-floating-label">Activation function</label> + content: html` + ${html` + <br /> + <div class="mdc-text-field mdc-text-field--textarea"> + <textarea + id="codeArea" + class="mdc-text-field__input js" + rows="8" + cols="40" + > +${gate.activation}</textarea + > + <div class="mdc-notched-outline"> + <div class="mdc-notched-outline__leading"></div> + <div class="mdc-notched-outline__notch"> + <label for="textarea" class="mdc-floating-label" + >Activation function</label + > + </div> + <div class="mdc-notched-outline__trailing"></div> </div> - <div class="mdc-notched-outline__trailing"></div> </div> - </div><br><br> - <div class="mdc-text-field" id="inputCount"> - <input 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><br><br> - <div class="mdc-text-field" id="outputCount"> - <input 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><br><br> - <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}> - <label class="mdc-floating-label" for="my-text-field">Outputs</label> - <div class="mdc-line-ripple"></div> - </div><br> - `}` + <br /><br /> + <div class="mdc-text-field" id="inputCount"> + <input + 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> + <br /><br /> + <div class="mdc-text-field" id="outputCount"> + <input + 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> + <br /><br /> + <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} + /> + <label class="mdc-floating-label" for="my-text-field" + >Color</label + > + <div class="mdc-line-ripple"></div> + </div> + <br /> + `} + ` }).then(val => { this.ignoreKeyDowns = false const elems: (HTMLInputElement | HTMLTextAreaElement)[] = [ - document.querySelector("#codeArea"), - document.querySelector(".inputCount-i"), - document.querySelector(".outputCount-i"), - document.querySelector(".color-i") + document.querySelector('#codeArea'), + document.querySelector('.inputCount-i'), + document.querySelector('.outputCount-i'), + document.querySelector('.color-i') ] const data = elems.map(val => val.value) @@ -334,21 +394,25 @@ export class ComponentManager { activation: data[0], inputs: Number(data[1]), outputs: Number(data[2]), - material:{ - mode: "color", + material: { + mode: 'color', data: data[3] } }) }) - new MDCTextField(document.querySelector('.mdc-text-field')); - new MDCTextField(document.querySelector('#outputCount')); - new MDCTextField(document.querySelector('#inputCount')); - new MDCTextField(document.querySelector('#color')); + new MDCTextField(document.querySelector('.mdc-text-field')) + new MDCTextField(document.querySelector('#outputCount')) + new MDCTextField(document.querySelector('#inputCount')) + new MDCTextField(document.querySelector('#color')) } 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.update() @@ -356,17 +420,20 @@ export class ComponentManager { public async delete(name: string) { const res = await modal({ - title: "Are you sure?", - content: html`Deleting a simulations is ireversible, and all work will be lost!` + title: 'Are you sure?', + content: html` + Deleting a simulations is ireversible, and all work will be + lost! + ` }) if (res) { if (this.name === name) { if (this.saves.value.length > 1) { this.switchTo(this.saves.value.find(val => val !== name)) - } - else { - let newName = (name === defaultName) ? `${defaultName}(1)` : defaultName + } else { + let newName = + name === defaultName ? `${defaultName}(1)` : defaultName await this.createEmptySimulation(newName) this.switchTo(newName) } @@ -384,15 +451,18 @@ export class ComponentManager { scale: [1, 1] }) - if (name !== this.name) - this.save() + if (name !== this.name) this.save() this.name = name this.refresh() } - return new Promise(async (res) => {//get wheater theres already a simulation with that name - if (this.store.get(name) && await this.handleDuplicateModal(name) || - !this.store.get(name)) { + return new Promise(async res => { + //get wheater theres already a simulation with that name + if ( + (this.store.get(name) && + (await this.handleDuplicateModal(name))) || + !this.store.get(name) + ) { create() res(true) } @@ -402,38 +472,53 @@ export class ComponentManager { public switchTo(name: string) { const data = this.store.get(name) 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.refresh() } eval(command: string) { - if (!this.commandHistory.includes(command)) // no duplicates + if (!this.commandHistory.includes(command)) + // no duplicates 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() - const words = command.split(" ") + const words = command.split(' ') if (words[0] in this.commands) { const remaining = words.slice(1) - const flags = remaining.filter(val => val[0] == "-") - const args = remaining.filter(val => val[0] != "-") + const flags = remaining.filter(val => val[0] == '-') + const args = remaining.filter(val => val[0] != '-') this.commands[words[0]](this, args, flags) - } - else - error(`Command ${words} doesn't exist. Run help to get a list of all commands.`, - "", this.alertOptions) + } else + error( + `Command ${words} doesn't exist. Run help to get a list of all commands.`, + '', + this.alertOptions + ) } public smartClear() { 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() - success("Succesfully cleared all unconnected components", "", this.alertOptions) + success( + 'Succesfully cleared all unconnected components', + '', + this.alertOptions + ) } public clear() { @@ -441,7 +526,7 @@ export class ComponentManager { this.wireManager.dispose() this.update() - success("Succesfully cleared all components", "", this.alertOptions) + success('Succesfully cleared all components', '', this.alertOptions) } public refresh() { @@ -454,7 +539,11 @@ export class ComponentManager { this.update() - success("Succesfully refreshed to the latest save", "", this.alertOptions) + success( + 'Succesfully refreshed to the latest save', + '', + this.alertOptions + ) } update() { @@ -488,12 +577,10 @@ export class ComponentManager { // if (false) { } if (toAddOnTop >= 0) { this.top(this.components[toAddOnTop]) - } - - else if (outsideComponents && this.clicked) { + } else if (outsideComponents && this.clicked) { const mousePosition = [e.clientX, e.clientY] - const delta = mousePosition.map((value, index) => - this.screen.mousePosition[index] - value + const delta = mousePosition.map( + (value, index) => this.screen.mousePosition[index] - value ) as [number, number] this.screen.move(...delta) } @@ -503,7 +590,11 @@ export class ComponentManager { public silentRefresh(verboose = false) { this.loadState(this.state) if (verboose) - success("Succesfully reloaded all components", "", this.alertOptions) + success( + 'Succesfully reloaded all components', + '', + this.alertOptions + ) } public top(component: Component) { @@ -523,57 +614,67 @@ export class ComponentManager { toRemoveDuplicatesFor = component } - const stroke = subscribe(component.clickedChanges.pipe(map( - val => val ? "yellow" : "black" - ))) + const stroke = subscribe( + component.clickedChanges.pipe( + map(val => (val ? 'yellow' : 'black')) + ) + ) return svg` <g> ${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)} @mouseup=${mouseupHandler} @touchend=${mouseupHandler}> - <rect width=${ subscribe(component.width)} - height=${ subscribe(component.height)} - x=${ subscribe(component.x)} - y=${ subscribe(component.y)} + <rect width=${subscribe(component.width)} + height=${subscribe(component.height)} + x=${subscribe(component.x)} + y=${subscribe(component.y)} stroke=${stroke} - fill=${(component.material.mode === "standard_image") ? - "rgba(0,0,0,0)" : - subscribe(component.material.color)} + fill=${ + component.material.mode !== 'color' + ? 'rgba(0,0,0,0)' + : subscribe(component.material.color) + } rx=20 ry=20> </rect> - ${(component.material.mode === "standard_image") ? component.material.innerHTML( - subscribe(component.x), - subscribe(component.y), - subscribe(component.width), - subscribe(component.height)) : ""} + ${ + component.material.mode !== 'color' + ? component.material.innerHTML( + subscribe(component.x), + subscribe(component.y), + subscribe(component.width), + subscribe(component.height) + ) + : '' + } </g> </g> - `}); + ` + }) - if (toRemoveDuplicatesFor) - this.removeDuplicates(toRemoveDuplicatesFor) + if (toRemoveDuplicatesFor) this.removeDuplicates(toRemoveDuplicatesFor) return svg`${this.wireManager.svg} ${result}` } private removeDuplicates(component: Component) { let instances = this.components - .map((value, index) => (value == component) ? index : null) + .map((value, index) => (value == component ? index : null)) .filter(value => value) instances.pop() - this.components = this.components - .filter((_, index) => instances.indexOf(index) != -1) + this.components = this.components.filter( + (_, index) => instances.indexOf(index) != -1 + ) } get state(): ManagerState { - const components = Array.from((new Set(this.components)).values()) + const components = Array.from(new Set(this.components).values()) return { components: components.map(value => value.state), position: this.screen.position as [number, number], @@ -591,17 +692,24 @@ export class ComponentManager { } private loadState(state: ManagerState) { - if (!state.wires) //old state + if (!state.wires) + //old state return this.wireManager.dispose() this.clicked = false - this.components = state.components.map(value => Component.fromState(value)) + this.components = state.components.map(value => + Component.fromState(value) + ) this.onTop = null state.wires.forEach(val => { - this.wireManager.start = this.getComponentById(val.from.owner).outputPins[val.from.index] - this.wireManager.end = this.getComponentById(val.to.owner).inputPins[val.to.index] + this.wireManager.start = this.getComponentById( + val.from.owner + ).outputPins[val.from.index] + this.wireManager.end = this.getComponentById( + val.to.owner + ).inputPins[val.to.index] this.wireManager.tryResolving() }) @@ -614,10 +722,14 @@ export class ComponentManager { save() { 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.store.set(this.name, this.state) - success(`Saved the simulation ${this.name} succesfully!`, "", this.alertOptions) + success( + `Saved the simulation ${this.name} succesfully!`, + '', + this.alertOptions + ) } -} \ No newline at end of file +} diff --git a/src/ts/common/componentManager/interfaces.ts b/src/ts/common/componentManager/interfaces.ts index 96fb1e4..af17101 100644 --- a/src/ts/common/componentManager/interfaces.ts +++ b/src/ts/common/componentManager/interfaces.ts @@ -1,5 +1,5 @@ -import { ComponentState, materialMode } from "../component/interfaces"; -import { WireState } from "../wires/interface"; +import { ComponentState, materialMode } from '../component/interfaces' +import { WireState } from '../wires/interface' export interface ManagerState { components: ComponentState[] @@ -20,4 +20,6 @@ export interface ComponentTemplate { data: string } editable?: boolean -} \ No newline at end of file + imported?: boolean + importCommand?: string +} diff --git a/src/ts/common/wires/wireManager.ts b/src/ts/common/wires/wireManager.ts index dbb323e..2d37f96 100644 --- a/src/ts/common/wires/wireManager.ts +++ b/src/ts/common/wires/wireManager.ts @@ -1,10 +1,11 @@ -import { Singleton } from "@eix/utils"; -import { Pin } from "../pin"; -import { Wire } from "./wire"; -import { svg } from "lit-html"; -import { subscribe } from "lit-rx"; -import { Subject } from "rxjs"; -import { WireStateVal } from "./interface"; +import { Singleton } from '@eix/utils' +import { Pin } from '../pin' +import { Wire } from './wire' +import { svg } from 'lit-html' +import { subscribe } from 'lit-rx' +import { Subject, combineLatest } from 'rxjs' +import { WireStateVal } from './interface' +import { merge, map } from 'rxjs/operators' @Singleton export class WireManager { @@ -15,20 +16,19 @@ export class WireManager { public update = new Subject<boolean>() - constructor() { } + constructor() {} public add(data: Pin) { - if (data.allowWrite) //output + if (data.allowWrite) + //output this.start = data - else - this.end = data + else this.end = data this.tryResolving() } public dispose() { - for (let i of this.wires) - i.dispose() + for (let i of this.wires) i.dispose() this.wires = [] } @@ -45,8 +45,7 @@ export class WireManager { } private canBind(end: Pin) { - if (this.wires.find(val => val.output === end)) - return false + if (this.wires.find(val => val.output === end)) return false return true } @@ -57,35 +56,60 @@ export class WireManager { } get svg() { - return svg`${this.wires.map((val) => { + return svg`${this.wires.map(val => { const i = val.input.of const o = val.output.of const inputIndex = i.outputPins.indexOf(val.input) - const input = i.piny(false, inputIndex) - const output = o.piny(true, o.inputPins.indexOf(val.output)) + const inputY = i.piny(false, inputIndex) + 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` - <line x2=${subscribe(i.pinx(false, 20))} - x1=${subscribe(o.pinx(true, 20))} - y2=${subscribe(input)} - y1=${subscribe(output)} + <path d=${subscribe(d)} stroke=${subscribe(val.input.svgColor)} - stroke-width=10 - @click=${() => this.remove(val)} - > - </line> - `})}` + stroke-width=10 + fill="rgba(0,0,0,0)" + @click=${() => this.remove(val)} /> + ` + })}` } get state() { - return this.wires.map((val): WireStateVal => ({ - from: { - owner: val.input.of.id, - index: val.input.of.outputPins.indexOf(val.input) - }, - to: { - owner: val.output.of.id, - index: val.output.of.inputPins.indexOf(val.output) - } - })) + return this.wires.map( + (val): WireStateVal => ({ + from: { + owner: val.input.of.id, + index: val.input.of.outputPins.indexOf(val.input) + }, + to: { + owner: val.output.of.id, + index: val.output.of.inputPins.indexOf(val.output) + } + }) + ) } -} \ No newline at end of file +} diff --git a/src/ts/main.ts b/src/ts/main.ts index 86a8c27..7d1d4d0 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -1,11 +1,12 @@ -import { render, html } from "lit-html" -import { subscribe } from "lit-rx" -import { Screen } from "./common/screen.ts"; -import { ComponentManager } from "./common/componentManager"; -import { map } from "rxjs/operators"; -import { MDCMenu } from '@material/menu'; -import { error } from "toastr" -import { modal } from "./common/modals"; +import { render, html } from 'lit-html' +import { subscribe } from 'lit-rx' +import { Screen } from './common/screen.ts' +import { ComponentManager } from './common/componentManager' +import { map } from 'rxjs/operators' +import { MDCMenu } from '@material/menu' +import { error } from 'toastr' +import { modal } from './common/modals' +import { importComponent } from './common/componentImporter/importComponent' const screen = new Screen() @@ -13,71 +14,75 @@ export const manager = new ComponentManager() manager.save() manager.update() -window.onerror = (message: string, url: string, lineNumber: number): boolean => { - error(message, "", { +window.onerror = ( + message: string, + url: string, + lineNumber: number +): boolean => { + error(message, '', { ...manager.alertOptions, - onclick: () => modal({ - no: "", - yes: "close", - title: "Error", - content: html` - <table> - <tr> - <td>Url:</td> - <td>${url}</td> - </tr> - <tr> - <td>Message:</td> - <td>${message}</td> - </tr> - <tr> - <td>Line:</td> - <td>${lineNumber}</td> - </tr> - </table> - ` - }) + onclick: () => + modal({ + no: '', + yes: 'close', + title: 'Error', + content: html` + <table> + <tr> + <td>Url:</td> + <td>${url}</td> + </tr> + <tr> + <td>Message:</td> + <td>${message}</td> + </tr> + <tr> + <td>Line:</td> + <td>${lineNumber}</td> + </tr> + </table> + ` + }) }) - 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") + return true } -const moveHandler = (e: MouseEvent) => handleEvent(e, (e: MouseEvent) => { - manager.handleMouseMove(e) - screen.updateMouse(e) -}) +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') +} -render(html` +const moveHandler = (e: MouseEvent) => + handleEvent(e, (e: MouseEvent) => { + manager.handleMouseMove(e) + screen.updateMouse(e) + }) + +render( + html` <div @mousemove=${moveHandler} @touchmove=${moveHandler} - @mousedown=${(e: MouseEvent) => handleEvent(e, () => - manager.handleMouseDown() -)} - @touchdown=${(e: MouseEvent) => handleEvent(e, () => - manager.handleMouseDown() -)} - @mouseup=${(e: MouseEvent) => handleEvent(e, () => - manager.handleMouseUp() -)} - @touchup=${(e: MouseEvent) => handleEvent(e, () => - manager.handleMouseUp() -)} - @wheel=${(e: MouseEvent) => handleEvent(e, (e: WheelEvent) => - screen.handleScroll(e) -)}> + @mousedown=${(e: MouseEvent) => + handleEvent(e, () => manager.handleMouseDown())} + @touchdown=${(e: MouseEvent) => + handleEvent(e, () => manager.handleMouseDown())} + @mouseup=${(e: MouseEvent) => + handleEvent(e, () => manager.handleMouseUp())} + @touchup=${(e: MouseEvent) => + handleEvent(e, () => manager.handleMouseUp())} + @wheel=${(e: MouseEvent) => + handleEvent(e, (e: WheelEvent) => screen.handleScroll(e))}> - <div id=${subscribe(manager.barAlpha.pipe(map(val => - (val == "1") ? "shown" : "" -)))} + <div id=${subscribe( + manager.barAlpha.pipe(map(val => (val == '1' ? 'shown' : ''))) + )} class=createBar> <div class="topContainer"> <div> @@ -87,41 +92,42 @@ render(html` </div> </div> </div> - <svg height=${ subscribe(screen.height)} - width=${ subscribe(screen.width)} + <svg height=${subscribe(screen.height)} + width=${subscribe(screen.width)} viewBox=${subscribe(screen.viewBox)}> - ${ subscribe(manager.svgs)} + ${subscribe(manager.svgs)} </svg> </div> <div class="ModalContainer"></div> <aside class="mdc-drawer main-sidebar"> <div class="mdc-drawer__content"> <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> <span class="mdc-list-item__text">Create new simulation</span> </a> <a class="mdc-list-item" href="#" id="openSimulation" @click=${() => { - menus[0].open = true - }}> + menus[0].open = true + }}> <i class="material-icons mdc-list-item__graphic" aria-hidden="true">folder_open</i> <span class="mdc-list-item__text">Open simulation</span> </a> <a class="mdc-list-item" href="#" id="openFile" @click=${() => { - menus[2].open = true - }}> + menus[2].open = true + }}> <i class="material-icons mdc-list-item__graphic" aria-hidden="true">insert_drive_file</i> <span class="mdc-list-item__text">Simulation</span> </a> <a class="mdc-list-item" href="#" id="openCustomGates" @click=${() => { - menus[3].open = true - }}> + menus[3].open = true + }}> <i class="material-icons mdc-list-item__graphic" aria-hidden="true">edit</i> <span class="mdc-list-item__text">Custom gates</span> </a> <a class="mdc-list-item" href="#" id="openGates" @click=${() => { - menus[1].open = true - }}> + menus[1].open = true + }}> <i class="material-icons mdc-list-item__graphic" aria-hidden="true">add</i> <span class="mdc-list-item__text">Add component</span> </a> @@ -132,61 +138,160 @@ render(html` <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"> - ${subscribe(manager.saves.pipe(map(_ => _.map(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>` - ))))} + ${subscribe( + manager.saves.pipe( + map(_ => + _.map( + 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> </div> <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"> - ${subscribe(manager.gates.pipe(map(_ => [..._].sort().map(val => html` - <li class= "mdc-list-item" role = "menuitem" @click=${() => manager.add(val)}> - <span class="mdc-list-item__text"> ${val} </span> - ${(manager.templateStore.store.get(val).editable ? - html`      <span class="material-icons mdc-list-item__meta" @click=${ - () => manager.templateStore.store.delete(val) - }> delete </span>` : - "" - )} - </li>` - ))))} + ${subscribe( + manager.gates.pipe( + map(gates => + [...gates].sort().map(name => { + const gate = manager.templateStore.store.get(name) + return html` + <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> + ` + }) + ) + ) + )} </ul> </div> <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"> - ${[...Object.keys(manager.file)].sort().map(key => html` - <li class= "mdc-list-item" role = "menuitem" @click=${() => manager.file[key]()}> - <span class="mdc-list-item__text">${key}</span> - ${manager.shortcuts[key] ? html` - <span class="mdc-list-item__meta">      ${manager.shortcuts[key]}</span> - ` : ""} - </li>` - )} + ${[...Object.keys(manager.file)].sort().map( + key => html` + <li + class="mdc-list-item" + role="menuitem" + @click=${() => manager.file[key]()} + > + <span class="mdc-list-item__text">${key}</span> + ${manager.shortcuts[key] + ? html` + <span class="mdc-list-item__meta" + >      + ${manager.shortcuts[key]}</span + > + ` + : ''} + </li> + ` + )} </ul> </div> <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"> - ${subscribe(manager.gates.pipe(map(_ => _ - .filter(val => manager.templateStore.store.get(val).editable) - .map(val => html` - <li class= "mdc-list-item" role = "menuitem" @click=${() => manager.edit(val)}> - <i class="material-icons mdc-list-item__graphic" aria-hidden="true">edit</i> - <span class="mdc-list-item__text"> ${val} </span> - </li>` - ))))} - <li class= "mdc-list-item" role = "menuitem" @click=${() => manager.newGate()}> + ${subscribe( + manager.gates.pipe( + map(gates => + gates + .map(name => manager.templateStore.store.get(name)) + .filter(gate => gate.editable || gate.imported) + .map( + gate => html` + <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> <span class="mdc-list-item__text"> New custom gate </span> </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> </div> -`, document.body) +`, + document.body +) const menus = [ new MDCMenu(document.querySelector('#saveMenu')), @@ -196,8 +301,8 @@ const menus = [ ] menus.forEach(menu => menu.hoistMenuToBody()) menus[0].setAnchorElement(document.querySelector(`#openSimulation`)) -menus[1].setAnchorElement(document.querySelector("#openGates")) -menus[2].setAnchorElement(document.querySelector("#openFile")) -menus[3].setAnchorElement(document.querySelector("#openCustomGates")) +menus[1].setAnchorElement(document.querySelector('#openGates')) +menus[2].setAnchorElement(document.querySelector('#openFile')) +menus[3].setAnchorElement(document.querySelector('#openCustomGates')) -manager.update() \ No newline at end of file +manager.update()