frst commit
This commit is contained in:
parent
5ae57c1910
commit
a4dd3c6468
10
package.json
10
package.json
|
@ -4,7 +4,7 @@
|
|||
"description": "A template for writing jam games in HTML5 + TypeScript",
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --open --mode development",
|
||||
"dev": "webpack-dev-server --open --mode development",
|
||||
"build": "webpack --mode production"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -32,5 +32,13 @@
|
|||
"webpack": "^4.29.5",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-server": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eix/utils": "git+https://github.com/eix-js/utils.git",
|
||||
"haunted": "^4.3.0",
|
||||
"lit-html": "^1.0.0",
|
||||
"lit-rx": "0.0.2",
|
||||
"prelude-ts": "^0.8.2",
|
||||
"rxjs": "^6.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
hello world
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,3 +1,14 @@
|
|||
body {
|
||||
padding: 0
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
height:100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
svg {
|
||||
background-color: #444444;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
23
src/ts/common/activation/activationStore.ts
Normal file
23
src/ts/common/activation/activationStore.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { activationFunction, activationFunctionParam } from "./interfaces"
|
||||
import { toActivationFunction } from "./toActivation";
|
||||
|
||||
@Singleton
|
||||
export class FunctionStore {
|
||||
functions = new Map<string, activationFunction>()
|
||||
|
||||
private storageKeyword: string
|
||||
|
||||
constructor(name="activation") {
|
||||
this.storageKeyword =`/${name}`
|
||||
for (let i in localStorage) {
|
||||
if (i.indexOf(this.storageKeyword) == 0)
|
||||
this.register(i.substr(this.storageKeyword.length), localStorage[i])
|
||||
}
|
||||
}
|
||||
|
||||
register(name: string, activation: activationFunctionParam) {
|
||||
this.functions.set(name, toActivationFunction(activation))
|
||||
localStorage[`${this.storageKeyword.substr(1)}/${name}`] = activation
|
||||
}
|
||||
}
|
1
src/ts/common/activation/index.ts
Normal file
1
src/ts/common/activation/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./toActivation"
|
4
src/ts/common/activation/interfaces.ts
Normal file
4
src/ts/common/activation/interfaces.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type activationInput = any[]
|
||||
export type activationOutput = boolean
|
||||
export type activationFunctionParam = ( (data:activationInput) => activationOutput ) | string
|
||||
export type activationFunction = (data:activationInput) => activationOutput
|
8
src/ts/common/activation/toActivation.ts
Normal file
8
src/ts/common/activation/toActivation.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { activationFunctionParam, activationFunction } from "./interfaces";
|
||||
|
||||
export const toActivationFunction = (original: activationFunctionParam) => {
|
||||
const stringified = (typeof original == "string") ? original : original.toString()
|
||||
const final = new Function(`return ${stringified}`) as () => activationFunction
|
||||
|
||||
return final()
|
||||
}
|
5
src/ts/common/clamp/clamp.ts
Normal file
5
src/ts/common/clamp/clamp.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default function clamp(value:number, min:number, max:number) {
|
||||
return min < max
|
||||
? (value < min ? min : value > max ? max : value)
|
||||
: (value < max ? max : value > min ? min : value)
|
||||
}
|
1
src/ts/common/clamp/index.ts
Normal file
1
src/ts/common/clamp/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./clamp"
|
76
src/ts/common/component/component.ts
Normal file
76
src/ts/common/component/component.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { Vector } from "prelude-ts"
|
||||
import { Subject, BehaviorSubject } from "rxjs";
|
||||
import { ComponentState } from "./interfaces";
|
||||
import { map } from "rxjs/operators";
|
||||
import { Screen } from "../screen.ts";
|
||||
|
||||
export class Component {
|
||||
private static screen = new Screen()
|
||||
|
||||
public position = new BehaviorSubject<number[]>(null)
|
||||
public scale = new BehaviorSubject<number[]>(null)
|
||||
public clicked = false
|
||||
|
||||
private mouserDelta: number[]
|
||||
|
||||
constructor(public activationType: string,
|
||||
position: [number, number] = [0, 0],
|
||||
scale: [number, number] = [0, 0]) {
|
||||
this.position.next(position)
|
||||
this.scale.next(scale)
|
||||
}
|
||||
|
||||
handleMouseUp(e: MouseEvent) {
|
||||
this.clicked = false
|
||||
}
|
||||
|
||||
move(e: MouseEvent) {
|
||||
const mousePosition = Component.screen.getWorldPosition(e.clientX, e.clientY)
|
||||
this.position.next(mousePosition.map((value, index) =>
|
||||
value - this.mouserDelta[index]
|
||||
))
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
const mousePosition = Component.screen.getWorldPosition(e.clientX, e.clientY)
|
||||
|
||||
this.mouserDelta = this.position.value.map((value, index) =>
|
||||
mousePosition[index] - value
|
||||
)
|
||||
this.clicked = true
|
||||
}
|
||||
|
||||
get state(): ComponentState {
|
||||
return {
|
||||
position: this.position.value as [number, number],
|
||||
scale: this.position.value as [number, number],
|
||||
activationType: this.activationType
|
||||
}
|
||||
}
|
||||
|
||||
get x() {
|
||||
return this.position.pipe(map(val =>
|
||||
val[0]
|
||||
))
|
||||
}
|
||||
get y() {
|
||||
return this.position.pipe(map(val =>
|
||||
val[1]
|
||||
))
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this.scale.pipe(map(val =>
|
||||
val[0]
|
||||
))
|
||||
}
|
||||
get height() {
|
||||
return this.scale.pipe(map(val =>
|
||||
val[1]
|
||||
))
|
||||
}
|
||||
|
||||
static fromState(state:ComponentState){
|
||||
return new Component(state.activationType, state.position, state.scale)
|
||||
}
|
||||
}
|
1
src/ts/common/component/index.ts
Normal file
1
src/ts/common/component/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./component"
|
5
src/ts/common/component/interfaces.ts
Normal file
5
src/ts/common/component/interfaces.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export interface ComponentState {
|
||||
position: [number,number]
|
||||
scale: [number,number]
|
||||
activationType: string
|
||||
}
|
115
src/ts/common/componentManager/componentManager.ts
Normal file
115
src/ts/common/componentManager/componentManager.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { Singleton } from "@eix/utils";
|
||||
import { Component } from "../component/component";
|
||||
import { Subject } from "rxjs";
|
||||
import { svg, SVGTemplateResult } from "lit-html";
|
||||
import { subscribe } from "lit-rx";
|
||||
import { Screen } from "../screen.ts";
|
||||
import { MnanagerState } from "./interfaces";
|
||||
|
||||
@Singleton
|
||||
export class ComponentManager {
|
||||
public components: Component[] = []
|
||||
public svgs = new Subject<SVGTemplateResult[]>()
|
||||
|
||||
private onTop: Component
|
||||
private clicked = false
|
||||
private screen = new Screen()
|
||||
|
||||
constructor() {
|
||||
this.svgs.next(this.render())
|
||||
}
|
||||
|
||||
update() {
|
||||
this.svgs.next(this.render())
|
||||
}
|
||||
|
||||
handleMouseDown(e: MouseEvent) {
|
||||
this.clicked = true
|
||||
}
|
||||
|
||||
handleMouseUp(e: MouseEvent) {
|
||||
this.clicked = false
|
||||
}
|
||||
|
||||
handleMouseMove(e: MouseEvent) {
|
||||
let toAddOnTop: number
|
||||
let outsideComponents = true
|
||||
|
||||
for (let i = 0; i < this.components.length; i++) {
|
||||
const component = this.components[i]
|
||||
if (component.clicked) {
|
||||
outsideComponents = false
|
||||
component.move(e)
|
||||
if (this.onTop != component) {
|
||||
toAddOnTop = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toAddOnTop >= 0) {
|
||||
this.onTop = this.components[toAddOnTop]
|
||||
this.components.push(this.onTop)
|
||||
this.update()
|
||||
}
|
||||
|
||||
else if (outsideComponents && this.clicked) {
|
||||
const mousePosition = [e.clientX, e.clientY]
|
||||
const delta = mousePosition.map((value, index) =>
|
||||
this.screen.mousePosition[index] - value
|
||||
) as [number, number]
|
||||
this.screen.move(...delta)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let toRemoveDuplicatesFor: Component
|
||||
|
||||
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) => {
|
||||
component.handleMouseUp(e)
|
||||
toRemoveDuplicatesFor = component
|
||||
}}
|
||||
>
|
||||
`);
|
||||
|
||||
if (toRemoveDuplicatesFor)
|
||||
this.removeDuplicates(toRemoveDuplicatesFor)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private removeDuplicates(component: Component) {
|
||||
let instances = this.components
|
||||
.map((value, index) => (value == component) ? index : null)
|
||||
.filter(value => value)
|
||||
instances.pop()
|
||||
|
||||
this.components = this.components
|
||||
.filter((val, index) => instances.indexOf(index) != -1)
|
||||
}
|
||||
|
||||
get state(): MnanagerState {
|
||||
const components = Array.from((new Set(this.components)).values())
|
||||
return {
|
||||
components: components.map(value => value.state)
|
||||
}
|
||||
}
|
||||
|
||||
loadState(state:MnanagerState) {
|
||||
this.clicked = false
|
||||
this.components = state.components.map(value => Component.fromState(value))
|
||||
this.onTop = null
|
||||
this.update()
|
||||
}
|
||||
|
||||
save(){
|
||||
//TODO: implement
|
||||
}
|
||||
}
|
1
src/ts/common/componentManager/index.ts
Normal file
1
src/ts/common/componentManager/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./componentManager"
|
5
src/ts/common/componentManager/interfaces.ts
Normal file
5
src/ts/common/componentManager/interfaces.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { ComponentState } from "../component/interfaces";
|
||||
|
||||
export interface MnanagerState {
|
||||
components: ComponentState[]
|
||||
}
|
1
src/ts/common/screen.ts/index.ts
Normal file
1
src/ts/common/screen.ts/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./screen"
|
72
src/ts/common/screen.ts/screen.ts
Normal file
72
src/ts/common/screen.ts/screen.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { Subject, fromEvent, BehaviorSubject, combineLatest } from "rxjs"
|
||||
import { Singleton } from "@eix/utils"
|
||||
import { map } from "rxjs/operators";
|
||||
import clamp from "../clamp/clamp";
|
||||
|
||||
@Singleton
|
||||
export class Screen {
|
||||
width = new BehaviorSubject<number>(0)
|
||||
height = new BehaviorSubject<number>(0)
|
||||
viewBox = combineLatest(this.width, this.height).pipe(map((values: [number,number]) =>
|
||||
this.getViewBox(...values)
|
||||
));
|
||||
|
||||
private scrollStep = 1.3
|
||||
private position = [0, 0]
|
||||
public scale = [2, 2]
|
||||
|
||||
private zoomLimits: [number,number] = [0.1,10]
|
||||
|
||||
public mousePosition = [this.width.value / 2, this.height.value / 2]
|
||||
|
||||
constructor() {
|
||||
this.update()
|
||||
|
||||
fromEvent(window, "resize").subscribe(() => this.update())
|
||||
}
|
||||
|
||||
updateMouse(e:MouseEvent){
|
||||
this.mousePosition = [e.clientX,e.clientY]
|
||||
}
|
||||
|
||||
handleScroll(e: WheelEvent){
|
||||
e.preventDefault()
|
||||
|
||||
const size = [this.width.value,this.height.value]
|
||||
const mouseFraction = size.map((value,index) => this.mousePosition[index] / value)
|
||||
const sign = e.deltaY / Math.abs(e.deltaY)
|
||||
const zoom = this.scrollStep ** sign
|
||||
const newScale = this.scale.map(value => clamp(value * zoom, ...this.zoomLimits ))
|
||||
const delta = this.scale.map((value,index) =>
|
||||
size[index] * (newScale[index] - this.scale[index]) * mouseFraction[index]
|
||||
)
|
||||
|
||||
this.scale = newScale
|
||||
this.position = this.position.map((value,index) => value - delta[index])
|
||||
this.update()
|
||||
}
|
||||
|
||||
move(x:number, y:number) {
|
||||
this.position[0] += x * this.scale[0]
|
||||
this.position[1] += y * this.scale[1]
|
||||
this.update()
|
||||
}
|
||||
|
||||
getViewBox(width: number, height:number) {
|
||||
return [
|
||||
this.position[0],
|
||||
this.position[1],
|
||||
this.scale[0] * width,
|
||||
this.scale[1] * height
|
||||
].join(" ")
|
||||
}
|
||||
|
||||
update() {
|
||||
this.height.next(window.innerHeight)
|
||||
this.width.next(window.innerWidth)
|
||||
}
|
||||
|
||||
getWorldPosition(x:number, y:number) {
|
||||
return [x * this.scale[0], y * this.scale[1]]
|
||||
}
|
||||
}
|
|
@ -1 +1,40 @@
|
|||
console.log("hello world!")
|
||||
import { render, html, svg } from "lit-html"
|
||||
import { subscribe } from "lit-rx"
|
||||
import { Screen } from "./common/screen.ts";
|
||||
import { Component } from "./common/component";
|
||||
import { FunctionStore } from "./common/activation/activationStore";
|
||||
import { ComponentManager } from "./common/componentManager";
|
||||
|
||||
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.update()
|
||||
|
||||
console.log(manager.state)
|
||||
|
||||
render(html`
|
||||
<svg height=${ subscribe(screen.height) } width=${ subscribe(screen.width) }
|
||||
@mousemove=${(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>
|
||||
`, document.body)
|
||||
|
||||
manager.update()
|
|
@ -6,7 +6,7 @@
|
|||
"types/*"
|
||||
]
|
||||
},
|
||||
"module": "es6",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
|
@ -17,7 +17,9 @@
|
|||
],
|
||||
"noImplicitAny": true,
|
||||
"alwaysStrict": true,
|
||||
"moduleResolution": "Node"
|
||||
"moduleResolution": "Node",
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
|
Loading…
Reference in a new issue