diff --git a/package-lock.json b/package-lock.json index 6fe14372c..ba4fcfb58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "googleapis": "^143.0.0", "hammerjs": "^2.0.8", "jimp": "^0.22.12", + "lit": "^3.2.1", "msgpack5": "^6.0.2", "node-addon-api": "^8.1.0", "node-gyp": "^10.2.0", @@ -43,7 +44,7 @@ "@types/d3": "^7.4.3", "@types/jest": "^29.5.12", "@types/mocha": "^10.0.7", - "@types/node": "^22.5.2", + "@types/node": "^22.7.5", "@types/sinon": "^17.0.3", "@types/uuid": "^10.0.0", "@types/ws": "^8.5.11", @@ -69,7 +70,7 @@ "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "tsx": "^4.17.0", - "typescript": "^5.5.4", + "typescript": "^5.6.3", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" @@ -3655,6 +3656,21 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@npmcli/agent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", @@ -4428,9 +4444,9 @@ } }, "node_modules/@types/node": { - "version": "22.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", - "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -4542,6 +4558,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -9990,6 +10012,37 @@ "dev": true, "license": "MIT" }, + "node_modules/lit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", + "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.1.0", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-element": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-html": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/load-bmfont": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", @@ -13470,9 +13523,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index a549a3c76..033f7c891 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@types/d3": "^7.4.3", "@types/jest": "^29.5.12", "@types/mocha": "^10.0.7", - "@types/node": "^22.5.2", + "@types/node": "^22.7.5", "@types/sinon": "^17.0.3", "@types/uuid": "^10.0.0", "@types/ws": "^8.5.11", @@ -46,7 +46,7 @@ "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "tsx": "^4.17.0", - "typescript": "^5.5.4", + "typescript": "^5.6.3", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" @@ -68,6 +68,7 @@ "googleapis": "^143.0.0", "hammerjs": "^2.0.8", "jimp": "^0.22.12", + "lit": "^3.2.1", "msgpack5": "^6.0.2", "node-addon-api": "^8.1.0", "node-gyp": "^10.2.0", diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index cbd22ab31..3b28f02fc 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -8,19 +8,24 @@ import {EventBus} from "../../core/EventBus"; import {TransformHandler} from "./TransformHandler"; import {Layer} from "./layers/Layer"; import {EventsDisplay} from "./layers/EventsDisplay"; -import {RadialMenu} from "./layers/RadialMenu"; +import {RadialMenu} from "./layers/radial/RadialMenu"; +import {EmojiTable} from "./layers/radial/EmojiTable"; export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer { const transformHandler = new TransformHandler(game, eventBus, canvas) + const emojiTable = document.querySelector('emoji-table') as EmojiTable; + if (!emojiTable || !(emojiTable instanceof EmojiTable)) { + console.error('EmojiTable element not found in the DOM'); + } const layers: Layer[] = [ new TerrainLayer(game), new TerritoryLayer(game, eventBus), new NameLayer(game, game.config().theme(), transformHandler, clientID), new UILayer(eventBus, game, clientID, transformHandler), new EventsDisplay(eventBus, game, clientID), - new RadialMenu(eventBus, game, transformHandler, clientID), + new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable), ] return new GameRenderer(game, eventBus, canvas, transformHandler, layers) @@ -65,7 +70,7 @@ export class GameRenderer { this.layers.forEach(l => { if (l.shouldTransform()) { - l.render(this.context) + l.renderLayer(this.context) } }) @@ -73,7 +78,7 @@ export class GameRenderer { this.layers.forEach(l => { if (!l.shouldTransform()) { - l.render(this.context) + l.renderLayer(this.context) } }) diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index ce390f1e0..17a2dd078 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -263,7 +263,7 @@ export class EventsDisplay implements Layer { this.events[index] = event; } - render(): void { } + renderLayer(): void { } renderTable(): void { if (this.events.length === 0) { diff --git a/src/client/graphics/layers/Layer.ts b/src/client/graphics/layers/Layer.ts index 11f6e3e7e..2c7d7ac7a 100644 --- a/src/client/graphics/layers/Layer.ts +++ b/src/client/graphics/layers/Layer.ts @@ -3,6 +3,6 @@ import {TransformHandler} from "../TransformHandler" export interface Layer { init() tick() - render(context: CanvasRenderingContext2D) + renderLayer(context: CanvasRenderingContext2D) shouldTransform(): boolean } \ No newline at end of file diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index 3303ff322..b71f1874c 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -100,7 +100,7 @@ export class NameLayer implements Layer { } } - public render(mainContex: CanvasRenderingContext2D) { + public renderLayer(mainContex: CanvasRenderingContext2D) { const [upperLeft, bottomRight] = this.transformHandler.screenBoundingRect() for (const render of this.renders) { render.isVisible = this.isVisible(render, upperLeft, bottomRight) diff --git a/src/client/graphics/layers/TerrainLayer.ts b/src/client/graphics/layers/TerrainLayer.ts index 5505cce62..69cffec3d 100644 --- a/src/client/graphics/layers/TerrainLayer.ts +++ b/src/client/graphics/layers/TerrainLayer.ts @@ -41,7 +41,7 @@ export class TerrainLayer implements Layer { }) } - render(context: CanvasRenderingContext2D) { + renderLayer(context: CanvasRenderingContext2D) { context.drawImage( this.canvas, -this.game.width() / 2, diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index b2c797761..4b9dd496d 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -50,7 +50,7 @@ export class TerritoryLayer implements Layer { }) } - render(context: CanvasRenderingContext2D) { + renderLayer(context: CanvasRenderingContext2D) { this.renderTerritory() this.context.putImageData(this.imageData, 0, 0); context.drawImage( diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 34d49234c..7543c6168 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -30,7 +30,7 @@ export class UILayer implements Layer { } - render(context: CanvasRenderingContext2D) { + renderLayer(context: CanvasRenderingContext2D) { if (!this.game.inSpawnPhase()) { return } diff --git a/src/client/graphics/layers/radial/EmojiTable.ts b/src/client/graphics/layers/radial/EmojiTable.ts new file mode 100644 index 000000000..a689be1b3 --- /dev/null +++ b/src/client/graphics/layers/radial/EmojiTable.ts @@ -0,0 +1,107 @@ +import {LitElement, html, css} from 'lit'; +import {customElement, state} from 'lit/decorators.js'; + +const emojiTable: string[][] = [ + ["😀", "😱", "🤩", "đŸŽ¯", "đŸĨē"], + ["đŸĒĻ", "👏", "đŸĨ‰", "đŸĨˆ", "đŸĨ‡"], + ["🤙", "đŸĨ°", "😇", "😊", "đŸ”Ĩ"], + ["đŸ’Ē", "đŸĨŗ", "💀", "😭", "đŸ¤Ļâ€â™‚ī¸"], + ["😎", "👎", "👍", "đŸĨą", "💔"], + ["â¤ī¸", "💰", "🤝", "đŸ›Ąī¸", "đŸ’Ĩ"], + ["🆘", "đŸ•Šī¸", "âžĄī¸", "âŦ…ī¸", "â†™ī¸"], + ["â†–ī¸", "â†—ī¸", "âŦ†ī¸", "â†˜ī¸", "âŦ‡ī¸"] +]; + +@customElement('emoji-table') +export class EmojiTable extends LitElement { + static styles = css` + :host { + display: block; + } + .emoji-table { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 9999; + background-color: #1E1E1E; + padding: 15px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: center; + max-width: 95vw; + max-height: 95vh; + overflow-y: auto; + } + .emoji-row { + display: flex; + justify-content: center; + width: 100%; + } + .emoji-button { + font-size: 60px; + width: 80px; + height: 80px; + border: 1px solid #333; + background-color: #2C2C2C; + color: white; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + justify-content: center; + align-items: center; + margin: 8px; + } + .emoji-button:hover { + background-color: #3A3A3A; + transform: scale(1.1); + } + .emoji-button:active { + background-color: #4A4A4A; + transform: scale(0.95); + } + .hidden { + display: none !important; + } + `; + + @state() + private _hidden = true; + + public onEmojiClicked: (emoji: string) => void = () => { } + + render() { + return html` +
+ ${emojiTable.map(row => html` +
+ ${row.map(emoji => html` + + `)} +
+ `)} +
+ `; + } + + + hideTable() { + this._hidden = true; + this.requestUpdate(); + + } + + showTable() { + this._hidden = false; + this.requestUpdate(); + } + + get isVisible() { + return !this._hidden; + } +} \ No newline at end of file diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/radial/RadialMenu.ts similarity index 83% rename from src/client/graphics/layers/RadialMenu.ts rename to src/client/graphics/layers/radial/RadialMenu.ts index fdbfd12a9..48c7ccd25 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/radial/RadialMenu.ts @@ -1,21 +1,21 @@ -import {EventBus} from "../../../core/EventBus"; -import {AllPlayers, Cell, Game, Player} from "../../../core/game/Game"; -import {ClientID} from "../../../core/Schemas"; -import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../core/Util"; -import {ContextMenuEvent, MouseUpEvent} from "../../InputHandler"; -import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../Transport"; -import {TransformHandler} from "../TransformHandler"; -import {Layer} from "./Layer"; +import {EventBus} from "../../../../core/EventBus"; +import {AllPlayers, Cell, Game, Player} from "../../../../core/game/Game"; +import {ClientID} from "../../../../core/Schemas"; +import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../../core/Util"; +import {ContextMenuEvent, MouseUpEvent} from "../../../InputHandler"; +import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../../Transport"; +import {TransformHandler} from "../../TransformHandler"; +import {Layer} from "../Layer"; import * as d3 from 'd3'; -import traitorIcon from '../../../../resources/images/TraitorIconWhite.png'; -import allianceIcon from '../../../../resources/images/AllianceIconWhite.png'; -import boatIcon from '../../../../resources/images/BoatIconWhite.png'; -import swordIcon from '../../../../resources/images/SwordIconWhite.png'; -import targetIcon from '../../../../resources/images/TargetIconWhite.png'; -import emojiIcon from '../../../../resources/images/EmojiIconWhite.png'; -import disabledIcon from '../../../../resources/images/DisabledIcon.png'; -import donateIcon from '../../../../resources/images/DonateIconWhite.png'; -import {MessageType} from "./EventsDisplay"; +import traitorIcon from '../../../../../resources/images/TraitorIconWhite.png'; +import allianceIcon from '../../../../../resources/images/AllianceIconWhite.png'; +import boatIcon from '../../../../../resources/images/BoatIconWhite.png'; +import swordIcon from '../../../../../resources/images/SwordIconWhite.png'; +import targetIcon from '../../../../../resources/images/TargetIconWhite.png'; +import emojiIcon from '../../../../../resources/images/EmojiIconWhite.png'; +import disabledIcon from '../../../../../resources/images/DisabledIcon.png'; +import donateIcon from '../../../../../resources/images/DonateIconWhite.png'; +import {EmojiTable} from "./EmojiTable"; enum Slot { @@ -45,12 +45,12 @@ export class RadialMenu implements Layer { private isCenterButtonEnabled = false - constructor( private eventBus: EventBus, private game: Game, private transformHandler: TransformHandler, private clientID: ClientID, + private emojiTable: EmojiTable ) { } init() { @@ -59,49 +59,6 @@ export class RadialMenu implements Layer { this.createMenuElement(); } - private hideEmojiTable(): void { - const emojiTable: HTMLTableElement | null = document.getElementById('uniqueEmojiTable') as HTMLTableElement | null; - - if (emojiTable instanceof HTMLTableElement) { - if (!emojiTable.classList.contains('hidden')) { - emojiTable.classList.add('hidden'); - } - } else { - console.error('Emoji table not found'); - } - } - - private showEmojiTable(recipient: Player | typeof AllPlayers): void { - const emojiTable: HTMLTableElement | null = document.getElementById('uniqueEmojiTable') as HTMLTableElement | null; - - if (emojiTable instanceof HTMLTableElement) { - emojiTable.classList.remove('hidden'); - } else { - console.error('Emoji table not found'); - } - this.setupEmojiButtons(recipient) - } - - private setupEmojiButtons(recipient: Player | typeof AllPlayers) { - let emojiTable = document.getElementById('uniqueEmojiTable'); - - if (emojiTable) { - // Remove existing listeners - emojiTable.replaceWith(emojiTable.cloneNode(true)); - emojiTable = document.getElementById('uniqueEmojiTable'); - emojiTable.addEventListener('click', (event) => { - const emojiElement = event.target as HTMLElement; - if (emojiElement.classList.contains('emoji-button')) { - const emoji = emojiElement.textContent; - this.hideEmojiTable() - this.eventBus.emit(new SendEmojiIntentEvent(recipient, emoji)) - } - }); - } else { - console.error('Emoji table not found'); - } - } - private createMenuElement() { this.menuElement = d3.select(document.body) .append('div') @@ -227,7 +184,7 @@ export class RadialMenu implements Layer { // Update logic if needed } - render(context: CanvasRenderingContext2D) { + renderLayer(context: CanvasRenderingContext2D) { // No need to render anything on the canvas } @@ -272,7 +229,11 @@ export class RadialMenu implements Layer { const target = tile.owner() == myPlayer ? AllPlayers : (tile.owner() as Player) if (myPlayer.canSendEmoji(target)) { this.activateMenuElement(Slot.Emoji, "#ebe250", emojiIcon, () => { - this.showEmojiTable(target) + this.emojiTable.onEmojiClicked = (emoji: string) => { + this.emojiTable.hideTable() + this.eventBus.emit(new SendEmojiIntentEvent(target, emoji)) + } + this.emojiTable.showTable() }) } } @@ -387,7 +348,7 @@ export class RadialMenu implements Layer { private onPointerUp(event: MouseUpEvent) { this.hideRadialMenu() - this.hideEmojiTable() + this.emojiTable.hideTable() } private showRadialMenu(x: number, y: number) { @@ -467,4 +428,4 @@ export class RadialMenu implements Layer { .attr('fill', enabled ? 'white' : '#cccccc'); }, 25); } -} +} \ No newline at end of file diff --git a/src/client/index.html b/src/client/index.html index 443223fec..1b79f0074 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -79,64 +79,7 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +