From 31cf1bf053fc85a6ca39c78ffc6a259e18816310 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 3 Apr 2022 17:02:04 +0300 Subject: [PATCH] placer request --- src/client/canvas.ts | 65 +++++++++++++++++++++++++++++++++--- src/client/index.ts | 22 ++++++++++++ src/client/scss/index.scss | 29 ++++++++++++++++ src/server/index.ts | 11 ++++++ src/server/object/canvas.ts | 8 ++--- src/server/object/history.ts | 8 +++++ 6 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/client/canvas.ts b/src/client/canvas.ts index 49ce078..933d19a 100644 --- a/src/client/canvas.ts +++ b/src/client/canvas.ts @@ -1,6 +1,6 @@ import { convertHex } from '../common/convert'; import { clamp, debounce } from '../common/helper'; -import { Placement } from '../common/types/canvas'; +import { CanvasRecord, Placement } from '../common/types/canvas'; import { IcyNetUser } from '../server/types/user'; import { Picker } from './picker'; import { $ } from './utils/dom'; @@ -9,7 +9,8 @@ export class ViewCanvas { public picker = new Picker(); private _user?: IcyNetUser; - private _fn?: (placement: Placement) => void; + private _placeFn?: (placement: Placement) => void; + private _getPlacerFn?: (x: number, y: number) => Promise; private _canvas = $('') as HTMLCanvasElement; private _ctx = this._canvas.getContext('2d'); private _wrapper = $('
'); @@ -41,6 +42,8 @@ export class ViewCanvas { private _dragging = false; private _pinching = false; private _previousPinchLength = 0; + private _placerTag: HTMLElement | null = null; + private _placerRequestTime: number = 0; constructor() {} @@ -61,6 +64,8 @@ export class ViewCanvas { } public moveCursor(): void { + this._resetPlacerTag(); + // Zoom multiplier // Apparent size of the canvas after scaling it const realSize = this._zoom * this._size; @@ -93,6 +98,10 @@ export class ViewCanvas { }) ${this._zoom.toFixed(2)}x`; this._updateURL(); + + if (this._zoom > 40) { + this._getPlacerAt(this._relcursorx, this._relcursory); + } } public initialize(): void { @@ -218,8 +227,8 @@ export class ViewCanvas { }); this.picker.registerOnPlace((color) => { - if (this._fn) { - this._fn({ + if (this._placeFn) { + this._placeFn({ x: this._relcursorx, y: this._relcursory, c: color, @@ -302,7 +311,13 @@ export class ViewCanvas { } public registerOnPlace(fn: (placement: Placement) => void): void { - this._fn = fn; + this._placeFn = fn; + } + + public registerGetPlacer( + fn: (x: number, y: number) => Promise, + ): void { + this._getPlacerFn = fn; } public setUser(user: IcyNetUser): void { @@ -324,6 +339,46 @@ export class ViewCanvas { ); }, 500); + private _getPlacerAtWaiter = debounce( + (x: number, y: number, order: number) => { + console.log(this._placerRequestTime); + if (this._placerRequestTime === order) { + this._getPlacerFn(x, y).then((placer) => { + if (placer && this._placerRequestTime === order) { + this._showPlacerTag(placer); + } + }); + } + }, + 1000, + ); + + private _getPlacerAt(x: number, y: number) { + if (!this._getPlacerFn) { + return; + } + + const stamp = Date.now(); + this._placerRequestTime = +stamp; + this._getPlacerAtWaiter(x, y, stamp); + } + + private _showPlacerTag(placer: CanvasRecord): void { + this._placerTag = $('
'); + this._placerTag.innerText = placer.user; + this._placerTag.style.setProperty('--base-size', `${this._zoom / 1.5}px`); + this._placerTag.style.setProperty('--base-scale', `${this._zoom / 100}`); + this._cursor.append(this._placerTag); + } + + private _resetPlacerTag(): void { + this._placerRequestTime = 0; + if (this._placerTag) { + this._cursor.removeChild(this._placerTag); + this._placerTag = null; + } + } + private _resetPositionOrCenter(): void { const search = window.location.search.substring(1); if (!search?.length) { diff --git a/src/client/index.ts b/src/client/index.ts index cffecdc..dd193b3 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -4,6 +4,9 @@ import { ViewCanvas } from './canvas'; const socket = SocketIO(); const canvas = new ViewCanvas(); +let placerRequestResolve: any; +let placerRequest: any; + canvas.initialize(); socket.on('connect', () => { @@ -32,6 +35,25 @@ socket.on('colorack', ({ x, y, c }: Placement) => { canvas.setPixel(x, y, c); }); +socket.on('placerat', (response: CanvasRecord) => { + if (placerRequest) { + placerRequestResolve(response); + } +}); + canvas.registerOnPlace((placement) => { socket.emit('color', placement); }); + +canvas.registerGetPlacer((x: number, y: number) => { + if (placerRequest) { + placerRequestResolve(null); + } + + placerRequest = new Promise((resolve) => { + placerRequestResolve = resolve; + }); + + socket.emit('getplacer', { x, y, reqt: Date.now() }); + return placerRequest; +}); diff --git a/src/client/scss/index.scss b/src/client/scss/index.scss index 3b012a6..7f6d29e 100644 --- a/src/client/scss/index.scss +++ b/src/client/scss/index.scss @@ -50,12 +50,41 @@ body { &__cursor { position: absolute; + display: flex; + align-items: flex-start; + justify-content: center; z-index: 1002; width: 1px; height: 1px; background: url('data:image/png;base64,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dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDo1MGFkNWM1Mi0xMWZkLTQ2ZDktOWQzMy0wODBhZDBlZmRkOGIiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjQxMTYxOWUtYTRhZC00OTQzLWE5MDEtMTMyODk4MjFkYjMyIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Mzc0YzA0MGItMjNlMy00ZDllLWI0ZWQtYWE5ZGJkMzVlZTBkIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjQ4ODkwNjQ1MTM0NTU2IgogICBHSU1QOlZlcnNpb249IjIuMTAuMzAiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDBiNzUzOTItOTU0NS00YzY3LTlhN2MtNGJjMmU5ZGIxM2E4IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMi0wNC0wMlQxMjoxMDo0NSswMzowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7Mc7bvAAAABmJLR0QAVACzAPRJGKQ4AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5gQCCQot46GzBAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAFdSURBVHja7d2xjYMwAEBROKVjC1p6CobwLB7FszAERXqXsAV1rroW54RQgvzeAAjZX0bYiWjHcXw1X+z5fLbNjYUQvnp8fxqqJgABIAAEgAAQALVpS/sA0zRdegMppbbmCYgxXrpPsG2bFQABIAAEgAAQAALgz2Pfd+/xH3R2/Er7COu6WgEQAAJAAAgAASAABIAAEIAADIEAEAACQADUpx2G4fA8OefsvP/GSvNrBfAIQAAIAAEgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADApdoQwuF35eZ59t3AGxvH0XcDEQACQAAIAAEgAN7eB+j7/vACKSX7BB8UYzycv2VZrAAIAAEgAASAABAAAkAACEAAhkAACAABIADq8zh7gdJ5dEntvyc4O35WAASAABAAAkAACIB/KP4voGRd10tvMOd8632CYRgufc/vus4KgAAQAAJAAAgAAfCuX64yPUr2kCHkAAAAAElFTkSuQmCC'); background-size: contain; background-repeat: no-repeat; + + &-placer { + position: relative; + min-width: 100px; + background: ivory; + display: block; + margin: 0 auto; + border-radius: 20px; + text-align: center; + padding: 1rem; + transform-origin: top center; + transform: translateY(calc(var(--base-size) * -1)) scale(var(--base-scale)); + + &::after { + content: ""; + height: 16px; + width: 16px; + background: ivory; + margin: 0 auto; + transform: rotate(45deg); + position: absolute; + left: calc(50% - 8px); + display: block; + bottom: -8px; + } + } } } diff --git a/src/server/index.ts b/src/server/index.ts index d29eddd..cd1b817 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -157,6 +157,17 @@ io.on('connection', (socket) => { socket.emit('colorack', { c, x, y, t }); }); + socket.on('getplacer', ({ x, y, reqt }) => { + canvas.history + .getPlacerAt(x, y) + .then((placer) => { + if (placer) { + socket.emit('placerat', { ...placer, reqt }); + } + }) + .catch((e) => console.error(e)); + }); + socket.on('disconnect', () => { connections.splice(connections.indexOf(socket), 1); }); diff --git a/src/server/object/canvas.ts b/src/server/object/canvas.ts index 986d84d..b7fae32 100644 --- a/src/server/object/canvas.ts +++ b/src/server/object/canvas.ts @@ -6,16 +6,16 @@ import { Imager } from './imager'; export class Canvas { private _fn?: (change: CanvasRecord) => void; private _canvas!: Uint32Array; - - private _history = new History(); private _imager = new Imager(this.size); + public history = new History(); + constructor(public size = 1000) {} public async initialize(): Promise { this._imager.registerGetCanvas(() => this._canvas); - await this._history.initialize(); + await this.history.initialize(); const image = await this._imager.load(); if (!image) { @@ -51,7 +51,7 @@ export class Canvas { ts: Date.now(), }; - this._history.insert(record); + this.history.insert(record); this._canvas[index] = color; if (this._fn) { diff --git a/src/server/object/history.ts b/src/server/object/history.ts index 73b4d8e..cfae563 100644 --- a/src/server/object/history.ts +++ b/src/server/object/history.ts @@ -35,4 +35,12 @@ export class History { user, ); } + + async getPlacerAt(x: number, y: number): Promise { + return this.db.get( + `SELECT * FROM Placement WHERE x = ? AND y = ? ORDER BY ts DESC`, + x, + y, + ); + } }