mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-28 10:00:33 +00:00
created Layers dir
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import {TransformHandler} from "../TransformHandler"
|
||||
|
||||
export interface Layer {
|
||||
init()
|
||||
tick()
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler)
|
||||
shouldTransform(): boolean
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import {Cell, Game, Player, PlayerType} from "../../../core/Game"
|
||||
import {PseudoRandom} from "../../../core/PseudoRandom"
|
||||
import {calculateBoundingBox} from "../../../core/Util"
|
||||
import {Theme} from "../../../core/configuration/Config"
|
||||
import {Layer} from "./Layer"
|
||||
import {placeName} from "../NameBoxCalculator"
|
||||
import {TransformHandler} from "../TransformHandler"
|
||||
import {renderTroops} from "../Utils"
|
||||
|
||||
class RenderInfo {
|
||||
public isVisible = true
|
||||
constructor(
|
||||
public player: Player,
|
||||
public lastRenderCalc: number,
|
||||
public lastBoundingCalculated: number,
|
||||
public boundingBox: {min: Cell, max: Cell},
|
||||
public location: Cell,
|
||||
public fontSize: number
|
||||
) { }
|
||||
}
|
||||
|
||||
export class NameLayer implements Layer {
|
||||
|
||||
private lastChecked = 0
|
||||
private refreshRate = 1000
|
||||
|
||||
private rand = new PseudoRandom(10)
|
||||
private renderInfo: Map<Player, RenderInfo> = new Map()
|
||||
private context: CanvasRenderingContext2D
|
||||
private canvas: HTMLCanvasElement
|
||||
private renders: RenderInfo[] = []
|
||||
private seenPlayers: Set<Player> = new Set()
|
||||
|
||||
constructor(private game: Game, private theme: Theme) {
|
||||
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.context = this.canvas.getContext('2d');
|
||||
|
||||
this.canvas.style.position = 'fixed';
|
||||
this.canvas.style.left = '0';
|
||||
this.canvas.style.top = '0';
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
}
|
||||
|
||||
// TODO: remove tick, move this to render
|
||||
public tick() {
|
||||
const now = Date.now()
|
||||
if (now - this.lastChecked > this.refreshRate) {
|
||||
this.lastChecked = now
|
||||
this.renders = this.renders.filter(r => r.player.isAlive())
|
||||
for (const player of this.game.players()) {
|
||||
if (player.isAlive()) {
|
||||
if (!this.seenPlayers.has(player)) {
|
||||
this.seenPlayers.add(player)
|
||||
this.renders.push(new RenderInfo(player, 0, 0, null, null, 0))
|
||||
}
|
||||
} else {
|
||||
this.seenPlayers.delete(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const render of this.renders) {
|
||||
const now = Date.now()
|
||||
if (now - render.lastBoundingCalculated > this.refreshRate) {
|
||||
render.boundingBox = calculateBoundingBox(render.player.borderTiles());
|
||||
render.lastBoundingCalculated = now
|
||||
}
|
||||
if (render.isVisible && now - render.lastRenderCalc > this.refreshRate) {
|
||||
this.calculateRenderInfo(render)
|
||||
render.lastRenderCalc = now + this.rand.nextInt(-50, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render(mainContex: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
const [upperLeft, bottomRight] = transformHandler.screenBoundingRect()
|
||||
for (const render of this.renders) {
|
||||
render.isVisible = this.isVisible(render, upperLeft, bottomRight)
|
||||
if (render.player.isAlive() && render.isVisible && render.fontSize * transformHandler.scale > 10) {
|
||||
this.renderPlayerInfo(render, mainContex, transformHandler.scale, upperLeft, bottomRight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(render: RenderInfo, min: Cell, max: Cell): boolean {
|
||||
const ratio = (max.x - min.x) / Math.max(20, (render.boundingBox.max.x - render.boundingBox.min.x))
|
||||
if (render.player.type() == PlayerType.Bot) {
|
||||
if (ratio > 35) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (ratio > 35) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (render.boundingBox.max.x < min.x || render.boundingBox.max.y < min.y || render.boundingBox.min.x > max.x || render.boundingBox.min.y > max.y) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
calculateRenderInfo(render: RenderInfo) {
|
||||
if (render.player.numTilesOwned() == 0) {
|
||||
render.fontSize = 0
|
||||
return
|
||||
}
|
||||
render.lastRenderCalc = Date.now() + this.rand.nextInt(0, 100)
|
||||
const [cell, size] = placeName(this.game, render.player)
|
||||
render.location = cell
|
||||
render.fontSize = Math.max(1, Math.floor(size))
|
||||
}
|
||||
|
||||
renderPlayerInfo(render: RenderInfo, context: CanvasRenderingContext2D, scale: number, uppperLeft: Cell, bottomRight: Cell) {
|
||||
const nameCenterX = Math.floor(render.location.x - this.game.width() / 2)
|
||||
const nameCenterY = Math.floor(render.location.y - this.game.height() / 2)
|
||||
|
||||
context.textRendering = "optimizeSpeed";
|
||||
|
||||
context.font = `${render.fontSize}px ${this.theme.font()}`;
|
||||
context.fillStyle = this.theme.playerInfoColor(render.player.id()).toHex();
|
||||
context.textAlign = 'center';
|
||||
context.textBaseline = 'middle';
|
||||
|
||||
context.fillText(render.player.name(), nameCenterX, nameCenterY - render.fontSize / 2);
|
||||
context.font = `bold ${render.fontSize}px ${this.theme.font()}`;
|
||||
|
||||
context.fillText(renderTroops(render.player.troops()), nameCenterX, nameCenterY + render.fontSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {inherits} from "util"
|
||||
import {Game} from "../../../core/Game";
|
||||
import {throws} from "assert";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
|
||||
export class TerrainLayer implements Layer {
|
||||
private canvas: HTMLCanvasElement
|
||||
private context: CanvasRenderingContext2D
|
||||
private imageData: ImageData
|
||||
|
||||
|
||||
constructor(private game: Game) { }
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.context = this.canvas.getContext("2d")
|
||||
|
||||
this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height())
|
||||
this.initImageData()
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
this.context.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
|
||||
initImageData() {
|
||||
const theme = this.game.config().theme()
|
||||
this.game.forEachTile((tile) => {
|
||||
let terrainColor = theme.terrainColor(tile)
|
||||
const index = (tile.cell().y * this.game.width()) + tile.cell().x
|
||||
const offset = index * 4
|
||||
this.imageData.data[offset] = terrainColor.rgba.r;
|
||||
this.imageData.data[offset + 1] = terrainColor.rgba.g;
|
||||
this.imageData.data[offset + 2] = terrainColor.rgba.b;
|
||||
this.imageData.data[offset + 3] = terrainColor.rgba.a * 255 | 0
|
||||
})
|
||||
}
|
||||
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
context.drawImage(
|
||||
this.canvas,
|
||||
-this.game.width() / 2,
|
||||
-this.game.height() / 2,
|
||||
this.game.width(),
|
||||
this.game.height()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import {PriorityQueue} from "@datastructures-js/priority-queue";
|
||||
import {Boat, BoatEvent, Cell, Game, Player, Tile, TileEvent} from "../../../core/Game";
|
||||
import {PseudoRandom} from "../../../core/PseudoRandom";
|
||||
import {Colord} from "colord";
|
||||
import {bfs, dist} from "../../../core/Util";
|
||||
import {Theme} from "../../../core/configuration/Config";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
import {EventBus} from "../../../core/EventBus";
|
||||
|
||||
export class TerritoryLayer implements Layer {
|
||||
private canvas: HTMLCanvasElement
|
||||
private context: CanvasRenderingContext2D
|
||||
private imageData: ImageData
|
||||
|
||||
private tileToRenderQueue: PriorityQueue<{tileEvent: TileEvent, lastUpdate: number}> = new PriorityQueue((a, b) => {return a.lastUpdate - b.lastUpdate})
|
||||
private random = new PseudoRandom(123)
|
||||
private theme: Theme = null
|
||||
|
||||
private boatToTrail = new Map<Boat, Set<Tile>>()
|
||||
|
||||
|
||||
constructor(private game: Game, eventBus: EventBus) {
|
||||
this.theme = game.config().theme()
|
||||
eventBus.on(TileEvent, e => this.tileUpdate(e))
|
||||
eventBus.on(BoatEvent, e => this.boatEvent(e))
|
||||
}
|
||||
shouldTransform(): boolean {
|
||||
return true
|
||||
}
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.context = this.canvas.getContext("2d")
|
||||
|
||||
this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height())
|
||||
this.initImageData()
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
this.context.putImageData(this.imageData, 0, 0);
|
||||
}
|
||||
|
||||
initImageData() {
|
||||
this.game.forEachTile((tile) => {
|
||||
const index = (tile.cell().y * this.game.width()) + tile.cell().x
|
||||
const offset = index * 4
|
||||
this.imageData.data[offset + 3] = 0
|
||||
})
|
||||
}
|
||||
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
this.renderTerritory()
|
||||
this.context.putImageData(this.imageData, 0, 0);
|
||||
context.drawImage(
|
||||
this.canvas,
|
||||
-this.game.width() / 2,
|
||||
-this.game.height() / 2,
|
||||
this.game.width(),
|
||||
this.game.height()
|
||||
)
|
||||
}
|
||||
|
||||
boatEvent(event: BoatEvent) {
|
||||
if (!this.boatToTrail.has(event.boat)) {
|
||||
this.boatToTrail.set(event.boat, new Set<Tile>())
|
||||
}
|
||||
const trail = this.boatToTrail.get(event.boat)
|
||||
trail.add(event.oldTile)
|
||||
bfs(event.oldTile, dist(event.oldTile, 3)).forEach(t => {
|
||||
this.paintTerritory(t)
|
||||
})
|
||||
if (event.boat.isActive()) {
|
||||
bfs(event.boat.tile(), dist(event.boat.tile(), 4)).forEach(
|
||||
t => {
|
||||
if (trail.has(t)) {
|
||||
this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 150)
|
||||
}
|
||||
}
|
||||
)
|
||||
bfs(event.boat.tile(), dist(event.boat.tile(), 2)).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().info()), 255))
|
||||
bfs(event.boat.tile(), dist(event.boat.tile(), 1)).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 180))
|
||||
} else {
|
||||
trail.forEach(t => this.paintTerritory(t))
|
||||
this.boatToTrail.delete(event.boat)
|
||||
}
|
||||
}
|
||||
|
||||
renderTerritory() {
|
||||
let numToRender = Math.floor(this.tileToRenderQueue.size() / 10)
|
||||
if (numToRender == 0) {
|
||||
numToRender = this.tileToRenderQueue.size()
|
||||
}
|
||||
|
||||
while (numToRender > 0) {
|
||||
numToRender--
|
||||
const event = this.tileToRenderQueue.pop().tileEvent
|
||||
this.paintTerritory(event.tile)
|
||||
event.tile.neighbors().forEach(t => this.paintTerritory(t))
|
||||
}
|
||||
}
|
||||
|
||||
paintTerritory(tile: Tile) {
|
||||
if (!tile.hasOwner()) {
|
||||
this.clearCell(tile.cell())
|
||||
return
|
||||
}
|
||||
const owner = tile.owner() as Player
|
||||
if (tile.isBorder()) {
|
||||
this.paintCell(
|
||||
tile.cell(),
|
||||
this.theme.borderColor(owner.info()),
|
||||
255
|
||||
)
|
||||
} else {
|
||||
this.paintCell(
|
||||
tile.cell(),
|
||||
this.theme.territoryColor(owner.info()),
|
||||
110
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
paintCell(cell: Cell, color: Colord, alpha: number) {
|
||||
const index = (cell.y * this.game.width()) + cell.x
|
||||
const offset = index * 4
|
||||
this.imageData.data[offset] = color.rgba.r;
|
||||
this.imageData.data[offset + 1] = color.rgba.g;
|
||||
this.imageData.data[offset + 2] = color.rgba.b;
|
||||
this.imageData.data[offset + 3] = alpha
|
||||
}
|
||||
|
||||
clearCell(cell: Cell) {
|
||||
const index = (cell.y * this.game.width()) + cell.x;
|
||||
const offset = index * 4;
|
||||
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
|
||||
}
|
||||
|
||||
tileUpdate(event: TileEvent) {
|
||||
this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5)})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
import {Theme} from "../../../core/configuration/Config";
|
||||
import {EventBus} from "../../../core/EventBus";
|
||||
import {WinEvent} from "../../../core/execution/WinCheckExecution";
|
||||
import {Game, Player} from "../../../core/Game";
|
||||
import {ClientID} from "../../../core/Schemas";
|
||||
import {renderTroops} from "../Utils";
|
||||
import winModalHtml from '../WinModal.html';
|
||||
import {RightClickEvent} from "../../InputHandler";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
|
||||
|
||||
interface MenuOption {
|
||||
label: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export class UILayer implements Layer {
|
||||
private exitButton: HTMLButtonElement;
|
||||
private winModal: HTMLElement | null = null;
|
||||
|
||||
private customMenu = document.getElementById('customMenu');
|
||||
|
||||
|
||||
constructor(private eventBus: EventBus, private game: Game, private theme: Theme, private clientID: ClientID) {
|
||||
|
||||
}
|
||||
render(context: CanvasRenderingContext2D, transformHandler: TransformHandler) {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
return
|
||||
}
|
||||
|
||||
const barHeight = 15;
|
||||
const barBackgroundWidth = transformHandler.width();
|
||||
|
||||
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns()
|
||||
|
||||
// Draw bar background
|
||||
context.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
context.fillRect(0, 0, barBackgroundWidth, barHeight);
|
||||
|
||||
context.fillStyle = 'rgba(0, 128, 255, 0.7)';
|
||||
context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight);
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
tick() {
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createExitButton()
|
||||
this.createWinModal()
|
||||
this.initRightClickMenu()
|
||||
this.eventBus.on(WinEvent, (e) => this.onWinEvent(e))
|
||||
this.eventBus.on(RightClickEvent, (e) => this.onRightClick(e))
|
||||
}
|
||||
|
||||
initRightClickMenu() {
|
||||
if (!this.customMenu) {
|
||||
console.error('Custom menu not found');
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
this.customMenu!.style.display = 'none';
|
||||
});
|
||||
|
||||
const menuItems = this.customMenu.querySelectorAll('li');
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
alert(`You clicked: ${item.textContent}`);
|
||||
this.customMenu!.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createWinModal() {
|
||||
console.log("Creating win modal");
|
||||
this.winModal = document.createElement('div');
|
||||
this.winModal.style.cssText = `
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border: 2px solid black;
|
||||
border-radius: 10px;
|
||||
z-index: 2000;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.textContent = 'Game Over';
|
||||
title.id = 'winTitle';
|
||||
title.style.marginTop = '0';
|
||||
|
||||
const message = document.createElement('p');
|
||||
message.id = 'winMessage';
|
||||
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'space-between';
|
||||
buttonContainer.style.marginTop = '20px';
|
||||
|
||||
const exitButton = document.createElement('button');
|
||||
exitButton.textContent = 'Exit Game';
|
||||
exitButton.onclick = () => this.exitGame();
|
||||
this.styleButton(exitButton);
|
||||
|
||||
const continueButton = document.createElement('button');
|
||||
continueButton.textContent = 'Keep Playing';
|
||||
continueButton.onclick = () => this.closeWinModal();
|
||||
this.styleButton(continueButton);
|
||||
|
||||
buttonContainer.appendChild(exitButton);
|
||||
buttonContainer.appendChild(continueButton);
|
||||
|
||||
content.appendChild(title);
|
||||
content.appendChild(message);
|
||||
content.appendChild(buttonContainer);
|
||||
|
||||
this.winModal.appendChild(content);
|
||||
document.body.appendChild(this.winModal);
|
||||
|
||||
console.log("Win modal appended to body");
|
||||
}
|
||||
|
||||
styleButton(button: HTMLButtonElement) {
|
||||
button.style.cssText = `
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #4A90E2;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
`;
|
||||
button.onmouseover = () => button.style.backgroundColor = '#3A7BCE';
|
||||
button.onmouseout = () => button.style.backgroundColor = '#4A90E2';
|
||||
}
|
||||
|
||||
createExitButton() {
|
||||
this.exitButton = document.createElement('button');
|
||||
this.exitButton.innerHTML = '✕'; // HTML entity for "×" (multiplication sign)
|
||||
this.exitButton.style.position = 'fixed';
|
||||
this.exitButton.style.top = '20px';
|
||||
this.exitButton.style.right = '20px';
|
||||
this.exitButton.style.zIndex = '1000';
|
||||
this.exitButton.style.width = '40px';
|
||||
this.exitButton.style.height = '40px';
|
||||
this.exitButton.style.fontSize = '20px';
|
||||
this.exitButton.style.fontWeight = 'bold';
|
||||
this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.4)'; // More translucent red
|
||||
this.exitButton.style.color = 'white';
|
||||
this.exitButton.style.border = 'none';
|
||||
this.exitButton.style.borderRadius = '50%';
|
||||
this.exitButton.style.cursor = 'pointer';
|
||||
this.exitButton.style.display = 'flex';
|
||||
this.exitButton.style.justifyContent = 'center';
|
||||
this.exitButton.style.alignItems = 'center';
|
||||
this.exitButton.style.transition = 'background-color 0.3s';
|
||||
|
||||
this.exitButton.addEventListener('mouseover', () => {
|
||||
this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; // Less translucent on hover
|
||||
});
|
||||
|
||||
this.exitButton.addEventListener('mouseout', () => {
|
||||
this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // Back to more translucent
|
||||
});
|
||||
|
||||
this.exitButton.addEventListener('click', () => this.onExitButtonClick());
|
||||
document.body.appendChild(this.exitButton);
|
||||
}
|
||||
|
||||
|
||||
onWinEvent(event: WinEvent) {
|
||||
console.log(`${event.winner.name()} won the game!!}`)
|
||||
this.showWinModal(event.winner)
|
||||
}
|
||||
|
||||
showWinModal(winner: Player) {
|
||||
if (this.winModal) {
|
||||
const message = this.winModal.querySelector('#winMessage');
|
||||
if (message) {
|
||||
message.textContent = `${winner.name()} won the game!`;
|
||||
}
|
||||
const title = this.winModal.querySelector('#winTitle')
|
||||
if (winner.clientID() == this.clientID) {
|
||||
title.textContent = 'You Won!!!'
|
||||
} else {
|
||||
title.textContent = 'You Lost!!!'
|
||||
}
|
||||
this.winModal.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
onExitButtonClick() {
|
||||
console.log('Button clicked!');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
closeWinModal() {
|
||||
if (this.winModal) {
|
||||
this.winModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
exitGame() {
|
||||
this.closeWinModal();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private onRightClick(e: RightClickEvent) {
|
||||
this.customMenu!.style.display = 'block';
|
||||
this.customMenu!.style.left = `${e.x}px`;
|
||||
this.customMenu!.style.top = `${e.y}px`;
|
||||
}
|
||||
|
||||
private populateMenu(options: MenuOption[]) {
|
||||
if (!this.customMenu) return;
|
||||
|
||||
// Clear existing menu items
|
||||
this.customMenu.innerHTML = '';
|
||||
|
||||
// Create new menu items
|
||||
const ul = document.createElement('ul');
|
||||
options.forEach(option => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = option.label;
|
||||
li.onclick = () => {
|
||||
option.action();
|
||||
this.hideMenu();
|
||||
};
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
this.customMenu.appendChild(ul);
|
||||
}
|
||||
|
||||
private hideMenu() {
|
||||
if (this.customMenu) {
|
||||
this.customMenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user