frst commit

This commit is contained in:
Matei Adriel 2019-05-22 22:43:04 +03:00
parent 5ae57c1910
commit a4dd3c6468
19 changed files with 385 additions and 6 deletions

View file

@ -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"
}
}

View file

@ -8,6 +8,7 @@
</head>
<body>
hello world
</body>
</html>

View file

@ -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;
}

View 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
}
}

View file

@ -0,0 +1 @@
export * from "./toActivation"

View 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

View 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()
}

View 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)
}

View file

@ -0,0 +1 @@
export * from "./clamp"

View 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)
}
}

View file

@ -0,0 +1 @@
export * from "./component"

View file

@ -0,0 +1,5 @@
export interface ComponentState {
position: [number,number]
scale: [number,number]
activationType: string
}

View 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
}
}

View file

@ -0,0 +1 @@
export * from "./componentManager"

View file

@ -0,0 +1,5 @@
import { ComponentState } from "../component/interfaces";
export interface MnanagerState {
components: ComponentState[]
}

View file

@ -0,0 +1 @@
export * from "./screen"

View 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]]
}
}

View file

@ -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()

View file

@ -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"