Lint/format cleanup: void-expression and empty-function fixes, optional-chain narrowing, ignore share/ in eslint+prettier
This commit is contained in:
parent
3cd9a2377a
commit
99419880a7
11 changed files with 208 additions and 75 deletions
|
|
@ -2,4 +2,5 @@ node_modules
|
||||||
dist
|
dist
|
||||||
coverage
|
coverage
|
||||||
experiments
|
experiments
|
||||||
|
share
|
||||||
*.md
|
*.md
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,14 @@ import globals from "globals";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ["dist/**", "node_modules/**", "coverage/**", "experiments/**", "public/sw.js"],
|
ignores: [
|
||||||
|
"dist/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"coverage/**",
|
||||||
|
"experiments/**",
|
||||||
|
"public/sw.js",
|
||||||
|
"share/**",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...tseslint.configs.strictTypeChecked,
|
...tseslint.configs.strictTypeChecked,
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
<script type="module" src="/src/app/main.ts"></script>
|
<script type="module" src="/src/app/main.ts"></script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<p role="alert">
|
<p role="alert">
|
||||||
Solarpunk requires JavaScript to run the app shell. Emergency information is available on the
|
Solarpunk requires JavaScript to run the app shell. Emergency information is available on
|
||||||
printed festival materials.
|
the printed festival materials.
|
||||||
</p>
|
</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,28 @@ export function createLayout(): {
|
||||||
themeBtn.setAttribute("aria-label", "Toggle light/dark theme");
|
themeBtn.setAttribute("aria-label", "Toggle light/dark theme");
|
||||||
themeBtn.onclick = () => {
|
themeBtn.onclick = () => {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
if (html.classList.contains("dark")) { html.classList.remove("dark"); html.classList.add("light"); localStorage.setItem("theme","light"); themeBtn.textContent="☀"; }
|
if (html.classList.contains("dark")) {
|
||||||
else { document.documentElement.classList.remove("light"); document.documentElement.classList.add("dark"); localStorage.setItem("theme","dark"); themeBtn.textContent="◐"; }
|
html.classList.remove("dark");
|
||||||
|
html.classList.add("light");
|
||||||
|
localStorage.setItem("theme", "light");
|
||||||
|
themeBtn.textContent = "☀";
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("light");
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
localStorage.setItem("theme", "dark");
|
||||||
|
themeBtn.textContent = "◐";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Apply saved theme on load
|
// Apply saved theme on load
|
||||||
if (localStorage.getItem("theme") === "dark") { document.documentElement.classList.add("dark"); document.documentElement.classList.remove("light"); themeBtn.textContent="◐"; }
|
if (localStorage.getItem("theme") === "dark") {
|
||||||
else { document.documentElement.classList.add("light"); document.documentElement.classList.remove("dark"); themeBtn.textContent="☀"; }
|
document.documentElement.classList.add("dark");
|
||||||
|
document.documentElement.classList.remove("light");
|
||||||
|
themeBtn.textContent = "◐";
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add("light");
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
themeBtn.textContent = "☀";
|
||||||
|
}
|
||||||
header.append(themeBtn);
|
header.append(themeBtn);
|
||||||
|
|
||||||
const statusChip = document.createElement("div");
|
const statusChip = document.createElement("div");
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ export interface MapUrlFactory {
|
||||||
|
|
||||||
const defaultUrls: MapUrlFactory = {
|
const defaultUrls: MapUrlFactory = {
|
||||||
createUrl: (blob) => URL.createObjectURL(blob),
|
createUrl: (blob) => URL.createObjectURL(blob),
|
||||||
revokeUrl: (url) => URL.revokeObjectURL(url),
|
revokeUrl: (url) => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MapHandle {
|
export interface MapHandle {
|
||||||
|
|
@ -43,7 +45,12 @@ export async function loadMapData(urls: MapUrlFactory = defaultUrls): Promise<Ma
|
||||||
return { data, revoke };
|
return { data, revoke };
|
||||||
} catch {
|
} catch {
|
||||||
revoke();
|
revoke();
|
||||||
return { data: null, revoke: () => {} };
|
return {
|
||||||
|
data: null,
|
||||||
|
revoke: () => {
|
||||||
|
/* nothing was created */
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,40 +110,91 @@ html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
html.dark {
|
html.dark {
|
||||||
--bg: #0b1411; --fg: #ede9de; --deck-bg: #0b1411; --deck-fg: #ede9de; --deck-surface: rgba(255,255,255,0.035); --deck-line: rgba(255,255,255,0.1); --accent: #75c7a9; --surface: rgba(255,255,255,0.035); --border: rgba(255,255,255,0.1);
|
--bg: #0b1411;
|
||||||
|
--fg: #ede9de;
|
||||||
|
--deck-bg: #0b1411;
|
||||||
|
--deck-fg: #ede9de;
|
||||||
|
--deck-surface: rgba(255, 255, 255, 0.035);
|
||||||
|
--deck-line: rgba(255, 255, 255, 0.1);
|
||||||
|
--accent: #75c7a9;
|
||||||
|
--surface: rgba(255, 255, 255, 0.035);
|
||||||
|
--border: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
html.light :root,
|
html.light :root,
|
||||||
html.light body {
|
html.light body {
|
||||||
--deck-bg: #f6f9f7; --deck-fg: #172621; --deck-fg-muted: #576b63;
|
--deck-bg: #f6f9f7;
|
||||||
--deck-surface: rgba(20,30,25,0.04); --deck-surface-2: rgba(20,30,25,0.06); --deck-line: rgba(20,30,25,0.12); --deck-line-2: rgba(20,30,25,0.2);
|
--deck-fg: #172621;
|
||||||
--deck-pine: #1f7a5f; --deck-indigo: #683ecc; --deck-rose: #d3224b; --deck-clay: #bf4622; --deck-amber: #b8610a;
|
--deck-fg-muted: #576b63;
|
||||||
--bg: #f6f9f7; --fg: #172621; --muted: #576b63; --accent: #1f7a5f; --surface: rgba(20,30,25,0.04); --border: rgba(20,30,25,0.12);
|
--deck-surface: rgba(20, 30, 25, 0.04);
|
||||||
--highlight-bg: rgba(184,97,10,0.07); --highlight-border: rgba(184,97,10,0.3);
|
--deck-surface-2: rgba(20, 30, 25, 0.06);
|
||||||
--chip-pink-bg: rgba(211,34,75,0.08); --chip-pink-fg: #b5244a;
|
--deck-line: rgba(20, 30, 25, 0.12);
|
||||||
--chip-green-bg: rgba(31,122,95,0.09); --chip-green-fg: #1f7a5f;
|
--deck-line-2: rgba(20, 30, 25, 0.2);
|
||||||
--chip-indigo-bg: rgba(104,62,204,0.08); --chip-indigo-fg: #683ecc;
|
--deck-pine: #1f7a5f;
|
||||||
--chip-amber-bg: rgba(184,97,10,0.09); --chip-amber-fg: #96500a;
|
--deck-indigo: #683ecc;
|
||||||
--focus: #1f7a5f; --emergency: #d3224b; --emergency-contrast: #fff;
|
--deck-rose: #d3224b;
|
||||||
|
--deck-clay: #bf4622;
|
||||||
|
--deck-amber: #b8610a;
|
||||||
|
--bg: #f6f9f7;
|
||||||
|
--fg: #172621;
|
||||||
|
--muted: #576b63;
|
||||||
|
--accent: #1f7a5f;
|
||||||
|
--surface: rgba(20, 30, 25, 0.04);
|
||||||
|
--border: rgba(20, 30, 25, 0.12);
|
||||||
|
--highlight-bg: rgba(184, 97, 10, 0.07);
|
||||||
|
--highlight-border: rgba(184, 97, 10, 0.3);
|
||||||
|
--chip-pink-bg: rgba(211, 34, 75, 0.08);
|
||||||
|
--chip-pink-fg: #b5244a;
|
||||||
|
--chip-green-bg: rgba(31, 122, 95, 0.09);
|
||||||
|
--chip-green-fg: #1f7a5f;
|
||||||
|
--chip-indigo-bg: rgba(104, 62, 204, 0.08);
|
||||||
|
--chip-indigo-fg: #683ecc;
|
||||||
|
--chip-amber-bg: rgba(184, 97, 10, 0.09);
|
||||||
|
--chip-amber-fg: #96500a;
|
||||||
|
--focus: #1f7a5f;
|
||||||
|
--emergency: #d3224b;
|
||||||
|
--emergency-contrast: #fff;
|
||||||
}
|
}
|
||||||
html.dark :root,
|
html.dark :root,
|
||||||
html.dark body {
|
html.dark body {
|
||||||
--deck-bg: #0b1411; --deck-fg: #ede9de; --deck-fg-muted: #96a69e;
|
--deck-bg: #0b1411;
|
||||||
--deck-surface: rgba(255,255,255,0.035); --deck-surface-2: rgba(255,255,255,0.055);
|
--deck-fg: #ede9de;
|
||||||
--deck-line: rgba(255,255,255,0.1); --deck-line-2: rgba(255,255,255,0.16);
|
--deck-fg-muted: #96a69e;
|
||||||
--deck-pine: #75c7a9; --deck-indigo: #af97f7; --deck-rose: #fc88ab; --deck-clay: #e47958; --deck-amber: #fcc669;
|
--deck-surface: rgba(255, 255, 255, 0.035);
|
||||||
--bg: var(--deck-bg); --fg: var(--deck-fg); --muted: var(--deck-fg-muted);
|
--deck-surface-2: rgba(255, 255, 255, 0.055);
|
||||||
--accent: var(--deck-pine); --accent-contrast: var(--deck-on-accent);
|
--deck-line: rgba(255, 255, 255, 0.1);
|
||||||
--accent-soft: rgba(117,199,169,0.12);
|
--deck-line-2: rgba(255, 255, 255, 0.16);
|
||||||
--surface: var(--deck-surface); --border: var(--deck-line); --border-accent: rgba(117,199,169,0.32);
|
--deck-pine: #75c7a9;
|
||||||
--highlight-bg: rgba(252,198,105,0.07); --highlight-border: rgba(252,198,105,0.3);
|
--deck-indigo: #af97f7;
|
||||||
--chip-pink-bg: rgba(252,136,171,0.12); --chip-pink-fg: #fc88ab;
|
--deck-rose: #fc88ab;
|
||||||
--chip-green-bg: rgba(117,199,169,0.12); --chip-green-fg: #75c7a9;
|
--deck-clay: #e47958;
|
||||||
--chip-indigo-bg: rgba(175,151,247,0.12); --chip-indigo-fg: #af97f7;
|
--deck-amber: #fcc669;
|
||||||
--chip-amber-bg: rgba(252,198,105,0.12); --chip-amber-fg: #fcc669;
|
--bg: var(--deck-bg);
|
||||||
--focus: var(--deck-pine); --emergency: #fc88ab; --emergency-contrast: var(--deck-on-accent);
|
--fg: var(--deck-fg);
|
||||||
|
--muted: var(--deck-fg-muted);
|
||||||
|
--accent: var(--deck-pine);
|
||||||
|
--accent-contrast: var(--deck-on-accent);
|
||||||
|
--accent-soft: rgba(117, 199, 169, 0.12);
|
||||||
|
--surface: var(--deck-surface);
|
||||||
|
--border: var(--deck-line);
|
||||||
|
--border-accent: rgba(117, 199, 169, 0.32);
|
||||||
|
--highlight-bg: rgba(252, 198, 105, 0.07);
|
||||||
|
--highlight-border: rgba(252, 198, 105, 0.3);
|
||||||
|
--chip-pink-bg: rgba(252, 136, 171, 0.12);
|
||||||
|
--chip-pink-fg: #fc88ab;
|
||||||
|
--chip-green-bg: rgba(117, 199, 169, 0.12);
|
||||||
|
--chip-green-fg: #75c7a9;
|
||||||
|
--chip-indigo-bg: rgba(175, 151, 247, 0.12);
|
||||||
|
--chip-indigo-fg: #af97f7;
|
||||||
|
--chip-amber-bg: rgba(252, 198, 105, 0.12);
|
||||||
|
--chip-amber-fg: #fcc669;
|
||||||
|
--focus: var(--deck-pine);
|
||||||
|
--emergency: #fc88ab;
|
||||||
|
--emergency-contrast: var(--deck-on-accent);
|
||||||
}
|
}
|
||||||
html.dark body {
|
html.dark body {
|
||||||
background: radial-gradient(120% 80% at 80% -10%, rgba(14,57,44,0.55), transparent 60%), radial-gradient(90% 70% at -10% 110%, rgba(83,48,9,0.35), transparent 55%), var(--bg);
|
background:
|
||||||
|
radial-gradient(120% 80% at 80% -10%, rgba(14, 57, 44, 0.55), transparent 60%),
|
||||||
|
radial-gradient(90% 70% at -10% 110%, rgba(83, 48, 9, 0.35), transparent 55%), var(--bg);
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
html {
|
html {
|
||||||
|
|
@ -359,7 +410,14 @@ h3 {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.theme-btn {
|
.theme-btn {
|
||||||
min-height: 36px; padding: 0.25rem 0.5rem; font: inherit; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; cursor: pointer; color: var(--fg);
|
min-height: 36px;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font: inherit;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
.app-header__status {
|
.app-header__status {
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,28 @@ const MS_PER_DAY = 24 * 3600 * 1000;
|
||||||
/* ClockService — pure, no network, no persistence. Pure time-model logic per SPIKE-08. */
|
/* ClockService — pure, no network, no persistence. Pure time-model logic per SPIKE-08. */
|
||||||
export interface ClockService {
|
export interface ClockService {
|
||||||
correctedNow(deviceNow: number, skew: ClockSkew | null): number;
|
correctedNow(deviceNow: number, skew: ClockSkew | null): number;
|
||||||
hasDrift(baseDevice: number, monoElapsed: number, observedDevice: number, thresholdMs?: number): boolean;
|
hasDrift(
|
||||||
shouldWarnOutsideWindow(nowCorrected: number, win: FestivalWindow, skew: ClockSkew | null, opts?: ClockOptions): boolean;
|
baseDevice: number,
|
||||||
|
monoElapsed: number,
|
||||||
|
observedDevice: number,
|
||||||
|
thresholdMs?: number,
|
||||||
|
): boolean;
|
||||||
|
shouldWarnOutsideWindow(
|
||||||
|
nowCorrected: number,
|
||||||
|
win: FestivalWindow,
|
||||||
|
skew: ClockSkew | null,
|
||||||
|
opts?: ClockOptions,
|
||||||
|
): boolean;
|
||||||
renderInZone(epochMs: number, timeZone: string): string;
|
renderInZone(epochMs: number, timeZone: string): string;
|
||||||
formatEventTime(epochMs: number, timeZone: string): string;
|
formatEventTime(epochMs: number, timeZone: string): string;
|
||||||
formatDayLabel(epochMs: number, timeZone: string): string;
|
formatDayLabel(epochMs: number, timeZone: string): string;
|
||||||
formatClockTime(epochMs: number, timeZone: string): string;
|
formatClockTime(epochMs: number, timeZone: string): string;
|
||||||
deviceTimeZone(): string;
|
deviceTimeZone(): string;
|
||||||
dayKeyFor(epochMs: number, timeZone: string): string;
|
dayKeyFor(epochMs: number, timeZone: string): string;
|
||||||
classifyNowNext<T extends SchedEventRef>(events: readonly T[], now: number): { now: readonly T[]; next: readonly T[] };
|
classifyNowNext<T extends SchedEventRef>(
|
||||||
|
events: readonly T[],
|
||||||
|
now: number,
|
||||||
|
): { now: readonly T[]; next: readonly T[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Render epoch ms in festival IANA zone via Intl (offline, ECMA-402). */
|
/** Render epoch ms in festival IANA zone via Intl (offline, ECMA-402). */
|
||||||
|
|
@ -72,7 +85,7 @@ export function formatClockTime(epochMs: number, timeZone: string): string {
|
||||||
/** Device IANA zone for the display toggle — UTC fallback when unresolvable. */
|
/** Device IANA zone for the display toggle — UTC fallback when unresolvable. */
|
||||||
export function deviceTimeZone(): string {
|
export function deviceTimeZone(): string {
|
||||||
try {
|
try {
|
||||||
return new Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
|
return new Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
||||||
} catch {
|
} catch {
|
||||||
return "UTC";
|
return "UTC";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,11 @@ export function groupFacilities(
|
||||||
category,
|
category,
|
||||||
pois: [...items].sort((a, b) => a.name.localeCompare(b.name)),
|
pois: [...items].sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => (order.get(a.category) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.category) ?? Number.MAX_SAFE_INTEGER));
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(order.get(a.category) ?? Number.MAX_SAFE_INTEGER) -
|
||||||
|
(order.get(b.category) ?? Number.MAX_SAFE_INTEGER),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Distinct categories present, in section order — for filter chips. */
|
/** Distinct categories present, in section order — for filter chips. */
|
||||||
|
|
@ -147,7 +151,7 @@ function validPoi(value: unknown): boolean {
|
||||||
/** Structural map check — per-POI tolerance lives in the queries above. */
|
/** Structural map check — per-POI tolerance lives in the queries above. */
|
||||||
export function narrowMapSection(value: unknown): MapSection | null {
|
export function narrowMapSection(value: unknown): MapSection | null {
|
||||||
const section = record(value);
|
const section = record(value);
|
||||||
if (!section || section.section !== "map") return null;
|
if (section?.section !== "map") return null;
|
||||||
const base = record(section.base);
|
const base = record(section.base);
|
||||||
if (!base || !Array.isArray(base.levels) || !base.levels.every(validLevel)) return null;
|
if (!base || !Array.isArray(base.levels) || !base.levels.every(validLevel)) return null;
|
||||||
if (!Array.isArray(section.pois) || !section.pois.every(validPoi)) return null;
|
if (!Array.isArray(section.pois) || !section.pois.every(validPoi)) return null;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,12 @@
|
||||||
* optional lat/lng is a hook for future positioning only (no GPS in V1).
|
* optional lat/lng is a hook for future positioning only (no GPS in V1).
|
||||||
* Trace: IMPLEMENTATION-CONTRACT.md §22, SPIKE-03.
|
* Trace: IMPLEMENTATION-CONTRACT.md §22, SPIKE-03.
|
||||||
*/
|
*/
|
||||||
import type { MapLevel, MapPoi, MapSection, PoiCategory } from "../../data/festival-package/types.js";
|
import type {
|
||||||
|
MapLevel,
|
||||||
|
MapPoi,
|
||||||
|
MapSection,
|
||||||
|
PoiCategory,
|
||||||
|
} from "../../data/festival-package/types.js";
|
||||||
|
|
||||||
export type { MapPoi, PoiCategory };
|
export type { MapPoi, PoiCategory };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,7 @@ import {
|
||||||
poiPosition,
|
poiPosition,
|
||||||
presentCategories,
|
presentCategories,
|
||||||
} from "../../../domain/map/logic.js";
|
} from "../../../domain/map/logic.js";
|
||||||
import type {
|
import type { MapLevelView, MapPoi, MapViewData, PoiCategory } from "../../../domain/map/types.js";
|
||||||
MapLevelView,
|
|
||||||
MapPoi,
|
|
||||||
MapViewData,
|
|
||||||
PoiCategory,
|
|
||||||
} from "../../../domain/map/types.js";
|
|
||||||
|
|
||||||
const MIN_SCALE = 1;
|
const MIN_SCALE = 1;
|
||||||
const MAX_SCALE = 4;
|
const MAX_SCALE = 4;
|
||||||
|
|
@ -65,7 +60,10 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
card.className = "view-placeholder";
|
card.className = "view-placeholder";
|
||||||
card.append(text("p", "The map isn't on this device yet."));
|
card.append(text("p", "The map isn't on this device yet."));
|
||||||
card.append(
|
card.append(
|
||||||
text("p", "Open with internet to get festival data — the emergency floor always works meanwhile."),
|
text(
|
||||||
|
"p",
|
||||||
|
"Open with internet to get festival data — the emergency floor always works meanwhile.",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
section.append(card);
|
section.append(card);
|
||||||
return section;
|
return section;
|
||||||
|
|
@ -127,7 +125,10 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
const stage = document.createElement("div");
|
const stage = document.createElement("div");
|
||||||
stage.className = "map-stage";
|
stage.className = "map-stage";
|
||||||
stage.tabIndex = 0;
|
stage.tabIndex = 0;
|
||||||
stage.setAttribute("aria-label", "Festival map. Drag to pan, use the zoom buttons, or arrow keys. Full facility list follows.");
|
stage.setAttribute(
|
||||||
|
"aria-label",
|
||||||
|
"Festival map. Drag to pan, use the zoom buttons, or arrow keys. Full facility list follows.",
|
||||||
|
);
|
||||||
stage.style.touchAction = "none";
|
stage.style.touchAction = "none";
|
||||||
|
|
||||||
const world = document.createElement("div");
|
const world = document.createElement("div");
|
||||||
|
|
@ -216,7 +217,11 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const rect = stage.getBoundingClientRect();
|
const rect = stage.getBoundingClientRect();
|
||||||
zoomAt(event.clientX - rect.left, event.clientY - rect.top, event.deltaY < 0 ? 1.25 : 1 / 1.25);
|
zoomAt(
|
||||||
|
event.clientX - rect.left,
|
||||||
|
event.clientY - rect.top,
|
||||||
|
event.deltaY < 0 ? 1.25 : 1 / 1.25,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{ passive: false },
|
{ passive: false },
|
||||||
);
|
);
|
||||||
|
|
@ -300,7 +305,9 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
// Filter application shared by chips + search.
|
// Filter application shared by chips + search.
|
||||||
const applyMarkerVisibility = (): void => {
|
const applyMarkerVisibility = (): void => {
|
||||||
const visible = new Set(
|
const visible = new Set(
|
||||||
filterPois(input.pois, { categories: state.categories, query: state.query }).map((p) => p.id),
|
filterPois(input.pois, { categories: state.categories, query: state.query }).map(
|
||||||
|
(p) => p.id,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
for (const [id, marker] of markers) marker.hidden = !visible.has(id);
|
for (const [id, marker] of markers) marker.hidden = !visible.has(id);
|
||||||
};
|
};
|
||||||
|
|
@ -313,7 +320,9 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
} else {
|
} else {
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "view-placeholder";
|
card.className = "view-placeholder";
|
||||||
card.append(text("p", "The map image isn't downloaded — the facilities list below still works."));
|
card.append(
|
||||||
|
text("p", "The map image isn't downloaded — the facilities list below still works."),
|
||||||
|
);
|
||||||
section.append(card);
|
section.append(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,7 +339,14 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
const paintChips = (): void => {
|
const paintChips = (): void => {
|
||||||
for (const button of chipButtons) {
|
for (const button of chipButtons) {
|
||||||
const category = button.getAttribute("data-category") ?? "";
|
const category = button.getAttribute("data-category") ?? "";
|
||||||
button.setAttribute("aria-pressed", String(category === "" ? state.categories.size === 0 : state.categories.has(category as PoiCategory)));
|
button.setAttribute(
|
||||||
|
"aria-pressed",
|
||||||
|
String(
|
||||||
|
category === ""
|
||||||
|
? state.categories.size === 0
|
||||||
|
: state.categories.has(category as PoiCategory),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const allButton = document.createElement("button");
|
const allButton = document.createElement("button");
|
||||||
|
|
@ -382,14 +398,20 @@ export function createMapView(input: MapViewData | null): HTMLElement {
|
||||||
facilities.className = "map-facilities";
|
facilities.className = "map-facilities";
|
||||||
section.append(facilities);
|
section.append(facilities);
|
||||||
|
|
||||||
|
// Narrowed binding: the `!input` guard above returned early, so `input` is
|
||||||
|
// MapViewData from here on (captured for the closure below).
|
||||||
|
const data = input;
|
||||||
|
|
||||||
function refreshFacilities(): void {
|
function refreshFacilities(): void {
|
||||||
facilities.replaceChildren();
|
facilities.replaceChildren();
|
||||||
const visible = filterPois(input!.pois, { categories: state.categories, query: state.query });
|
const visible = filterPois(data.pois, { categories: state.categories, query: state.query });
|
||||||
if (visible.length === 0) {
|
if (visible.length === 0) {
|
||||||
facilities.append(text("p", input!.pois.length === 0 ? "No facilities listed." : "No facilities match."));
|
facilities.append(
|
||||||
|
text("p", data.pois.length === 0 ? "No facilities listed." : "No facilities match."),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const group of groupFacilities(visible, input!.categories)) {
|
for (const group of groupFacilities(visible, data.categories)) {
|
||||||
const heading = document.createElement("h2");
|
const heading = document.createElement("h2");
|
||||||
heading.textContent = `${group.category} (${String(group.pois.length)})`;
|
heading.textContent = `${group.category} (${String(group.pois.length)})`;
|
||||||
facilities.append(heading);
|
facilities.append(heading);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class MemoryStorage implements Storage {
|
||||||
this.map.clear();
|
this.map.clear();
|
||||||
}
|
}
|
||||||
getItem(key: string): string | null {
|
getItem(key: string): string | null {
|
||||||
return this.map.has(key) ? (this.map.get(key) as string) : null;
|
return this.map.get(key) ?? null;
|
||||||
}
|
}
|
||||||
key(index: number): string | null {
|
key(index: number): string | null {
|
||||||
return [...this.map.keys()][index] ?? null;
|
return [...this.map.keys()][index] ?? null;
|
||||||
|
|
@ -25,7 +25,7 @@ class MemoryStorage implements Storage {
|
||||||
this.map.delete(key);
|
this.map.delete(key);
|
||||||
}
|
}
|
||||||
setItem(key: string, value: string): void {
|
setItem(key: string, value: string): void {
|
||||||
this.map.set(key, String(value));
|
this.map.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue