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",
|
"description": "A template for writing jam games in HTML5 + TypeScript",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --open --mode development",
|
"dev": "webpack-dev-server --open --mode development",
|
||||||
"build": "webpack --mode production"
|
"build": "webpack --mode production"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -32,5 +32,13 @@
|
||||||
"webpack": "^4.29.5",
|
"webpack": "^4.29.5",
|
||||||
"webpack-cli": "^3.2.3",
|
"webpack-cli": "^3.2.3",
|
||||||
"webpack-dev-server": "^3.2.0"
|
"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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
hello world
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,3 +1,14 @@
|
||||||
body {
|
html, body {
|
||||||
padding: 0
|
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/*"
|
"types/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"module": "es6",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
@ -17,7 +17,9 @@
|
||||||
],
|
],
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"moduleResolution": "Node"
|
"moduleResolution": "Node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"src/**/*.ts"
|
||||||
|
|
Loading…
Reference in a new issue