diff --git a/package-lock.json b/package-lock.json index 024c063..190cef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,12 @@ "@types/sizzle": "*" } }, + "@types/micromodal": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/micromodal/-/micromodal-0.3.0.tgz", + "integrity": "sha512-n8Qicw8bPWAFJjR8HICQhgUXmTUjDZf2Yt85N0LozuOMKEYEIRMwqvTCp1A5dd5ycE5piyvERQajyUDF8SGaOw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -4479,6 +4485,11 @@ "to-regex": "^3.0.2" } }, + "micromodal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.0.tgz", + "integrity": "sha512-YDku9Fi57S4Sm6oitSy3sr786qSp5L6NbatuH2kEeXf0jStvZgZk4bLBKaoSONBaq3BEvFz3hAaoUa7/pV1Kgg==" + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", diff --git a/package.json b/package.json index e26a30d..0d8b66a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "devDependencies": { "@types/file-saver": "^2.0.1", "@types/gh-pages": "^2.0.0", + "@types/micromodal": "^0.3.0", "@types/toastr": "^2.1.37", "css-loader": "^2.1.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", @@ -50,6 +51,7 @@ "haunted": "^4.3.0", "lit-html": "^1.0.0", "lit-rx": "0.0.2", + "micromodal": "^0.4.0", "prelude-ts": "^0.8.2", "rxjs": "^6.5.2", "toastr": "^2.1.4" diff --git a/src/assets/and_gate.jpg b/src/assets/and_gate.jpg new file mode 100644 index 0000000..c9ee14f Binary files /dev/null and b/src/assets/and_gate.jpg differ diff --git a/src/scss/base.scss b/src/scss/base.scss index 487d817..eb61694 100644 --- a/src/scss/base.scss +++ b/src/scss/base.scss @@ -1,4 +1,5 @@ @import "./toastr.scss"; +@import "./modal.scss"; html, body { padding: 0; @@ -57,4 +58,18 @@ svg { .toasts{ background-color: #000000; box-shadow: 0 0 0px black !important; +} + +div.component-container{ + display: flex; + height: 100%; + width: 100%; + flex-direction: column; + align-items: center; + justify-content: center; + img.component { + // border: 10px solid black; + border-radius: 20px; + user-select: none; + } } \ No newline at end of file diff --git a/src/scss/modal.scss b/src/scss/modal.scss new file mode 100644 index 0000000..e284575 --- /dev/null +++ b/src/scss/modal.scss @@ -0,0 +1,153 @@ +/**************************\ + Basic Modal Styles +\**************************/ + +.modal { + font-family: -apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif; + } + + .modal__overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.6); + display: flex; + justify-content: center; + align-items: center; + } + + .modal__container { + background-color: #222; + color: rgb(256,256,256); + padding: 30px; + max-width: 500px; + max-height: 100vh; + border-radius: 4px; + overflow-y: auto; + box-sizing: border-box; + } + + .modal__header { + display: flex; + justify-content: space-between; + align-items: center; + } + + .modal__title { + margin-top: 0; + margin-bottom: 0; + font-weight: 600; + font-size: 1.25rem; + line-height: 1.25; + color: #0099aa; + box-sizing: border-box; + } + + .modal__close { + background: transparent; + border: 0; + color: white; + } + + .modal__header .modal__close:before { content: "\2715"; } + + .modal__content { + margin-top: 2rem; + margin-bottom: 2rem; + line-height: 1.5; + color: rgba(256,256,256,0.8); + } + + .modal__btn { + font-size: .875rem; + padding-left: 1rem; + padding-right: 1rem; + padding-top: .5rem; + padding-bottom: .5rem; + background-color: #e6e6e6; + color: rgba(0,0,0,.8); + border-radius: .25rem; + border-style: none; + border-width: 0; + cursor: pointer; + -webkit-appearance: button; + text-transform: none; + overflow: visible; + line-height: 1.15; + margin: 0; + will-change: transform; + -moz-osx-font-smoothing: grayscale; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform: translateZ(0); + transform: translateZ(0); + transition: -webkit-transform .25s ease-out; + transition: transform .25s ease-out; + transition: transform .25s ease-out,-webkit-transform .25s ease-out; + } + + .modal__btn:focus, .modal__btn:hover { + -webkit-transform: scale(1.05); + transform: scale(1.05); + } + + .modal__btn-primary { + background-color: #00449e; + color: #fff; + } + + + + /**************************\ + Demo Animation Style + \**************************/ + @keyframes mmfadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes mmfadeOut { + from { opacity: 1; } + to { opacity: 0; } + } + + @keyframes mmslideIn { + from { transform: translateY(15%); } + to { transform: translateY(0); } + } + + @keyframes mmslideOut { + from { transform: translateY(0); } + to { transform: translateY(-10%); } + } + + .micromodal-slide { + display: none; + } + + .micromodal-slide.is-open { + display: block; + } + + .micromodal-slide[aria-hidden="false"] .modal__overlay { + animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1); + } + + .micromodal-slide[aria-hidden="false"] .modal__container { + animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1); + } + + .micromodal-slide[aria-hidden="true"] .modal__overlay { + animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1); + } + + .micromodal-slide[aria-hidden="true"] .modal__container { + animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1); + } + + .micromodal-slide .modal__container, + .micromodal-slide .modal__overlay { + will-change: transform; + } \ No newline at end of file diff --git a/src/ts/common/component/component.ts b/src/ts/common/component/component.ts index cce6247..8f9830e 100644 --- a/src/ts/common/component/component.ts +++ b/src/ts/common/component/component.ts @@ -11,6 +11,7 @@ import { success, error } from "toastr" import { alertOptions } from "../componentManager/alertOptions"; import { WireManager } from "../wires"; import { runCounter } from "./runCounter"; +import { Material } from "./material"; export class Component { private static store = new ComponentTemplateStore() @@ -21,23 +22,24 @@ export class Component { public position = new BehaviorSubject(null) public scale = new BehaviorSubject(null) public clicked = false + public id: number + public material: Material + public clickedChanges = new BehaviorSubject(false) private mouserDelta: number[] private strokeColor = "#888888" private inputs: number private outputs: number private activation: (ctx: activationContext) => any - private subscriptions:Subscription[] = [] + private subscriptions: Subscription[] = [] public inputPins: Pin[] = [] public outputPins: Pin[] = [] - public id: number - constructor(private template: string, position: [number, number] = [0, 0], scale: [number, number] = [0, 0], - id? : number) { + id?: number) { //set initial props this.position.next(position) @@ -74,14 +76,17 @@ export class Component { }) this.activate() + + this.material = new Material(data.material.mode, data.material.data) } - public dispose(){ + public dispose() { this.subscriptions.forEach(val => val.unsubscribe()) } public handleMouseUp(e: MouseEvent) { this.clicked = false + this.clickedChanges.next(this.clicked) } private activate() { @@ -107,6 +112,7 @@ export class Component { mousePosition[index] - value ) this.clicked = true + this.clickedChanges.next(this.clicked) } handlePinClick(e: MouseEvent, pin: Pin) { @@ -149,17 +155,18 @@ export class Component { return ((mode === "input") ? this.inputPins : this.outputPins) .map((val, index) => { - const y = subscribe(this.piny(mode === "input",index)) + const y = subscribe(this.piny(mode === "input", index)) - const x = subscribe(this.pinx(mode === "input",pinLength)) + const x = subscribe(this.pinx(mode === "input", pinLength)) const linex = subscribe(this.x.pipe(map(val => val + ((mode === "input") ? -pinLength : pinLength + this.scale.value[0]) ))) - const middleX = subscribe(this.x.pipe(map(val => - val + this.scale.value[0] / 2 - ))) + const middleX = subscribe(this.x.pipe(map(val => { + const scale = this.scale.value[0] + return val + ((mode === "input") ? scale / 10 : 9 * scale / 10 ) + }))) return svg` val + ( (mode) ? @@ -187,7 +194,7 @@ export class Component { ) } - public piny(mode = true, index: number){ + public piny(mode = true, index: number) { const space = this.scale.value[1] / (mode ? this.inputs : this.outputs) return this.y.pipe( map(val => val + space * (2 * index + 1) / 2) diff --git a/src/ts/common/component/interfaces.ts b/src/ts/common/component/interfaces.ts index 73db1cb..ba44851 100644 --- a/src/ts/common/component/interfaces.ts +++ b/src/ts/common/component/interfaces.ts @@ -12,4 +12,6 @@ export interface activationContext { outputs: Pin[] succes: (mes: string) => any error: (mes:string) => any -} \ No newline at end of file +} + +export type materialMode = "standard_image" | "color" \ No newline at end of file diff --git a/src/ts/common/component/material.ts b/src/ts/common/component/material.ts new file mode 100644 index 0000000..7af6633 --- /dev/null +++ b/src/ts/common/component/material.ts @@ -0,0 +1,39 @@ +import { TemplateResult, svg, SVGTemplateResult, Part } from "lit-html"; +import { Subject, BehaviorSubject } from "rxjs"; +import { materialMode } from "./interfaces"; + +declare function require(path:string):T + +type partFactory = (part:Part) => void + +export class Material { + private static images: { + [key: string]: string + } = { + and: require("../../../assets/and_gate.jpg") + } + + private static cached = new Map() + + public color = new BehaviorSubject("rgba(0,0,0,0)") + + constructor (public mode: materialMode,public name:string) { + const saved = Material.cached.get(mode + name) + + if (saved) + return saved + + else Material.cached.set(mode + name,this) + + if (this.mode === "color") + this.color.next(name) + } + + innerHTML (x: partFactory, y: partFactory, w: partFactory, h: partFactory) { + return svg` +
+ +
+
` + } +} \ No newline at end of file diff --git a/src/ts/common/componentManager/componentManager.ts b/src/ts/common/componentManager/componentManager.ts index 50edc67..4c0cb38 100644 --- a/src/ts/common/componentManager/componentManager.ts +++ b/src/ts/common/componentManager/componentManager.ts @@ -1,7 +1,7 @@ import { Singleton } from "@eix/utils"; import { Component } from "../component"; import { Subject, BehaviorSubject, fromEvent } from "rxjs"; -import { svg, SVGTemplateResult } from "lit-html"; +import { svg, SVGTemplateResult, html } from "lit-html"; import { subscribe } from "lit-rx"; import { Screen } from "../screen.ts"; import { ManagerState } from "./interfaces"; @@ -14,6 +14,10 @@ import { WireManager } from "../wires"; import { runCounter } from "../component/runCounter"; import { Settings } from "../store/settings"; import { download } from "./download"; +import Modal from "micromodal" +import { modal } from "../modals"; +import { map } from "rxjs/operators"; + @Singleton export class ComponentManager { @@ -167,17 +171,57 @@ export class ComponentManager { this.barAlpha.next("1") } - create() { + async create() { 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) + } else if (this.inputMode == "command") this.eval(elem.value) } + 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 you work will be lost!` + }) + + return result + } + + public createEmptySimulation(name: string) { + const create = () => { + this.store.set(name, { + wires: [], + components: [], + position: [0, 0], + scale: [1, 1] + }) + + if (name !== this.name) + this.save() + this.refresh() + } + + return new Promise(async (res, rej) => {//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) + } + }) + } + + public switchTo(name: string) { + //TODO: implement + } + eval(command: string) { if (!this.commandHistory.includes(command)) // no duplicates this.commandHistory.push(command) @@ -265,26 +309,42 @@ export class ComponentManager { let toRemoveDuplicatesFor: Component const size = 10 - const result = this.components.map(component => svg` + const result = this.components.map(component => { + const mouseupHandler = (e: MouseEvent) => { + component.handleMouseUp(e) + toRemoveDuplicatesFor = component + } + + const stroke = subscribe(component.clickedChanges.pipe(map( + val => val ? "yellow" : "black" + ))) + + return svg` ${component.pinsSvg(10, 20)} ${component.pinsSvg(10, 20, "output")} - component.handleClick(e)} - @mouseup=${(e: MouseEvent) => { - component.handleMouseUp(e) - toRemoveDuplicatesFor = component - }}> + component.handleClick(e)} + @mouseup=${mouseupHandler}> + + + ${(component.material.mode === "standard_image") ? component.material.innerHTML( + subscribe(component.x), + subscribe(component.y), + subscribe(component.width), + subscribe(component.height)) : ""} + - `); + `}); if (toRemoveDuplicatesFor) this.removeDuplicates(toRemoveDuplicatesFor) diff --git a/src/ts/common/componentManager/componentTemplateStore.ts b/src/ts/common/componentManager/componentTemplateStore.ts index 2ed960e..f728839 100644 --- a/src/ts/common/componentManager/componentTemplateStore.ts +++ b/src/ts/common/componentManager/componentTemplateStore.ts @@ -72,7 +72,11 @@ export class ComponentTemplateStore { version: "1.0.0", activation: ` ctx.outputs[0].value = ctx.inputs[0].value - `.trim() + `.trim(), + material: { + mode: "color", + data: "blue" + } }) this.store.set("not", { inputs: 1, @@ -81,7 +85,11 @@ export class ComponentTemplateStore { version: "1.0.0", activation: ` ctx.outputs[0].value = !ctx.inputs[0].value - `.trim() + `.trim(), + material: { + mode: "color", + data: "red" + } }) this.store.set("and", { inputs: 2, @@ -90,7 +98,11 @@ export class ComponentTemplateStore { version: "1.0.0", activation: ` ctx.outputs[0].value = ctx.inputs[0].value && ctx.inputs[1].value - `.trim() + `.trim(), + material: { + mode: "standard_image", + data: "and" + } }) this.store.set("true", { inputs: 0, @@ -99,7 +111,11 @@ export class ComponentTemplateStore { version: "1.0.0", activation: ` ctx.outputs[0].value = true - `.trim() + `.trim(), + material:{ + mode:"color", + data:"green" + } }) } } \ No newline at end of file diff --git a/src/ts/common/componentManager/interfaces.ts b/src/ts/common/componentManager/interfaces.ts index 53023e4..1f86304 100644 --- a/src/ts/common/componentManager/interfaces.ts +++ b/src/ts/common/componentManager/interfaces.ts @@ -1,4 +1,4 @@ -import { ComponentState } from "../component/interfaces"; +import { ComponentState, materialMode } from "../component/interfaces"; import { WireState } from "../wires/interface"; export interface ManagerState { @@ -14,4 +14,8 @@ export interface ComponentTemplate { activation: string inputs: number outputs: number + material: { + mode: materialMode + data: string + } } \ No newline at end of file diff --git a/src/ts/common/modals/index.ts b/src/ts/common/modals/index.ts new file mode 100644 index 0000000..5848715 --- /dev/null +++ b/src/ts/common/modals/index.ts @@ -0,0 +1 @@ +export * from "./modal" \ No newline at end of file diff --git a/src/ts/common/modals/interfaces.ts b/src/ts/common/modals/interfaces.ts new file mode 100644 index 0000000..be93ee8 --- /dev/null +++ b/src/ts/common/modals/interfaces.ts @@ -0,0 +1,8 @@ +import { TemplateResult } from "lit-html"; + +export interface confirmModalOptions { + title: string + content: TemplateResult + yes: string + no: string +} \ No newline at end of file diff --git a/src/ts/common/modals/modal.ts b/src/ts/common/modals/modal.ts new file mode 100644 index 0000000..236145a --- /dev/null +++ b/src/ts/common/modals/modal.ts @@ -0,0 +1,83 @@ +import { render, html } from "lit-html" +import { confirmModalOptions } from "./interfaces"; +import { fromEvent } from "rxjs"; +import MicroModal from "micromodal" + +let lastId = 0 + +export const modal = (options: Partial) => new Promise((res, rej) => { + const defaultOptions = { + yes: "yes", + no: "no", + title: "modal", + content: html`Hello world!` + } + + const parent = document.getElementsByClassName("ModalContainer")[0] + const finalOptions: confirmModalOptions = { ...defaultOptions, ...options } + const id = lastId++ + + if (!parent) + rej(false) + + const template = html` + + + ` + + render(template, parent) + + const yes = document.getElementById(`yes-${id}`) + const no = document.getElementById(`no-${id}`) + + const clear = () => { + // render(html``,parent) + MicroModal.close() + subscriptions.forEach(val => val.unsubscribe()) + } + + const subscriptions = [ + fromEvent(yes, "click").subscribe(val => { + clear() + res(true) + }), + fromEvent(no, "click").subscribe(val => { + clear() + res(false) + }) + ] + + MicroModal.show(`modal-${id}`) +}) + + + + + + + + + + + + diff --git a/src/ts/common/store/settings.ts b/src/ts/common/store/settings.ts index e3a9a57..14bb049 100644 --- a/src/ts/common/store/settings.ts +++ b/src/ts/common/store/settings.ts @@ -6,6 +6,10 @@ import { success, error } from "toastr" export class Settings { version = "1.0.0" + settings = { + jumpToNewSimulations: true + } + commands = (ctx: ComponentManager, args: string[], flags: string[]) => { //important flags for (let i of flags) { diff --git a/src/ts/main.ts b/src/ts/main.ts index 0b1b910..f945e06 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -56,6 +56,8 @@ render(html` ${ subscribe(manager.svgs) } + +
`, document.body) manager.update() \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a3eb178..c4ea556 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,7 @@ module.exports = { ] }, { - test: /\.(png|mp3|wav)$/, + test: /\.(png|jpg|mp3|wav)$/, use: [ { loader: 'file-loader',