Remove PixiJS dependencies and references from the project, transitioning to a plain DOM approach for UI rendering. Update documentation to reflect these changes and enhance the layout and structure of the game screen for improved user experience.
This commit is contained in:
Generated
-104
@@ -7,9 +7,6 @@
|
||||
"": {
|
||||
"name": "hschool-client",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"pixi.js": "^8.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"typescript": "~5.9.3",
|
||||
@@ -37,12 +34,6 @@
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/colord": {
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz",
|
||||
"integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
|
||||
@@ -313,12 +304,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/earcut": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz",
|
||||
"integrity": "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
@@ -449,21 +434,6 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.71",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz",
|
||||
"integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.14",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.14.tgz",
|
||||
"integrity": "sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -501,12 +471,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/earcut": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.2.3.tgz",
|
||||
"integrity": "sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
|
||||
@@ -524,12 +488,6 @@
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
|
||||
@@ -573,27 +531,6 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gifuct-js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz",
|
||||
"integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-binary-schema-parser": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/ismobilejs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz",
|
||||
"integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-binary-schema-parser": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz",
|
||||
"integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
|
||||
@@ -898,12 +835,6 @@
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-svg-path": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.2.0.tgz",
|
||||
"integrity": "sha512-Tf7FFIrguPKQwzD4pWnYkR2VOv3raoHeKED80Bm+BYHI3KxC8KsgsGC5+fSMzAGDA6UEk4bHvmi+RsjmL3khpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
@@ -931,32 +862,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pixi.js": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.19.0.tgz",
|
||||
"integrity": "sha512-pq1O6emA/GFjjeF+8d3Pb5t7knD8FsnfWGqQcRjYjsqFZ7QdzG1XgjLDUu0DFJRbafjV5+g8iNLFBx0b9649lg==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"examples",
|
||||
"playground"
|
||||
],
|
||||
"dependencies": {
|
||||
"@pixi/colord": "^2.9.6",
|
||||
"@types/earcut": "^3.0.0",
|
||||
"@webgpu/types": "^0.1.69",
|
||||
"@xmldom/xmldom": "^0.8.13",
|
||||
"earcut": "^3.0.2",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"gifuct-js": "^2.1.2",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"parse-svg-path": "^0.2.0",
|
||||
"tiny-lru": "^11.4.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/pixijs"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.26",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
|
||||
@@ -1050,15 +955,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-lru": {
|
||||
"version": "11.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz",
|
||||
"integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"pixi.js": "^8.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"typescript": "~5.9.3",
|
||||
|
||||
@@ -40,9 +40,25 @@ const ru = {
|
||||
errorInvalidStartDate: 'Дата начала вне допустимого диапазона.',
|
||||
|
||||
backToMenu: '← В главное меню',
|
||||
emptySchoolHint: 'Школа пока пуста — здесь появится сама игра.',
|
||||
pause: 'Пауза',
|
||||
resume: 'Продолжить',
|
||||
|
||||
mapTitle: 'Карта',
|
||||
eventsTitle: 'События',
|
||||
eventsEmpty: 'Пока ничего не происходит.',
|
||||
locationName: 'Локация',
|
||||
locationItems: 'Предметы',
|
||||
locationCharacters: 'Персонажи',
|
||||
locationActivities: 'Сейчас',
|
||||
locationPositions: 'Должности',
|
||||
itemsEmpty: 'Пусто.',
|
||||
charactersEmpty: 'Никого нет.',
|
||||
activitiesEmpty: 'Ничего не происходит.',
|
||||
positionsEmpty: 'Нет должностей.',
|
||||
stubTerritory: 'Двор',
|
||||
stubBuilding: 'Главный корпус',
|
||||
stubFloor: '1 этаж',
|
||||
stubRoom: 'Кабинет директора',
|
||||
} as const;
|
||||
|
||||
type Messages = { [K in keyof typeof ru]: string };
|
||||
@@ -87,9 +103,25 @@ const en: Messages = {
|
||||
errorInvalidStartDate: 'The start date is outside the allowed range.',
|
||||
|
||||
backToMenu: '← Main menu',
|
||||
emptySchoolHint: 'The school is empty for now — the game itself will appear here.',
|
||||
pause: 'Pause',
|
||||
resume: 'Resume',
|
||||
|
||||
mapTitle: 'Map',
|
||||
eventsTitle: 'Events',
|
||||
eventsEmpty: 'Nothing is happening yet.',
|
||||
locationName: 'Location',
|
||||
locationItems: 'Items',
|
||||
locationCharacters: 'People',
|
||||
locationActivities: 'Now',
|
||||
locationPositions: 'Positions',
|
||||
itemsEmpty: 'Empty.',
|
||||
charactersEmpty: 'Nobody here.',
|
||||
activitiesEmpty: 'Nothing is happening.',
|
||||
positionsEmpty: 'No positions.',
|
||||
stubTerritory: 'Yard',
|
||||
stubBuilding: 'Main building',
|
||||
stubFloor: 'Floor 1',
|
||||
stubRoom: "Principal's office",
|
||||
};
|
||||
|
||||
const catalogs: Record<Locale, Messages> = { ru, en };
|
||||
|
||||
@@ -24,7 +24,7 @@ body {
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 900px;
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 20px 72px;
|
||||
}
|
||||
@@ -191,6 +191,101 @@ body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.screen.game .clock {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.screen.game .clock__time {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
/* Manager */
|
||||
|
||||
.manager {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) minmax(200px, 1fr) minmax(240px, 1.35fr);
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
min-width: 0;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
.panel__title {
|
||||
margin: 0 0 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.panel__name {
|
||||
margin: 0 0 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.panel__section {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.panel__section-title {
|
||||
margin: 0 0 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.panel__empty {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tree .tree {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.tree__button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree__button:hover {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
.tree__button--active {
|
||||
border-color: var(--accent);
|
||||
background: rgba(76, 201, 240, 0.16);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.manager {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
|
||||
.button {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CLOCK_SPEEDS, type ClockMessage } from '../net/protocol.ts';
|
||||
import { formatGameDate, formatGameTimeOfDay, formatGameWeekday } from '../format/gameTime.ts';
|
||||
import { t } from '../i18n/strings.ts';
|
||||
import { t, type MessageKey } from '../i18n/strings.ts';
|
||||
import type { School } from '../net/api.ts';
|
||||
import { el } from './dom.ts';
|
||||
|
||||
@@ -10,11 +10,37 @@ interface GameScreenOptions {
|
||||
readonly onSetSpeed: (speedIndex: number) => void;
|
||||
}
|
||||
|
||||
interface StubNode {
|
||||
readonly id: string;
|
||||
readonly labelKey: MessageKey;
|
||||
readonly children?: readonly StubNode[];
|
||||
}
|
||||
|
||||
/** Placeholder tree until the server sends a map snapshot. Clicking only filters the location panel. */
|
||||
const STUB_MAP: StubNode = {
|
||||
id: 'territory',
|
||||
labelKey: 'stubTerritory',
|
||||
children: [
|
||||
{
|
||||
id: 'building',
|
||||
labelKey: 'stubBuilding',
|
||||
children: [
|
||||
{
|
||||
id: 'floor',
|
||||
labelKey: 'stubFloor',
|
||||
children: [{ id: 'room', labelKey: 'stubRoom' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const SPEED_LABELS = ['×½', '×1', '×2', '×3', '×4'];
|
||||
|
||||
/**
|
||||
* The inside of a school: for now the calendar and the controls that drive it. The server owns
|
||||
* the clock, so every button only sends an intent and the display follows the next clock frame.
|
||||
* The inside of a school: calendar controls plus the manager shell. Data for the tree and
|
||||
* location lists arrives in a later phase; until then the panels stay empty except for stubs
|
||||
* that prove the tree filters the location pane on the client.
|
||||
*/
|
||||
export class GameScreen {
|
||||
private readonly root = el('section', { class: 'screen game' });
|
||||
@@ -23,10 +49,26 @@ export class GameScreen {
|
||||
private readonly time = el('p', { class: 'clock__time', text: '--:--' });
|
||||
private readonly date = el('p', { class: 'clock__date' });
|
||||
private readonly weekday = el('p', { class: 'clock__weekday' });
|
||||
private readonly emptyHint = el('p', { class: 'hint' });
|
||||
private readonly playPauseButton = el('button', { class: 'button button--icon', type: 'button', text: '▶' });
|
||||
private readonly speedButtons: HTMLButtonElement[];
|
||||
|
||||
private readonly mapTitle = el('h2', { class: 'panel__title' });
|
||||
private readonly tree = el('ul', { class: 'tree' });
|
||||
private readonly eventsTitle = el('h2', { class: 'panel__title' });
|
||||
private readonly eventsEmpty = el('p', { class: 'panel__empty' });
|
||||
private readonly locationTitle = el('h2', { class: 'panel__title' });
|
||||
private readonly locationName = el('p', { class: 'panel__name' });
|
||||
private readonly itemsHeading = el('h3', { class: 'panel__section-title' });
|
||||
private readonly itemsEmpty = el('p', { class: 'panel__empty' });
|
||||
private readonly charactersHeading = el('h3', { class: 'panel__section-title' });
|
||||
private readonly charactersEmpty = el('p', { class: 'panel__empty' });
|
||||
private readonly activitiesHeading = el('h3', { class: 'panel__section-title' });
|
||||
private readonly activitiesEmpty = el('p', { class: 'panel__empty' });
|
||||
private readonly positionsHeading = el('h3', { class: 'panel__section-title' });
|
||||
private readonly positionsEmpty = el('p', { class: 'panel__empty' });
|
||||
|
||||
private readonly treeButtons = new Map<string, HTMLButtonElement>();
|
||||
private selectedId = STUB_MAP.id;
|
||||
private running = false;
|
||||
private lastGameTime: Date | null = null;
|
||||
private lastSpeedIndex = 0;
|
||||
@@ -44,6 +86,8 @@ export class GameScreen {
|
||||
this.backButton.addEventListener('click', options.onLeave);
|
||||
this.playPauseButton.addEventListener('click', () => options.onSetRunning(!this.running));
|
||||
|
||||
this.buildTree(this.tree, [STUB_MAP], 0);
|
||||
|
||||
this.root.append(
|
||||
el('header', { class: 'screen__header' }, this.backButton, this.schoolName),
|
||||
el(
|
||||
@@ -54,7 +98,22 @@ export class GameScreen {
|
||||
this.weekday,
|
||||
el('div', { class: 'clock__controls' }, this.playPauseButton, ...this.speedButtons),
|
||||
),
|
||||
this.emptyHint,
|
||||
el(
|
||||
'div',
|
||||
{ class: 'manager' },
|
||||
el('section', { class: 'panel' }, this.mapTitle, this.tree),
|
||||
el('section', { class: 'panel' }, this.eventsTitle, this.eventsEmpty),
|
||||
el(
|
||||
'section',
|
||||
{ class: 'panel' },
|
||||
this.locationTitle,
|
||||
this.locationName,
|
||||
el('div', { class: 'panel__section' }, this.itemsHeading, this.itemsEmpty),
|
||||
el('div', { class: 'panel__section' }, this.charactersHeading, this.charactersEmpty),
|
||||
el('div', { class: 'panel__section' }, this.activitiesHeading, this.activitiesEmpty),
|
||||
el('div', { class: 'panel__section' }, this.positionsHeading, this.positionsEmpty),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
this.localize();
|
||||
@@ -66,7 +125,21 @@ export class GameScreen {
|
||||
|
||||
localize(): void {
|
||||
this.backButton.textContent = t('backToMenu');
|
||||
this.emptyHint.textContent = t('emptySchoolHint');
|
||||
this.mapTitle.textContent = t('mapTitle');
|
||||
this.eventsTitle.textContent = t('eventsTitle');
|
||||
this.eventsEmpty.textContent = t('eventsEmpty');
|
||||
this.locationTitle.textContent = t('locationName');
|
||||
this.itemsHeading.textContent = t('locationItems');
|
||||
this.itemsEmpty.textContent = t('itemsEmpty');
|
||||
this.charactersHeading.textContent = t('locationCharacters');
|
||||
this.charactersEmpty.textContent = t('charactersEmpty');
|
||||
this.activitiesHeading.textContent = t('locationActivities');
|
||||
this.activitiesEmpty.textContent = t('activitiesEmpty');
|
||||
this.positionsHeading.textContent = t('locationPositions');
|
||||
this.positionsEmpty.textContent = t('positionsEmpty');
|
||||
|
||||
this.paintTreeLabels(STUB_MAP);
|
||||
this.paintSelection();
|
||||
|
||||
if (this.lastGameTime !== null) {
|
||||
this.applyClock(this.lastGameTime, this.running, this.lastSpeedIndex);
|
||||
@@ -85,6 +158,53 @@ export class GameScreen {
|
||||
this.applyClock(clock.gameTime, clock.running, clock.speedIndex);
|
||||
}
|
||||
|
||||
private buildTree(list: HTMLUListElement, nodes: readonly StubNode[], depth: number): void {
|
||||
for (const node of nodes) {
|
||||
const item = el('li', { class: 'tree__node' });
|
||||
const button = el('button', {
|
||||
class: 'tree__button',
|
||||
type: 'button',
|
||||
onClick: () => this.select(node.id),
|
||||
});
|
||||
button.style.paddingLeft = `${8 + depth * 14}px`;
|
||||
this.treeButtons.set(node.id, button);
|
||||
item.append(button);
|
||||
|
||||
if (node.children !== undefined && node.children.length > 0) {
|
||||
const nested = el('ul', { class: 'tree' });
|
||||
this.buildTree(nested, node.children, depth + 1);
|
||||
item.append(nested);
|
||||
}
|
||||
|
||||
list.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
private paintTreeLabels(node: StubNode): void {
|
||||
const button = this.treeButtons.get(node.id);
|
||||
if (button !== undefined) {
|
||||
button.textContent = t(node.labelKey);
|
||||
}
|
||||
|
||||
for (const child of node.children ?? []) {
|
||||
this.paintTreeLabels(child);
|
||||
}
|
||||
}
|
||||
|
||||
private select(id: string): void {
|
||||
this.selectedId = id;
|
||||
this.paintSelection();
|
||||
}
|
||||
|
||||
private paintSelection(): void {
|
||||
for (const [id, button] of this.treeButtons) {
|
||||
button.classList.toggle('tree__button--active', id === this.selectedId);
|
||||
}
|
||||
|
||||
const labelKey = findStub(STUB_MAP, this.selectedId)?.labelKey;
|
||||
this.locationName.textContent = labelKey === undefined ? '' : t(labelKey);
|
||||
}
|
||||
|
||||
private applyClock(gameTime: Date, running: boolean, speedIndex: number): void {
|
||||
this.running = running;
|
||||
this.lastGameTime = gameTime;
|
||||
@@ -102,3 +222,18 @@ export class GameScreen {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findStub(node: StubNode, id: string): StubNode | null {
|
||||
if (node.id === id) {
|
||||
return node;
|
||||
}
|
||||
|
||||
for (const child of node.children ?? []) {
|
||||
const match = findStub(child, id);
|
||||
if (match !== null) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user