import { storeHex, to1D } from '../../common/convert'; import { CanvasRecord } from '../../common/types/canvas'; import { History } from './history'; import { Imager } from './imager'; import { config } from '../config'; export class Canvas { private _fn?: (change: CanvasRecord) => void; private _canvas!: Uint32Array; private _imager = new Imager( this.size, config.persistence?.filename, config.persistence?.debounce, config.persistence?.forcedInterval, ); public history = new History(config.persistence?.placeStorage); constructor(public size = 1000) {} public async initialize(): Promise { this._imager.registerGetCanvas(() => this._canvas); await this.history.initialize(); const image = await this._imager.load(); if (!image) { this._canvas = new Uint32Array(this.size * this.size).fill(0xffffff); return; } this._canvas = image; } public setPixelAt( pixel: string | number, x: number, y: number, user: string, ) { if (x > this.size || y > this.size || x < 0 || y < 0) { return; } const color = typeof pixel === 'string' ? storeHex(pixel) : pixel; const index = to1D(x, y, this.size); // prevent duplicate writes if (this._canvas[index] === color) { return; } const record = { user, color, x, y, ts: Date.now(), }; this.history.insert(record); this._canvas[index] = color; if (this._fn) { this._fn(record); } this._imager.save(); } public getFullPlane(): Uint32Array { return this._canvas; } public registerChangeHandler(handler: (change: CanvasRecord) => void) { this._fn = handler; } }