Last active
January 25, 2023 09:38
-
-
Save lxchurbakov/9d2d936202da7ea4ba7f396b4ef58e50 to your computer and use it in GitHub Desktop.
plugins
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Image = HTMLImageElement; | |
const loadImage = async (url: string) => new Promise<Image>((resolve, reject) => { | |
const img = new Image(); | |
img.onload = () => resolve(img); | |
img.onerror = (e) => reject(e); | |
img.src = url; | |
}); | |
export default class AssetsStore { | |
private store = new Map<string, Image>(); | |
private status = { loaded: 0, all: 0 }; | |
public add = async (name: string, url: string) => { | |
this.status.all++; | |
const img = await loadImage(url); | |
this.store.set(name, img); | |
this.status.loaded++; | |
}; | |
public get = (name: string) => { | |
return this.store.get(name) || null; | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { EventEmitter } from '../../libs/events'; | |
export type Point = { x: number, y: number }; | |
export type Rect = { width: number, height: number }; | |
export default class Canvas { | |
public rect: Rect; | |
public canvas: HTMLCanvasElement; | |
public context: CanvasRenderingContext2D; | |
public onRender = new EventEmitter<CanvasRenderingContext2D>(); | |
constructor (public root: HTMLElement) { | |
const pixelRatio = window.devicePixelRatio || 1; | |
this.rect = this.root.getBoundingClientRect(); | |
this.canvas = document.createElement('canvas'); | |
this.canvas.style.width = this.rect.width + 'px'; | |
this.canvas.style.height = this.rect.height + 'px'; | |
this.canvas.width = this.rect.width * pixelRatio; | |
this.canvas.height = this.rect.height * pixelRatio; | |
this.root.appendChild(this.canvas); | |
this.context = this.canvas.getContext('2d'); | |
this.context.scale(pixelRatio, pixelRatio); | |
requestAnimationFrame(this.render); | |
} | |
public render = () => { | |
this.context.clearRect(0, 0, this.rect.width, this.rect.height); | |
this.onRender.emitSync(this.context); | |
requestAnimationFrame(this.render); | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
type AsyncListener<T> = (arg: T) => Promise<any>; | |
type SyncListener<T> = (arg: T) => any; | |
type Listener<T> = AsyncListener<T> | SyncListener<T>; | |
export class EventEmitter<T> { | |
listeners: { listener: Listener<T>, priority: number }[] = []; | |
subscribe = (listener: Listener<T>, priority: number = 0) => { | |
this.listeners.push({ listener ,priority }); | |
}; | |
unsubscribe = (listener: Listener<T>) => { | |
this.listeners = _.remove(this.listeners, (l) => l.listener === listener); | |
}; | |
get = () => this.listeners.sort((a, b) => a.priority - b.priority).map((l) => l.listener); | |
emitSequenceAsync = (data: T) => { | |
return this.get().reduce((acc, listener) => | |
acc.then((intermediate) => Promise.resolve(listener(intermediate))) | |
, Promise.resolve(data)); | |
}; | |
emitSequenceSync = (data: T) => { | |
return this.get().reduce((acc, listener) => listener(acc) as T, data); // unsafe here | |
}; | |
emitAsync = (data: T) => { | |
return this.get().reduce((acc, listener) => | |
acc.then(() => Promise.resolve(listener(data))) | |
, Promise.resolve()); | |
}; | |
emitSync = (data: T) => { | |
this.get().forEach((listener) => { | |
listener(data); | |
}); | |
}; | |
emitParallelAsync = (data: T) => { | |
return Promise.all(this.get().map((listener) => listener(data))); | |
}; | |
emitParallelSync = (data: T) => { | |
return this.get().map((listener) => listener(data)); | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { EventEmitter } from '../../libs/events'; | |
import { Point, Rect } from './canvas'; | |
/** | |
* This plugin handles html events and forwards them | |
* into "drag", "click" etc | |
*/ | |
export default class AdvancedEvents { | |
public onMouseDown = new EventEmitter<Point>(); | |
public onMouseUp = new EventEmitter<Point>(); | |
public onMouseMove = new EventEmitter<Point>(); | |
public onDrag = new EventEmitter<Point>(); | |
public onClick = new EventEmitter<Point>(); | |
public onZoom = new EventEmitter<Point>(); | |
public onKey = new EventEmitter<number>(); | |
public onKeyDown = new EventEmitter<number>(); | |
public onKeyUp = new EventEmitter<number>(); | |
public onSetup = new EventEmitter<HTMLElement>(); | |
private lastmousepos: Point | null = null; | |
private mousepressed = false; | |
private rect: DOMRect; | |
private map = (p: Point) => { | |
return ({ x: p.x - this.rect.x, y: p.y - this.rect.y }); | |
} | |
constructor(private node: HTMLElement) { | |
const eventHandlerNode = document.createElement('div'); | |
eventHandlerNode.style.position = 'absolute'; | |
eventHandlerNode.style.top = '0px'; | |
eventHandlerNode.style.left = '0px'; | |
eventHandlerNode.style.width = '100%'; | |
eventHandlerNode.style.height = '100%'; | |
eventHandlerNode.style.zIndex = '100'; | |
this.node.appendChild(eventHandlerNode); | |
this.rect = this.node.getBoundingClientRect(); | |
setInterval(() => { | |
this.rect = this.node.getBoundingClientRect(); | |
}, 1000); | |
eventHandlerNode.addEventListener('mousedown', (e) => { | |
const { clientX: x, clientY: y } = e; | |
this.lastmousepos = { x, y }; | |
this.mousepressed = true; | |
this.onMouseDown.emitSync(this.map({ x, y })); | |
}); | |
eventHandlerNode.addEventListener('mousemove', (e) => { | |
const { clientX: x, clientY: y } = e; | |
if (this.mousepressed) { | |
const offsetx = x - this.lastmousepos.x, offsety = y - this.lastmousepos.y; | |
this.onDrag.emitSync({ x: offsetx, y: offsety }); | |
this.lastmousepos = { x, y }; | |
} | |
this.onMouseMove.emitSync(this.map({ x, y })); | |
}); | |
eventHandlerNode.addEventListener('mouseup', (e) => { | |
const { clientX: x, clientY: y } = e; | |
if (this.mousepressed) { | |
const offsetx = this.lastmousepos.x - x, offsety = this.lastmousepos.y - y; | |
if (Math.abs(offsetx) + Math.abs(offsety) < 20) { | |
this.onClick.emitSync(this.map({ x, y })); | |
} | |
this.mousepressed = false; | |
}; | |
this.onMouseUp.emitSync(this.map({ x, y })); | |
}); | |
eventHandlerNode.addEventListener('mousewheel', (e: any) => { | |
this.onZoom.emitSync({ x: e.deltaX, y: e.deltaY }); | |
e.preventDefault(); | |
}); | |
let keys = {}; | |
document.addEventListener('keydown', (e: any) => { | |
if (!keys[e.keyCode]) { | |
this.onKey.emitSync(e.keyCode); | |
this.onKeyDown.emitSync(e.keyCode); | |
keys[e.keyCode] = true; | |
} | |
}); | |
document.addEventListener('keyup', (e: any) => { | |
this.onKeyUp.emitSync(e.keyCode); | |
delete keys[e.keyCode]; | |
}); | |
setTimeout(() => { | |
this.onSetup.emitSync(eventHandlerNode); | |
}, 0); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment