From b06a4c744161615c2235b6d3ff7d3a4d6d934962 Mon Sep 17 00:00:00 2001 From: Matei Adriel 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 => { 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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 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 +``` + +![example of a gist id](./assets/gist_url.png) + +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 @@ + + Logic gate simulator - - Game + - + - @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; - } - } - + + + - - - - - -
-
-
-
-
-
- - - \ No newline at end of file + +
+
+
+
+
+
+ + 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(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('rgba(0,0,0,0)') - public color = new BehaviorSubject("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`
- +
` } -} \ 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 ( + command: string, + extension = 'json' +): Promise => { + const words = command.split(' ') + + let final: T + + if (words.length === 1) { + if (extension === 'json') { + final = await fecthAsJson(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 (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 +} + +export const getGist = async (id: string) => { + const url = `https://api.github.com/gists/${id}` + const json = await fecthAsJson(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(words.slice(1).join(' '), 'js') + } else { + return activaton + } +} + +export async function importComponent( + manager: ComponentManager, + command: string +): Promise { + 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() - public placeholder = new BehaviorSubject("Create simulation") - public barAlpha = new BehaviorSubject("0"); + public placeholder = new BehaviorSubject('Create simulation') + public barAlpha = new BehaviorSubject('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("commandHistory") - private store = new Store("simulationStates") + private commandHistoryStore = new Store('commandHistory') + private store = new Store('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(defaultName, "main") + @persistent(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>
+ success(message, '', ctx.alertOptions) + }, + help(ctx: ComponentManager) { + success( + `Usage: <command>
Where <command> is one of:
    - ${Object.keys(ctx.commands).map(val => ` + ${Object.keys(ctx.commands) + .map( + val => `
  • ${val}
  • - `).join("")} + ` + ) + .join('')}
- `, "", 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 = 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 = ( + 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 = document.getElementById("nameInput") - elem.value = "" - this.barAlpha.next("1") + const elem = document.getElementById('nameInput') + elem.value = '' + this.barAlpha.next('1') } private async create() { - const elem = document.getElementById("nameInput") - this.barAlpha.next("0") + const elem = 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` -
-
- -
-
-
- + content: html` + ${html` +
+
+ +
+
+
+ +
+
-
-


-
- - -
-


-
- - -
-


-
- - -
-

- `}` +

+
+ + +
+
+

+
+ + +
+
+

+
+ + +
+
+
+ `} + ` }).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` ${component.pinsSvg(10, 20)} - ${component.pinsSvg(10, 20, "output")} + ${component.pinsSvg(10, 20, 'output')} - component.handleClick(e)} + component.handleClick(e)} @touchstart=${(e: MouseEvent) => component.handleClick(e)} @mouseup=${mouseupHandler} @touchend=${mouseupHandler}> - - ${(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) + ) + : '' + } - `}); + ` + }) - 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() - 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( + ...output, + ...mid1, + ...mid2, + ...input + ).pipe( + map( + points => + `M ${points.slice(0, 2).join(' ')} C ${points + .slice(2) + .join(' ')}` + ) + ) + return svg` - this.remove(val)} - > - - `})}` + 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` - - - - - - - - - - - - - -
Url:${url}
Message:${message}
Line:${lineNumber}
- ` - }) + onclick: () => + modal({ + no: '', + yes: 'close', + title: 'Error', + content: html` + + + + + + + + + + + + + +
Url:${url}
Message:${message}
Line:${lineNumber}
+ ` + }) }) - return true; -}; - -const handleEvent = (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 = (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`
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))}> -
- (val == "1") ? "shown" : "" -)))} +
(val == '1' ? 'shown' : ''))) + )} class=createBar>
@@ -87,41 +92,42 @@ render(html`
- - ${ subscribe(manager.svgs)} + ${subscribe(manager.svgs)}