😁 Added too much stuff to explain!
This commit is contained in:
parent
df26cb6ddb
commit
900272a9e1
|
@ -18,6 +18,8 @@
|
|||
},
|
||||
"homepage": "https://github.com/neverix/html5-game-template#readme",
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/toastr": "^2.1.37",
|
||||
"css-loader": "^2.1.0",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^3.0.1",
|
||||
|
@ -34,11 +36,14 @@
|
|||
"webpack-dev-server": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eix/input": "git+https://github.com/eix-js/input.git",
|
||||
"@eix/utils": "git+https://github.com/eix-js/utils.git",
|
||||
"file-saver": "^2.0.2",
|
||||
"haunted": "^4.3.0",
|
||||
"lit-html": "^1.0.0",
|
||||
"lit-rx": "0.0.2",
|
||||
"prelude-ts": "^0.8.2",
|
||||
"rxjs": "^6.5.2"
|
||||
"rxjs": "^6.5.2",
|
||||
"toastr": "^2.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "./toastr.scss";
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -7,8 +9,52 @@ html, body {
|
|||
}
|
||||
|
||||
svg {
|
||||
background-color: #444444;
|
||||
background-color: #222222;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.createBar {
|
||||
z-index:10;
|
||||
position: absolute;
|
||||
top:0px;
|
||||
left:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
transition: all 0.6s ease-in-out 0s;
|
||||
.topContainer {
|
||||
height: 30%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
div{
|
||||
height:25%;
|
||||
width:75%;
|
||||
input{
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 250%;
|
||||
height:100%;
|
||||
width:100%;
|
||||
padding: 1%;
|
||||
font-family: "roboto";
|
||||
}
|
||||
}
|
||||
}
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
.createBar#shown{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.toasts{
|
||||
background-color: #000000;
|
||||
box-shadow: 0 0 0px black !important;
|
||||
}
|
1
src/scss/toastr.scss
Normal file
1
src/scss/toastr.scss
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,29 +1,98 @@
|
|||
import { Vector } from "prelude-ts"
|
||||
import { Subject, BehaviorSubject } from "rxjs";
|
||||
import { ComponentState } from "./interfaces";
|
||||
import { map } from "rxjs/operators";
|
||||
import { Subject, BehaviorSubject, Subscription, timer } from "rxjs";
|
||||
import { ComponentState, activationContext } from "./interfaces";
|
||||
import { map, debounce } from "rxjs/operators";
|
||||
import { Screen } from "../screen.ts";
|
||||
import { ComponentTemplateStore } from "../componentManager/componentTemplateStore";
|
||||
import { svg } from "lit-html";
|
||||
import { subscribe } from "lit-rx";
|
||||
import { Pin } from "../pin";
|
||||
import { success, error } from "toastr"
|
||||
import { alertOptions } from "../componentManager/alertOptions";
|
||||
import { WireManager } from "../wires";
|
||||
import { runCounter } from "./runCounter";
|
||||
|
||||
export class Component {
|
||||
private static store = new ComponentTemplateStore()
|
||||
private static screen = new Screen()
|
||||
private static wireManager = new WireManager()
|
||||
private static lastId = runCounter.get() + 1
|
||||
|
||||
public position = new BehaviorSubject<number[]>(null)
|
||||
public scale = new BehaviorSubject<number[]>(null)
|
||||
public clicked = false
|
||||
|
||||
private mouserDelta: number[]
|
||||
private strokeColor = "#888888"
|
||||
private inputs: number
|
||||
private outputs: number
|
||||
private activation: (ctx: activationContext) => any
|
||||
private subscriptions:Subscription[] = []
|
||||
|
||||
constructor(public activationType: string,
|
||||
public inputPins: Pin[] = []
|
||||
public outputPins: Pin[] = []
|
||||
|
||||
public id: number
|
||||
|
||||
constructor(private template: string,
|
||||
position: [number, number] = [0, 0],
|
||||
scale: [number, number] = [0, 0]) {
|
||||
scale: [number, number] = [0, 0],
|
||||
id? : number) {
|
||||
|
||||
//set initial props
|
||||
this.position.next(position)
|
||||
this.scale.next(scale)
|
||||
|
||||
//set the correct id
|
||||
this.id = (typeof id === "number") ? id : Component.lastId++
|
||||
|
||||
//load template
|
||||
const data = Component.store.store.get(template)
|
||||
|
||||
if (!data)
|
||||
throw new Error(`Template ${template} doesnt exist`)
|
||||
|
||||
this.inputs = data.inputs
|
||||
this.outputs = data.outputs
|
||||
|
||||
this.inputPins = [...Array(this.inputs)].fill(true).map(val => new Pin(false, this))
|
||||
this.outputPins = [...Array(this.outputs)].fill(true).map(val => new Pin(true, this))
|
||||
|
||||
this.activation = new Function(`return (ctx) => {
|
||||
try{
|
||||
${data.activation}
|
||||
}
|
||||
catch(err){
|
||||
ctx.error(err,"",ctx.alertOptions)
|
||||
}
|
||||
}`)()
|
||||
|
||||
this.inputPins.forEach(val => {
|
||||
const subscription = val.valueChanges.pipe(debounce(() => timer(1000 / 60)))
|
||||
.subscribe(val => this.activate())
|
||||
this.subscriptions.push(subscription)
|
||||
})
|
||||
|
||||
this.activate()
|
||||
}
|
||||
|
||||
handleMouseUp(e: MouseEvent) {
|
||||
public dispose(){
|
||||
this.subscriptions.forEach(val => val.unsubscribe())
|
||||
}
|
||||
|
||||
public handleMouseUp(e: MouseEvent) {
|
||||
this.clicked = false
|
||||
}
|
||||
|
||||
private activate() {
|
||||
this.activation({
|
||||
outputs: this.outputPins,
|
||||
inputs: this.inputPins,
|
||||
succes: (mes: string) => { success(mes, "", alertOptions) },
|
||||
error: (mes: string) => { error(mes, "", alertOptions) }
|
||||
} as activationContext)
|
||||
}
|
||||
|
||||
move(e: MouseEvent) {
|
||||
const mousePosition = Component.screen.getWorldPosition(e.clientX, e.clientY)
|
||||
this.position.next(mousePosition.map((value, index) =>
|
||||
|
@ -40,11 +109,16 @@ export class Component {
|
|||
this.clicked = true
|
||||
}
|
||||
|
||||
handlePinClick(e: MouseEvent, pin: Pin) {
|
||||
Component.wireManager.add(pin)
|
||||
}
|
||||
|
||||
get state(): ComponentState {
|
||||
return {
|
||||
position: this.position.value as [number, number],
|
||||
scale: this.position.value as [number, number],
|
||||
activationType: this.activationType
|
||||
scale: this.scale.value as [number, number],
|
||||
template: this.template,
|
||||
id: this.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +144,57 @@ export class Component {
|
|||
))
|
||||
}
|
||||
|
||||
static fromState(state:ComponentState){
|
||||
return new Component(state.activationType, state.position, state.scale)
|
||||
pinsSvg(pinScale: number, pinLength = 20, mode = "input") {
|
||||
const stroke = 3
|
||||
|
||||
return ((mode === "input") ? this.inputPins : this.outputPins)
|
||||
.map((val, index) => {
|
||||
const y = subscribe(this.piny(mode === "input",index))
|
||||
|
||||
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
|
||||
)))
|
||||
|
||||
return svg`
|
||||
<line stroke=${this.strokeColor} y1=${y} y2=${y}
|
||||
x1=${(mode === "input") ? linex : middleX}
|
||||
x2=${(mode !== "input") ? linex : middleX}
|
||||
stroke-width=${stroke}></line>
|
||||
|
||||
<circle fill=${subscribe(val.svgColor)}
|
||||
stroke=${this.strokeColor}
|
||||
r=${pinScale}
|
||||
cx=${x}
|
||||
cy=${y} stroke-width=${stroke}
|
||||
@click=${(e: MouseEvent) => this.handlePinClick(e, val)}
|
||||
></circle>
|
||||
`})
|
||||
}
|
||||
|
||||
public pinx(mode = true, pinLength = 15){
|
||||
return this.x.pipe(
|
||||
map(val => val + (
|
||||
(mode) ?
|
||||
-pinLength :
|
||||
this.scale.value[0] + pinLength
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
static fromState(state: ComponentState) {
|
||||
return new Component(state.template, state.position, state.scale, state.id)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,15 @@
|
|||
import { Pin } from "../pin";
|
||||
|
||||
export interface ComponentState {
|
||||
position: [number,number]
|
||||
scale: [number,number]
|
||||
activationType: string
|
||||
template: string
|
||||
id: number
|
||||
}
|
||||
|
||||
export interface activationContext {
|
||||
inputs: Pin[]
|
||||
outputs: Pin[]
|
||||
succes: (mes: string) => any
|
||||
error: (mes:string) => any
|
||||
}
|
14
src/ts/common/component/runCounter.ts
Normal file
14
src/ts/common/component/runCounter.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Store } from "../store";
|
||||
|
||||
export const runCounter = {
|
||||
store: new Store<number>("runCounter"),
|
||||
get(){
|
||||
return runCounter.store.get("main")
|
||||
},
|
||||
increase(){
|
||||
runCounter.store.set("main", runCounter.store.get("main") + 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (!runCounter.get())
|
||||
runCounter.store.set("main",1)
|
4
src/ts/common/componentManager/alertOptions.ts
Normal file
4
src/ts/common/componentManager/alertOptions.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const alertOptions = {
|
||||
positionClass: "toast-bottom-right",
|
||||
toastClass: "toasts"
|
||||
}
|
|
@ -1,22 +1,222 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { Component } from "../component/component";
|
||||
import { Subject } from "rxjs";
|
||||
import { Component } from "../component";
|
||||
import { Subject, BehaviorSubject, fromEvent } from "rxjs";
|
||||
import { svg, SVGTemplateResult } from "lit-html";
|
||||
import { subscribe } from "lit-rx";
|
||||
import { Screen } from "../screen.ts";
|
||||
import { MnanagerState } from "./interfaces";
|
||||
import { ManagerState } 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";
|
||||
|
||||
@Singleton
|
||||
export class ComponentManager {
|
||||
public components: Component[] = []
|
||||
public svgs = new Subject<SVGTemplateResult[]>()
|
||||
public svgs = new Subject<SVGTemplateResult>()
|
||||
public placeholder = new BehaviorSubject("Create simulation")
|
||||
|
||||
private temporaryCommnad = ""
|
||||
private onTop: Component
|
||||
private clicked = false
|
||||
|
||||
private screen = new Screen()
|
||||
private wireManager = new WireManager()
|
||||
private templateStore = new ComponentTemplateStore()
|
||||
private settings = new Settings()
|
||||
|
||||
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 shiftEvent = new KeyboardInput("shift")
|
||||
private refreshEvent = new KeyboardInput("r")
|
||||
private clearEvent = new KeyboardInput("c")
|
||||
private upEvent = new KeyboardInput("up")
|
||||
private downEvent = new KeyboardInput("down")
|
||||
|
||||
public name = "current"
|
||||
public alertOptions = alertOptions
|
||||
|
||||
private commandHistory: string[] = []
|
||||
private commands: {
|
||||
[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")
|
||||
|
||||
success(message, "", ctx.alertOptions)
|
||||
},
|
||||
help(ctx: ComponentManager) {
|
||||
success(`Usage: <command> <br>
|
||||
Where <command> is one of:
|
||||
<ul>
|
||||
${Object.keys(ctx.commands).map(val => `
|
||||
<li>${val}</li>
|
||||
`).join("")}
|
||||
</ul>
|
||||
`, "", ctx.alertOptions)
|
||||
},
|
||||
refresh(ctx: ComponentManager) {
|
||||
ctx.refresh()
|
||||
},
|
||||
ctp: this.templateStore.commands.template,
|
||||
settings: this.settings.commands,
|
||||
download
|
||||
}
|
||||
private inputMode: string
|
||||
|
||||
public barAlpha = new BehaviorSubject<string>("0");
|
||||
|
||||
constructor() {
|
||||
runCounter.increase()
|
||||
|
||||
this.svgs.next(this.render())
|
||||
|
||||
this.refresh()
|
||||
|
||||
fromEvent(document.body, "keydown").subscribe((e: KeyboardEvent) => {
|
||||
if (this.barAlpha.value == "1") {
|
||||
const elem = document.getElementById("nameInput")
|
||||
elem.focus()
|
||||
}
|
||||
else {
|
||||
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")
|
||||
if (this.upEvent.value) {
|
||||
document.body.focus()
|
||||
e.preventDefault()
|
||||
const index = this.commandHistory.indexOf(elem.value)
|
||||
|
||||
if (index) {
|
||||
//save drafts
|
||||
if (index === -1)
|
||||
this.temporaryCommnad = elem.value
|
||||
|
||||
const newIndex = (index === -1) ? this.commandHistory.length - 1 : index - 1
|
||||
elem.value = this.commandHistory[newIndex]
|
||||
}
|
||||
}
|
||||
if (this.downEvent.value) {
|
||||
document.body.focus()
|
||||
e.preventDefault()
|
||||
const index = this.commandHistory.indexOf(elem.value)
|
||||
|
||||
if (index > -1) {
|
||||
const maxIndex = this.commandHistory.length - 1
|
||||
elem.value = (index === maxIndex) ? this.temporaryCommnad : this.commandHistory[index + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.ctrlEvent.value) {
|
||||
if (this.createEvent.value) {
|
||||
this.preInput()
|
||||
this.inputMode = "create"
|
||||
this.placeholder.next("Create simulation")
|
||||
}
|
||||
else if (this.shiftEvent.value && this.palleteEvent.value) {
|
||||
this.preInput()
|
||||
this.inputMode = "command"
|
||||
this.placeholder.next("Command palette")
|
||||
}
|
||||
else if (this.clearEvent.value) {
|
||||
this.clear()
|
||||
}
|
||||
else if (this.saveEvent.value) {
|
||||
this.save()
|
||||
}
|
||||
else if (this.refreshEvent.value) {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.wireManager.update.subscribe(val => this.update())
|
||||
}
|
||||
|
||||
preInput() {
|
||||
const elem = <HTMLInputElement>document.getElementById("nameInput")
|
||||
elem.value = ""
|
||||
this.barAlpha.next("1")
|
||||
}
|
||||
|
||||
create() {
|
||||
const elem = <HTMLInputElement>document.getElementById("nameInput")
|
||||
this.barAlpha.next("0")
|
||||
|
||||
if (this.inputMode == "create")
|
||||
success(`Succesfully created simulation ${elem.value}`, "", this.alertOptions)
|
||||
|
||||
else if (this.inputMode == "command")
|
||||
this.eval(elem.value)
|
||||
}
|
||||
|
||||
eval(command: string) {
|
||||
if (!this.commandHistory.includes(command)) // no duplicates
|
||||
this.commandHistory.push(command)
|
||||
|
||||
while (this.commandHistory.length > 10) // max of 10 elements
|
||||
this.commandHistory.shift()
|
||||
|
||||
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] != "-")
|
||||
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)
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.components = []
|
||||
this.wireManager.dispose()
|
||||
this.update()
|
||||
|
||||
success("Succesfully cleared all components", "", this.alertOptions)
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.store.get(this.name)) {
|
||||
this.loadState(this.store.get(this.name))
|
||||
}
|
||||
|
||||
for (const i of this.commandHistoryStore.ls())
|
||||
this.commandHistory[Number(i)] = this.commandHistoryStore.get(i)
|
||||
|
||||
this.update()
|
||||
|
||||
success("Succesfully refreshed to the latest save", "", this.alertOptions)
|
||||
}
|
||||
|
||||
update() {
|
||||
|
@ -64,25 +264,32 @@ export class ComponentManager {
|
|||
render() {
|
||||
let toRemoveDuplicatesFor: Component
|
||||
|
||||
const size = 10
|
||||
const result = this.components.map(component => svg`
|
||||
<rect width=${ subscribe(component.width)}
|
||||
height=${ subscribe(component.height)}
|
||||
x=${ subscribe(component.x)}
|
||||
y=${ subscribe(component.y)}
|
||||
fill="red"
|
||||
stroke="black"
|
||||
@mousedown=${ (e: MouseEvent) => component.handleClick(e)}
|
||||
@mouseup=${ (e: MouseEvent) => {
|
||||
<g>
|
||||
${component.pinsSvg(10, 20)}
|
||||
${component.pinsSvg(10, 20, "output")}
|
||||
|
||||
<rect width=${ subscribe(component.width)}
|
||||
height=${ subscribe(component.height)}
|
||||
x=${ subscribe(component.x)}
|
||||
y=${ subscribe(component.y)}
|
||||
fill="red"
|
||||
stroke="black"
|
||||
rx=20
|
||||
ry=20
|
||||
@mousedown=${ (e: MouseEvent) => component.handleClick(e)}
|
||||
@mouseup=${(e: MouseEvent) => {
|
||||
component.handleMouseUp(e)
|
||||
toRemoveDuplicatesFor = component
|
||||
}}
|
||||
>
|
||||
}}></rect>
|
||||
</g>
|
||||
`);
|
||||
|
||||
if (toRemoveDuplicatesFor)
|
||||
this.removeDuplicates(toRemoveDuplicatesFor)
|
||||
|
||||
return result
|
||||
return svg`${this.wireManager.svg} ${result}`
|
||||
}
|
||||
|
||||
private removeDuplicates(component: Component) {
|
||||
|
@ -95,21 +302,47 @@ export class ComponentManager {
|
|||
.filter((val, index) => instances.indexOf(index) != -1)
|
||||
}
|
||||
|
||||
get state(): MnanagerState {
|
||||
get state(): ManagerState {
|
||||
const components = Array.from((new Set(this.components)).values())
|
||||
return {
|
||||
components: components.map(value => value.state)
|
||||
components: components.map(value => value.state),
|
||||
position: this.screen.position as [number, number],
|
||||
scale: this.screen.scale as [number, number],
|
||||
wires: this.wireManager.state
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state:MnanagerState) {
|
||||
public getComponentById(id: number) {
|
||||
return this.components.find(val => val.id === id)
|
||||
}
|
||||
|
||||
loadState(state: ManagerState) {
|
||||
if (!state.wires) //old state
|
||||
return
|
||||
|
||||
this.wireManager.dispose()
|
||||
this.clicked = false
|
||||
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.tryResolving()
|
||||
})
|
||||
|
||||
this.screen.scale = state.scale
|
||||
this.screen.position = state.position
|
||||
|
||||
this.update()
|
||||
}
|
||||
|
||||
save(){
|
||||
//TODO: implement
|
||||
save(name?: string) {
|
||||
for (let i = 0; i < this.commandHistory.length; i++) {
|
||||
const element = this.commandHistory[i];
|
||||
this.commandHistoryStore.set(i.toString(), element)
|
||||
}
|
||||
this.store.set(name || this.name, this.state)
|
||||
success("Saved the simulation succesfully!", "", this.alertOptions)
|
||||
}
|
||||
}
|
105
src/ts/common/componentManager/componentTemplateStore.ts
Normal file
105
src/ts/common/componentManager/componentTemplateStore.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { Store } from "../store";
|
||||
import { ComponentTemplate } from "./interfaces";
|
||||
import { ComponentManager } from "./componentManager";
|
||||
import { success, error } from "toastr"
|
||||
|
||||
@Singleton
|
||||
export class ComponentTemplateStore {
|
||||
public store = new Store<ComponentTemplate>("componentTemplate")
|
||||
|
||||
public commands = {
|
||||
template: (ctx: ComponentManager, args: string[], flags: string[]) => {
|
||||
const command = args[0]
|
||||
switch (command) {
|
||||
case (undefined):
|
||||
for (let i of flags) {
|
||||
if (i === "--version" || i === "-v")
|
||||
return success("1.0.1", "", ctx.alertOptions)
|
||||
}
|
||||
|
||||
error(`Welcome to the component template program!
|
||||
To get started, try running this basic commands:
|
||||
${["--version", "ls"].map(val => `${val}`).join(" ")}
|
||||
`, "", {
|
||||
...ctx.alertOptions,
|
||||
timeOut: 7500
|
||||
})
|
||||
|
||||
break
|
||||
case ("ls"):
|
||||
success(`Here is a list of all the current registered component templates (including ics):
|
||||
<ul>
|
||||
${this.store.ls().map(val => `
|
||||
<li>
|
||||
${val}
|
||||
</li>
|
||||
`).join(" ")}
|
||||
</ul>
|
||||
`, "", ctx.alertOptions)
|
||||
break
|
||||
case ("info"):
|
||||
if (!args[1])
|
||||
return error("You need to specify a template name", "", ctx.alertOptions)
|
||||
|
||||
const data = this.store.get(args[1])
|
||||
|
||||
if (!data)
|
||||
return error(`Component ${args[1]} doesnt exist`, "", ctx.alertOptions)
|
||||
|
||||
const showFunction = flags.find(value =>
|
||||
value === "-sf" || value === "--showFunctions"
|
||||
)
|
||||
|
||||
success(`
|
||||
Name: ${data.name} <br>
|
||||
Inputs: ${data.inputs} <br>
|
||||
Outputs: ${data.outputs}
|
||||
${showFunction ? `<br> Activation: ${data.activation}` : ""}
|
||||
`, "", ctx.alertOptions)
|
||||
break
|
||||
default:
|
||||
error(`${command} is not a valid command for the template program`, "", ctx.alertOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.store.set("buffer", {
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
name: "buffer",
|
||||
version: "1.0.0",
|
||||
activation: `
|
||||
ctx.outputs[0].value = ctx.inputs[0].value
|
||||
`.trim()
|
||||
})
|
||||
this.store.set("not", {
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
name: "buffer",
|
||||
version: "1.0.0",
|
||||
activation: `
|
||||
ctx.outputs[0].value = !ctx.inputs[0].value
|
||||
`.trim()
|
||||
})
|
||||
this.store.set("and", {
|
||||
inputs: 2,
|
||||
outputs: 1,
|
||||
name: "and",
|
||||
version: "1.0.0",
|
||||
activation: `
|
||||
ctx.outputs[0].value = ctx.inputs[0].value && ctx.inputs[1].value
|
||||
`.trim()
|
||||
})
|
||||
this.store.set("true", {
|
||||
inputs: 0,
|
||||
outputs: 1,
|
||||
name: "true",
|
||||
version: "1.0.0",
|
||||
activation: `
|
||||
ctx.outputs[0].value = true
|
||||
`.trim()
|
||||
})
|
||||
}
|
||||
}
|
31
src/ts/common/componentManager/download.ts
Normal file
31
src/ts/common/componentManager/download.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { ComponentManager } from "./componentManager";
|
||||
import { success } from "toastr"
|
||||
import { saveAs } from "file-saver"
|
||||
|
||||
const version = "1.0.0"
|
||||
|
||||
export const download = (ctx: ComponentManager, args: string[], flags:string[]) => {
|
||||
//important flags
|
||||
for (let i of flags) {
|
||||
if (i === "--version" || i === "-v")
|
||||
return success(`${version}`, "", ctx.alertOptions)
|
||||
else if (i === "--help" || i === "-h")
|
||||
return success(`Run "download" to download the save as a json file.
|
||||
Flags:<ul>
|
||||
<li>-v or --version to get the version.</li>
|
||||
<li>-h or --help to get help (ou are reading that right now)</li>
|
||||
<li>-s or --save to automatically save before downloading</li>
|
||||
</ul>`,"",ctx.alertOptions)
|
||||
}
|
||||
|
||||
const command = args[0]
|
||||
|
||||
if (command === undefined){
|
||||
if (flags.includes("-s") || flags.includes("--save"))
|
||||
ctx.save()
|
||||
|
||||
const data = JSON.stringify(ctx.state)
|
||||
saveAs(new Blob([data]), `${ctx.name}.json`)
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,17 @@
|
|||
import { ComponentState } from "../component/interfaces";
|
||||
import { WireState } from "../wires/interface";
|
||||
|
||||
export interface MnanagerState {
|
||||
export interface ManagerState {
|
||||
components: ComponentState[]
|
||||
scale: [number,number]
|
||||
position: [number,number]
|
||||
wires: WireState
|
||||
}
|
||||
|
||||
export interface ComponentTemplate {
|
||||
name: string
|
||||
version: string
|
||||
activation: string
|
||||
inputs: number
|
||||
outputs: number
|
||||
}
|
1
src/ts/common/pin/index.ts
Normal file
1
src/ts/common/pin/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./pin"
|
65
src/ts/common/pin/pin.ts
Normal file
65
src/ts/common/pin/pin.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { BehaviorSubject, Subject, Subscription } from "rxjs";
|
||||
import { map } from "rxjs/operators"
|
||||
import clamp from "../clamp/clamp";
|
||||
import { Component } from "../component";
|
||||
|
||||
export class Pin {
|
||||
private static lastId = 0
|
||||
|
||||
public pair: Pin
|
||||
private subscriptions: Subscription[] = []
|
||||
|
||||
public id: number
|
||||
public _value = 0
|
||||
public color = new BehaviorSubject<[number, number, number, number]>([0, 0, 0,0])
|
||||
public memory: any = {}
|
||||
public valueChanges = new Subject<number>()
|
||||
|
||||
public svgColor = this.color.pipe(map(val =>
|
||||
`rgb(${val.join(",")})`
|
||||
))
|
||||
|
||||
constructor(public allowWrite = true, public of: Component) {
|
||||
this.setValue(0)
|
||||
this.id = Pin.lastId++
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value
|
||||
}
|
||||
|
||||
set value(value: number) {
|
||||
if (!this.allowWrite) return
|
||||
this.setValue(value)
|
||||
|
||||
}
|
||||
|
||||
public setValue(value: number) {
|
||||
this._value = clamp(value, 0, 1)
|
||||
this.valueChanges.next(this._value)
|
||||
|
||||
const color: [number, number, number, number] = (value > 0.5) ?
|
||||
[255, 216, 20, 1] :
|
||||
[90, 90, 90, 1]
|
||||
|
||||
this.color.next((this.pair) ? color : [0,0,0,0])
|
||||
}
|
||||
|
||||
public bindTo(pin: Pin){
|
||||
this.pair = pin
|
||||
const subscription = pin.valueChanges.subscribe(val => this.setValue(val))
|
||||
|
||||
this.subscriptions.push(subscription)
|
||||
}
|
||||
|
||||
public unbind(pin: Pin) {
|
||||
if (this.pair == pin){
|
||||
this.pair = null
|
||||
this.subscriptions.forEach(val => val.unsubscribe())
|
||||
}
|
||||
}
|
||||
|
||||
public update(){
|
||||
this.setValue(this._value)
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ export class Screen {
|
|||
viewBox = combineLatest(this.width, this.height).pipe(map((values: [number,number]) =>
|
||||
this.getViewBox(...values)
|
||||
));
|
||||
|
||||
private scrollStep = 1.3
|
||||
private position = [0, 0]
|
||||
|
||||
public position = [0, 0]
|
||||
public scale = [2, 2]
|
||||
|
||||
private zoomLimits: [number,number] = [0.1,10]
|
||||
|
||||
private scrollStep = 1.3
|
||||
public mousePosition = [this.width.value / 2, this.height.value / 2]
|
||||
|
||||
constructor() {
|
||||
|
|
1
src/ts/common/store/index.ts
Normal file
1
src/ts/common/store/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./store"
|
29
src/ts/common/store/settings.ts
Normal file
29
src/ts/common/store/settings.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { ComponentManager } from "../componentManager";
|
||||
import { success, error } from "toastr"
|
||||
|
||||
@Singleton
|
||||
export class Settings {
|
||||
version = "1.0.0"
|
||||
|
||||
commands = (ctx: ComponentManager, args: string[], flags: string[]) => {
|
||||
//important flags
|
||||
for (let i of flags) {
|
||||
if (i === "--version" || i === "-v")
|
||||
return success(`${this.version}`, "", ctx.alertOptions)
|
||||
}
|
||||
|
||||
const command = args[0]
|
||||
|
||||
if (command === undefined)
|
||||
return success(
|
||||
`Welcome to the settings cli. You can use this to tweak settings in any way imaginable!`,
|
||||
"",
|
||||
ctx.alertOptions)
|
||||
|
||||
//nothing here
|
||||
error(`Commands ${args} couldnt be found`,"",ctx.alertOptions)
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
}
|
28
src/ts/common/store/store.ts
Normal file
28
src/ts/common/store/store.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
export class Store<T> {
|
||||
constructor(private name: string){ }
|
||||
|
||||
get(key:string):T{
|
||||
const data = localStorage[`${this.name}/${key}`]
|
||||
|
||||
if(data)
|
||||
return JSON.parse(data).value
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
set(key:string,value:T){
|
||||
localStorage[`${this.name}/${key}`] = JSON.stringify({ value })
|
||||
return this
|
||||
}
|
||||
|
||||
ls() {
|
||||
let keys = []
|
||||
|
||||
for (const i in localStorage){
|
||||
if (i.indexOf(this.name) == 0)
|
||||
keys.push(i.substr(this.name.length + 1))
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
}
|
1
src/ts/common/wires/index.ts
Normal file
1
src/ts/common/wires/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./wireManager"
|
12
src/ts/common/wires/interface.ts
Normal file
12
src/ts/common/wires/interface.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export interface WireStateVal {
|
||||
from: {
|
||||
owner: number
|
||||
index: number
|
||||
},
|
||||
to: {
|
||||
owner: number
|
||||
index: number
|
||||
}
|
||||
}
|
||||
|
||||
export type WireState = WireStateVal[]
|
15
src/ts/common/wires/wire.ts
Normal file
15
src/ts/common/wires/wire.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Pin } from "../pin";
|
||||
|
||||
export class Wire {
|
||||
constructor (public input:Pin,public output:Pin){
|
||||
this.output.bindTo(this.input)
|
||||
this.input.pair = this.output
|
||||
this.input.update()
|
||||
this.output.update()
|
||||
}
|
||||
|
||||
public dispose(){
|
||||
this.output.unbind(this.input)
|
||||
this.input.pair = null
|
||||
}
|
||||
}
|
81
src/ts/common/wires/wireManager.ts
Normal file
81
src/ts/common/wires/wireManager.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { Pin } from "../pin";
|
||||
import { Wire } from "./wire";
|
||||
import { svg } from "lit-html";
|
||||
import { subscribe } from "lit-rx";
|
||||
import { ComponentManager } from "../componentManager";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { WireState, WireStateVal } from "./interface";
|
||||
|
||||
@Singleton
|
||||
export class WireManager {
|
||||
public start: Pin
|
||||
public end: Pin
|
||||
|
||||
private wires: Wire[] = []
|
||||
|
||||
public update = new Subject<boolean>()
|
||||
|
||||
constructor() { }
|
||||
|
||||
public add(data: Pin) {
|
||||
if (data.allowWrite) //output
|
||||
this.start = data
|
||||
else
|
||||
this.end = data
|
||||
|
||||
this.tryResolving()
|
||||
}
|
||||
|
||||
public dispose(){
|
||||
for (let i of this.wires)
|
||||
i.dispose()
|
||||
|
||||
this.wires = []
|
||||
}
|
||||
|
||||
public tryResolving() {
|
||||
if (this.start && this.end && this.start != this.end) {
|
||||
if (this.canBind(this.start, this.end)) {
|
||||
this.wires.push(new Wire(this.start, this.end))
|
||||
this.start = null
|
||||
this.end = null
|
||||
this.update.next(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private canBind(start: Pin, end: Pin) {
|
||||
if (this.wires.find(val => val.output === end))
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
get svg() {
|
||||
return this.wires.map(val => {
|
||||
const i = val.input.of
|
||||
const o = val.output.of
|
||||
return svg`
|
||||
<line x1=${subscribe(i.pinx(false, 20))}
|
||||
x2=${subscribe(o.pinx(true, 20))}
|
||||
y1=${subscribe(i.piny(false,i.outputPins.indexOf(val.input)))}
|
||||
y2=${subscribe(o.piny(true,o.inputPins.indexOf(val.output)))}
|
||||
stroke=${subscribe(val.input.svgColor)}
|
||||
>
|
||||
</line>
|
||||
`})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -4,37 +4,59 @@ import { Screen } from "./common/screen.ts";
|
|||
import { Component } from "./common/component";
|
||||
import { FunctionStore } from "./common/activation/activationStore";
|
||||
import { ComponentManager } from "./common/componentManager";
|
||||
import { map } from "rxjs/operators";
|
||||
|
||||
const screen = new Screen()
|
||||
|
||||
const test = new FunctionStore()
|
||||
test.register("buffer",(data) => {
|
||||
return true;
|
||||
})
|
||||
|
||||
const manager = new ComponentManager()
|
||||
manager.components.push(new Component("none",[200,100],[200,30]))
|
||||
manager.components.push(new Component("none",[300,100],[200,30]))
|
||||
manager.components.push(new Component("none",[400,100],[200,30]))
|
||||
manager.components.push(new Component("none",[500,100],[200,30]))
|
||||
manager.components.push(new Component("none",[600,100],[200,30]))
|
||||
manager.components.push(new Component("and",[200,100],[100,100]))
|
||||
manager.components.push(new Component("not",[200,500],[100,100]))
|
||||
manager.components.push(new Component("true",[200,500],[100,100]))
|
||||
manager.update()
|
||||
|
||||
console.log(manager.state)
|
||||
|
||||
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`
|
||||
<svg height=${ subscribe(screen.height) } width=${ subscribe(screen.width) }
|
||||
@mousemove=${(e:MouseEvent) => {
|
||||
<div @mousemove=${(e:MouseEvent) => handleEvent(e,(e:MouseEvent) => {
|
||||
manager.handleMouseMove(e)
|
||||
screen.updateMouse(e)
|
||||
}}
|
||||
viewBox=${subscribe(screen.viewBox)}
|
||||
@mousedown=${(e:MouseEvent) => manager.handleMouseDown(e)}
|
||||
@mouseup=${(e:MouseEvent) => manager.handleMouseUp(e)}
|
||||
@wheel=${(e:WheelEvent) => screen.handleScroll(e)}
|
||||
>
|
||||
${ subscribe(manager.svgs) }
|
||||
</svg>
|
||||
})}
|
||||
@mousedown=${(e:MouseEvent) => handleEvent(e,(e:MouseEvent) =>
|
||||
manager.handleMouseDown(e)
|
||||
)}
|
||||
@mouseup=${(e:MouseEvent) => handleEvent(e,(e:MouseEvent) =>
|
||||
manager.handleMouseUp(e)
|
||||
)}
|
||||
@wheel=${(e:MouseEvent) => handleEvent(e,(e:WheelEvent) =>
|
||||
screen.handleScroll(e)
|
||||
)}>
|
||||
|
||||
<div id=${subscribe(manager.barAlpha.pipe(map(val =>
|
||||
(val == "1") ? "shown" : ""
|
||||
)))}
|
||||
class=createBar>
|
||||
<div class="topContainer">
|
||||
<div>
|
||||
<input name="ComponentName" id="nameInput"
|
||||
placeholder=${subscribe(manager.placeholder)}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svg height=${ subscribe(screen.height) }
|
||||
width=${ subscribe(screen.width) }
|
||||
viewBox=${subscribe(screen.viewBox)}>
|
||||
${ subscribe(manager.svgs) }
|
||||
</svg>
|
||||
</div>
|
||||
`, document.body)
|
||||
|
||||
manager.update()
|
Loading…
Reference in a new issue