Keep several Ziele and fill the Cache from Einstellungen.
A Koordinate adds another place, and Hessen fetches only missing Kacheln without marking a Gebiet. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,18 @@ function stringsFrom(value: unknown): string[] {
|
|||||||
return value.filter((item): item is string => typeof item === "string");
|
return value.filter((item): item is string => typeof item === "string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rememberedFrom(value: unknown): { heruntergeladen: string[]; ziele: string[] } {
|
||||||
|
if (Array.isArray(value)) return { heruntergeladen: stringsFrom(value), ziele: [] };
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
const record = value as { heruntergeladen?: unknown; ziele?: unknown };
|
||||||
|
return {
|
||||||
|
heruntergeladen: stringsFrom(record.heruntergeladen),
|
||||||
|
ziele: stringsFrom(record.ziele),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { heruntergeladen: [], ziele: [] };
|
||||||
|
}
|
||||||
|
|
||||||
function settle(tx: IDBTransaction, value: () => unknown): Promise<unknown> {
|
function settle(tx: IDBTransaction, value: () => unknown): Promise<unknown> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
tx.oncomplete = () => resolve(value());
|
tx.oncomplete = () => resolve(value());
|
||||||
@@ -37,7 +49,7 @@ export function createMarksDb(): MarksPort {
|
|||||||
const tx = db.transaction(STORE, "readonly");
|
const tx = db.transaction(STORE, "readonly");
|
||||||
const request = tx.objectStore(STORE).get(KEY);
|
const request = tx.objectStore(STORE).get(KEY);
|
||||||
const value = await settle(tx, () => request.result);
|
const value = await settle(tx, () => request.result);
|
||||||
return { heruntergeladen: stringsFrom(value) };
|
return rememberedFrom(value);
|
||||||
} finally {
|
} finally {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
@@ -47,7 +59,10 @@ export function createMarksDb(): MarksPort {
|
|||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
try {
|
try {
|
||||||
const tx = db.transaction(STORE, "readwrite");
|
const tx = db.transaction(STORE, "readwrite");
|
||||||
tx.objectStore(STORE).put(state.heruntergeladen, KEY);
|
tx.objectStore(STORE).put(
|
||||||
|
{ heruntergeladen: state.heruntergeladen, ziele: state.ziele },
|
||||||
|
KEY,
|
||||||
|
);
|
||||||
await settle(tx, () => undefined);
|
await settle(tx, () => undefined);
|
||||||
} finally {
|
} finally {
|
||||||
db.close();
|
db.close();
|
||||||
|
|||||||
+3
-1
@@ -17,12 +17,14 @@ export function createMemoryCache(initial: string[] = []): CachePort {
|
|||||||
|
|
||||||
export function createMemoryMarks(initial: string[] = []): MarksPort {
|
export function createMemoryMarks(initial: string[] = []): MarksPort {
|
||||||
let heruntergeladen = [...initial];
|
let heruntergeladen = [...initial];
|
||||||
|
let ziele: string[] = [];
|
||||||
return {
|
return {
|
||||||
async read() {
|
async read() {
|
||||||
return { heruntergeladen: [...heruntergeladen] };
|
return { heruntergeladen: [...heruntergeladen], ziele: [...ziele] };
|
||||||
},
|
},
|
||||||
async write(state) {
|
async write(state) {
|
||||||
heruntergeladen = [...state.heruntergeladen];
|
heruntergeladen = [...state.heruntergeladen];
|
||||||
|
ziele = [...state.ziele];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ const GITTER_PATTERN = /^(\d{1,2})([C-HJ-NP-X])([A-HJ-NP-Z]{2})$/;
|
|||||||
export function parseKoordinate(
|
export function parseKoordinate(
|
||||||
input: { gitter: string; rechtswert: string; hochwert: string },
|
input: { gitter: string; rechtswert: string; hochwert: string },
|
||||||
blattCovers: (point: Point) => boolean,
|
blattCovers: (point: Point) => boolean,
|
||||||
): { ok: true; point: Point } | { ok: false; notice: string } {
|
): { ok: true; point: Point; koordinate: string } | { ok: false; notice: string } {
|
||||||
if (!/^\d{5}$/.test(input.rechtswert) || !/^\d{5}$/.test(input.hochwert)) {
|
if (!/^\d{5}$/.test(input.rechtswert) || !/^\d{5}$/.test(input.hochwert)) {
|
||||||
return { ok: false, notice: DIGITS };
|
return { ok: false, notice: DIGITS };
|
||||||
}
|
}
|
||||||
|
|
||||||
const compact = input.gitter.trim().toUpperCase().replace(/\s+/g, "");
|
const compact = input.gitter.trim().toUpperCase().replace(/\s+/g, "");
|
||||||
if (!GITTER_PATTERN.test(compact)) {
|
const gitter = GITTER_PATTERN.exec(compact);
|
||||||
|
if (!gitter) {
|
||||||
return { ok: false, notice: GITTER_NOTICE };
|
return { ok: false, notice: GITTER_NOTICE };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,5 +38,10 @@ export function parseKoordinate(
|
|||||||
const projected = proj4("EPSG:4326", "EPSG:25832", geographic);
|
const projected = proj4("EPSG:4326", "EPSG:25832", geographic);
|
||||||
const point = { easting: projected[0], northing: projected[1] };
|
const point = { easting: projected[0], northing: projected[1] };
|
||||||
if (!blattCovers(point)) return { ok: false, notice: BLATT };
|
if (!blattCovers(point)) return { ok: false, notice: BLATT };
|
||||||
return { ok: true, point };
|
const canonicalGitter = `${gitter[1]}${gitter[2]} ${gitter[3]}`;
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
point,
|
||||||
|
koordinate: `${canonicalGitter} ${input.rechtswert} ${input.hochwert}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+337
-179
@@ -1,7 +1,7 @@
|
|||||||
import { shippedConfig } from "../config";
|
import { shippedConfig } from "../config";
|
||||||
import { createFetchClock, createMemoryCache, createMemoryMarks } from "./fakes";
|
import { createFetchClock, createMemoryCache, createMemoryMarks } from "./fakes";
|
||||||
import { openKarte } from "./session";
|
import { openKarte } from "./session";
|
||||||
import type { KarteSession, KarteSnapshot, Point, Uebersicht, Viewport } from "./types";
|
import type { KarteSession, Point, Uebersicht, Viewport } from "./types";
|
||||||
|
|
||||||
const sheet: Uebersicht = {
|
const sheet: Uebersicht = {
|
||||||
tileSizePx: 10,
|
tileSizePx: 10,
|
||||||
@@ -71,12 +71,6 @@ function appearance(karte: KarteSession, id: string) {
|
|||||||
return kachel.appearance;
|
return kachel.appearance;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hessen(snapshot: KarteSnapshot) {
|
|
||||||
const gebiet = snapshot.gebiete.find((item) => item.name === "Hessen");
|
|
||||||
if (!gebiet) throw new Error("Hessen missing");
|
|
||||||
return gebiet;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sample = { gitter: "32U MA", rechtswert: "67030", hochwert: "78109" };
|
const sample = { gitter: "32U MA", rechtswert: "67030", hochwert: "78109" };
|
||||||
|
|
||||||
describe("opening the Karte", () => {
|
describe("opening the Karte", () => {
|
||||||
@@ -87,7 +81,7 @@ describe("opening the Karte", () => {
|
|||||||
expect(snapshot.resolution).toBe(7);
|
expect(snapshot.resolution).toBe(7);
|
||||||
expect(snapshot.center).toEqual({ easting: 150, northing: 50 });
|
expect(snapshot.center).toEqual({ easting: 150, northing: 50 });
|
||||||
expect(snapshot.koordinate.gitter).toBe("32U MA");
|
expect(snapshot.koordinate.gitter).toBe("32U MA");
|
||||||
expect(snapshot.ziel).toBeNull();
|
expect(snapshot.ziele).toEqual([]);
|
||||||
expect(snapshot.standort).toBeNull();
|
expect(snapshot.standort).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,18 +237,6 @@ describe("fetching a missing Kachel", () => {
|
|||||||
expect(appearance(opened.karte, "1/0/0")).toBe("blank-downloading");
|
expect(appearance(opened.karte, "1/0/0")).toBe("blank-downloading");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("leaving the app pauses the fetch, and opening online continues it", async () => {
|
|
||||||
const { karte, clock } = await viewTile(250);
|
|
||||||
karte.setOnline(true);
|
|
||||||
karte.startGebiet("Hessen");
|
|
||||||
karte.setAppOpen(false);
|
|
||||||
await clock.tick();
|
|
||||||
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
|
||||||
karte.setAppOpen(true);
|
|
||||||
await clock.tick();
|
|
||||||
expect(appearance(karte, TILE_C)).toBe("drawn");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a finished Kachel is still drawn after the app is left and opened again", async () => {
|
test("a finished Kachel is still drawn after the app is left and opened again", async () => {
|
||||||
const cache = createMemoryCache();
|
const cache = createMemoryCache();
|
||||||
const marks = createMemoryMarks();
|
const marks = createMemoryMarks();
|
||||||
@@ -270,7 +252,7 @@ describe("fetching a missing Kachel", () => {
|
|||||||
second.karte.zoomTo(10);
|
second.karte.zoomTo(10);
|
||||||
second.karte.panTo({ easting: 250, northing: 50 });
|
second.karte.panTo({ easting: 250, northing: 50 });
|
||||||
expect(appearance(second.karte, TILE_C)).toBe("drawn");
|
expect(appearance(second.karte, TILE_C)).toBe("drawn");
|
||||||
expect(second.karte.snapshot().ziel).toBeNull();
|
expect(second.karte.snapshot().ziele).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -281,24 +263,29 @@ describe("naming a Ziel", () => {
|
|||||||
const snapshot = karte.snapshot();
|
const snapshot = karte.snapshot();
|
||||||
expect(snapshot.koordinate).toEqual({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" });
|
expect(snapshot.koordinate).toEqual({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" });
|
||||||
expect(snapshot.resolution).toBe(7);
|
expect(snapshot.resolution).toBe(7);
|
||||||
expect(snapshot.ziel).not.toBeNull();
|
expect(snapshot.ziele).toHaveLength(1);
|
||||||
expect(Math.abs(snapshot.ziel!.easting - 467_030)).toBeLessThan(1);
|
expect(snapshot.ziele[0].koordinate).toBe("32U MA 67030 78109");
|
||||||
expect(Math.abs(snapshot.ziel!.northing - 5_578_109)).toBeLessThan(1);
|
expect(Math.abs(snapshot.ziele[0].point.easting - 467_030)).toBeLessThan(1);
|
||||||
|
expect(Math.abs(snapshot.ziele[0].point.northing - 5_578_109)).toBeLessThan(1);
|
||||||
|
expect(snapshot.ziele[0].entfernung).toBeNull();
|
||||||
|
expect(snapshot.center).toEqual(snapshot.ziele[0].point);
|
||||||
expect(snapshot.notice).toBe("Nicht auf dem Gerät.");
|
expect(snapshot.notice).toBe("Nicht auf dem Gerät.");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("treats 32UMA like 32U MA", async () => {
|
test("treats 32UMA like 32U MA", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: wideSquare });
|
const { karte } = await openSheet({ uebersicht: wideSquare });
|
||||||
karte.enterKoordinate({ gitter: "32UMA", rechtswert: "67030", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32UMA", rechtswert: "67030", hochwert: "78109" });
|
||||||
expect(Math.abs(karte.snapshot().ziel!.easting - 467_030)).toBeLessThan(1);
|
const snapshot = karte.snapshot();
|
||||||
expect(karte.snapshot().koordinate.gitter).toBe("32UMA");
|
expect(snapshot.ziele[0].koordinate).toBe("32U MA 67030 78109");
|
||||||
|
expect(Math.abs(snapshot.ziele[0].point.easting - 467_030)).toBeLessThan(1);
|
||||||
|
expect(snapshot.koordinate.gitter).toBe("32UMA");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("accepts a leading zero as five digits, and works with the device offline", async () => {
|
test("accepts a leading zero as five digits, and works with the device offline", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: wideSquare, online: false });
|
const { karte } = await openSheet({ uebersicht: wideSquare, online: false });
|
||||||
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "06703", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "06703", hochwert: "78109" });
|
||||||
expect(karte.snapshot().ziel).not.toBeNull();
|
expect(karte.snapshot().ziele[0].koordinate).toBe("32U MA 06703 78109");
|
||||||
expect(Math.abs(karte.snapshot().ziel!.easting - 406_703)).toBeLessThan(1);
|
expect(Math.abs(karte.snapshot().ziele[0].point.easting - 406_703)).toBeLessThan(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rejects a Rechtswert or Hochwert that is not exactly five digits", async () => {
|
test("rejects a Rechtswert or Hochwert that is not exactly five digits", async () => {
|
||||||
@@ -308,13 +295,14 @@ describe("naming a Ziel", () => {
|
|||||||
karte.panTo({ easting: 420_000, northing: 5_520_000 });
|
karte.panTo({ easting: 420_000, northing: 5_520_000 });
|
||||||
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "6703", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "6703", hochwert: "78109" });
|
||||||
expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben.");
|
expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben.");
|
||||||
expect(karte.snapshot().ziel).toEqual(kept.ziel);
|
expect(karte.snapshot().ziele).toEqual(kept.ziele);
|
||||||
expect(karte.snapshot().center).toEqual({ easting: 420_000, northing: 5_520_000 });
|
expect(karte.snapshot().center).toEqual({ easting: 420_000, northing: 5_520_000 });
|
||||||
expect(karte.snapshot().koordinate).toEqual(kept.koordinate);
|
expect(karte.snapshot().koordinate).toEqual(kept.koordinate);
|
||||||
|
|
||||||
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "670300", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "670300", hochwert: "78109" });
|
||||||
expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben.");
|
expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben.");
|
||||||
expect(karte.snapshot().ziel).toEqual(kept.ziel);
|
expect(karte.snapshot().ziele).toEqual(kept.ziele);
|
||||||
|
expect(karte.snapshot().koordinate).toEqual(kept.koordinate);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rejects an impossible Gitter", async () => {
|
test("rejects an impossible Gitter", async () => {
|
||||||
@@ -323,31 +311,62 @@ describe("naming a Ziel", () => {
|
|||||||
const kept = karte.snapshot();
|
const kept = karte.snapshot();
|
||||||
karte.enterKoordinate({ gitter: "32U II", rechtswert: "67030", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U II", rechtswert: "67030", hochwert: "78109" });
|
||||||
expect(karte.snapshot().notice).toBe("Dieses Gitter ist keine gültige Koordinate.");
|
expect(karte.snapshot().notice).toBe("Dieses Gitter ist keine gültige Koordinate.");
|
||||||
expect(karte.snapshot().ziel).toEqual(kept.ziel);
|
expect(karte.snapshot().ziele).toEqual(kept.ziele);
|
||||||
expect(karte.snapshot().center).toEqual(kept.center);
|
expect(karte.snapshot().center).toEqual(kept.center);
|
||||||
|
expect(karte.snapshot().koordinate).toEqual(kept.koordinate);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("rejects a Koordinate no Blatt covers, including a hole between Blätter", async () => {
|
test("rejects a Koordinate no Blatt covers, including a hole between Blätter", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: withHole });
|
const { karte } = await openSheet({ uebersicht: withHole });
|
||||||
karte.enterKoordinate(sample);
|
karte.enterKoordinate(sample);
|
||||||
const kept = karte.snapshot();
|
const kept = karte.snapshot();
|
||||||
expect(kept.ziel).not.toBeNull();
|
expect(kept.ziele).toHaveLength(1);
|
||||||
|
|
||||||
karte.panTo({ easting: 465_000, northing: 5_580_000 });
|
karte.panTo({ easting: 465_000, northing: 5_580_000 });
|
||||||
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "75000", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "75000", hochwert: "78109" });
|
||||||
expect(karte.snapshot().notice).toBe("Kein Blatt deckt diese Koordinate.");
|
expect(karte.snapshot().notice).toBe("Kein Blatt deckt diese Koordinate.");
|
||||||
expect(karte.snapshot().ziel).toEqual(kept.ziel);
|
expect(karte.snapshot().ziele).toEqual(kept.ziele);
|
||||||
expect(karte.snapshot().center).toEqual({ easting: 465_000, northing: 5_580_000 });
|
expect(karte.snapshot().center).toEqual({ easting: 465_000, northing: 5_580_000 });
|
||||||
expect(karte.snapshot().resolution).toBe(kept.resolution);
|
expect(karte.snapshot().resolution).toBe(kept.resolution);
|
||||||
|
expect(karte.snapshot().koordinate).toEqual(kept.koordinate);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a second Koordinate replaces the Ziel, and there is only one", async () => {
|
test("a valid Koordinate clears the previous refusal", async () => {
|
||||||
|
const { karte } = await openSheet({ uebersicht: wideSquare, online: true });
|
||||||
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "6703", hochwert: "78109" });
|
||||||
|
expect(karte.snapshot().notice).toBe("Rechtswert und Hochwert müssen genau fünf Ziffern haben.");
|
||||||
|
karte.enterKoordinate(sample);
|
||||||
|
expect(karte.snapshot().notice).toBeNull();
|
||||||
|
expect(karte.snapshot().ziele).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a repeated Koordinate is refused and changes nothing", async () => {
|
||||||
|
const { karte } = await openSheet({ uebersicht: wideSquare });
|
||||||
|
karte.enterKoordinate({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" });
|
||||||
|
const kept = karte.snapshot();
|
||||||
|
karte.panTo({ easting: 420_000, northing: 5_520_000 });
|
||||||
|
karte.zoomTo(10);
|
||||||
|
karte.enterKoordinate({ gitter: "32UMA", rechtswert: "67030", hochwert: "78109" });
|
||||||
|
const snapshot = karte.snapshot();
|
||||||
|
expect(snapshot.notice).toBe("Diese Koordinate ist schon ein Ziel.");
|
||||||
|
expect(snapshot.ziele).toEqual(kept.ziele);
|
||||||
|
expect(snapshot.center).toEqual({ easting: 420_000, northing: 5_520_000 });
|
||||||
|
expect(snapshot.resolution).toBe(10);
|
||||||
|
expect(snapshot.koordinate).toEqual(kept.koordinate);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a second Koordinate adds another Ziel, newest first", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: withHole });
|
const { karte } = await openSheet({ uebersicht: withHole });
|
||||||
karte.enterKoordinate(sample);
|
karte.enterKoordinate(sample);
|
||||||
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" });
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" });
|
||||||
const snapshot = karte.snapshot();
|
const snapshot = karte.snapshot();
|
||||||
expect(Math.abs(snapshot.ziel!.easting - 481_000)).toBeLessThan(1);
|
expect(snapshot.ziele.map((ziel) => ziel.koordinate)).toEqual([
|
||||||
|
"32U MA 81000 78109",
|
||||||
|
"32U MA 67030 78109",
|
||||||
|
]);
|
||||||
|
expect(Math.abs(snapshot.ziele[0].point.easting - 481_000)).toBeLessThan(1);
|
||||||
expect(snapshot.resolution).toBe(7);
|
expect(snapshot.resolution).toBe(7);
|
||||||
|
expect(snapshot.center).toEqual(snapshot.ziele[0].point);
|
||||||
expect(snapshot.koordinate).toEqual({
|
expect(snapshot.koordinate).toEqual({
|
||||||
gitter: "32U MA",
|
gitter: "32U MA",
|
||||||
rechtswert: "81000",
|
rechtswert: "81000",
|
||||||
@@ -355,21 +374,64 @@ describe("naming a Ziel", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("leaving the page drops the Ziel and keeps the Cache marks", async () => {
|
test("deleteZiel removes that Ziel and leaves the view", async () => {
|
||||||
|
const { karte } = await openSheet({ uebersicht: withHole });
|
||||||
|
karte.enterKoordinate(sample);
|
||||||
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" });
|
||||||
|
karte.panTo({ easting: 465_000, northing: 5_580_000 });
|
||||||
|
karte.zoomTo(10);
|
||||||
|
karte.deleteZiel("32U MA 67030 78109");
|
||||||
|
const snapshot = karte.snapshot();
|
||||||
|
expect(snapshot.ziele.map((ziel) => ziel.koordinate)).toEqual(["32U MA 81000 78109"]);
|
||||||
|
expect(snapshot.center).toEqual({ easting: 465_000, northing: 5_580_000 });
|
||||||
|
expect(snapshot.resolution).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("leaving the page keeps the Ziele and the Cache marks", async () => {
|
||||||
|
const cache = createMemoryCache();
|
||||||
|
const marks = createMemoryMarks();
|
||||||
|
const first = await openSheet({ uebersicht: wideSquare, cache, marks });
|
||||||
|
first.karte.enterKoordinate({ gitter: "32u ma", rechtswert: "67030", hochwert: "78109" });
|
||||||
|
first.karte.enterKoordinate({ gitter: "32UMA", rechtswert: "81000", hochwert: "78109" });
|
||||||
|
first.karte.reportStandort({ easting: 420_000, northing: 5_520_000 });
|
||||||
|
expect(first.karte.snapshot().ziele.map((ziel) => ziel.koordinate)).toEqual([
|
||||||
|
"32U MA 81000 78109",
|
||||||
|
"32U MA 67030 78109",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const kept = await openSheet({ uebersicht: wideSquare, cache, marks });
|
||||||
|
expect(kept.karte.snapshot().ziele.map((ziel) => ziel.koordinate)).toEqual([
|
||||||
|
"32U MA 81000 78109",
|
||||||
|
"32U MA 67030 78109",
|
||||||
|
]);
|
||||||
|
expect(Math.abs(kept.karte.snapshot().ziele[1].point.easting - 467_030)).toBeLessThan(1);
|
||||||
|
expect(kept.karte.snapshot().ziele[0].entfernung).toBeNull();
|
||||||
|
expect(kept.karte.snapshot().standort).toBeNull();
|
||||||
|
expect(kept.karte.snapshot().koordinate).toEqual({
|
||||||
|
gitter: "32U MA",
|
||||||
|
rechtswert: "",
|
||||||
|
hochwert: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleting the Cache leaves the Ziele in place", async () => {
|
||||||
const cache = createMemoryCache();
|
const cache = createMemoryCache();
|
||||||
const marks = createMemoryMarks();
|
const marks = createMemoryMarks();
|
||||||
const first = await openSheet({ uebersicht: wideSquare, cache, marks });
|
const first = await openSheet({ uebersicht: wideSquare, cache, marks });
|
||||||
first.karte.enterKoordinate(sample);
|
first.karte.enterKoordinate(sample);
|
||||||
first.karte.startGebiet("Hessen");
|
first.karte.deleteCache();
|
||||||
expect(first.karte.snapshot().ziel).not.toBeNull();
|
expect(first.karte.snapshot().ziele.map((ziel) => ziel.koordinate)).toEqual([
|
||||||
|
"32U MA 67030 78109",
|
||||||
|
]);
|
||||||
|
|
||||||
const second = await openSheet({ uebersicht: wideSquare, cache, marks });
|
const second = await openSheet({ uebersicht: wideSquare, cache, marks });
|
||||||
expect(second.karte.snapshot().ziel).toBeNull();
|
expect(second.karte.snapshot().ziele.map((ziel) => ziel.koordinate)).toEqual([
|
||||||
expect(hessen(second.karte.snapshot()).heruntergeladen).toBe(true);
|
"32U MA 67030 78109",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Standort and distance", () => {
|
describe("Standort and Entfernung", () => {
|
||||||
test("a late first Standort centers the view once", async () => {
|
test("a late first Standort centers the view once", async () => {
|
||||||
const { karte } = await openSheet();
|
const { karte } = await openSheet();
|
||||||
expect(karte.snapshot().center).toEqual({ easting: 150, northing: 50 });
|
expect(karte.snapshot().center).toEqual({ easting: 150, northing: 50 });
|
||||||
@@ -424,24 +486,26 @@ describe("Standort and distance", () => {
|
|||||||
expect(karte.snapshot().center).toEqual({ easting: 5, northing: 5 });
|
expect(karte.snapshot().center).toEqual({ easting: 5, northing: 5 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("centers on the Standort and on the Ziel at 1:25,000", async () => {
|
test("centers on the Standort and on one Ziel at 1:25,000", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: wideSquare });
|
const { karte } = await openSheet({ uebersicht: withHole });
|
||||||
const before = karte.snapshot();
|
const before = karte.snapshot();
|
||||||
karte.centerOnStandort();
|
karte.centerOnStandort();
|
||||||
karte.centerOnZiel();
|
karte.centerOnZiel("32U MA 67030 78109");
|
||||||
expect(karte.snapshot().center).toEqual(before.center);
|
expect(karte.snapshot().center).toEqual(before.center);
|
||||||
expect(karte.snapshot().resolution).toBe(before.resolution);
|
expect(karte.snapshot().resolution).toBe(before.resolution);
|
||||||
|
|
||||||
karte.enterKoordinate(sample);
|
karte.enterKoordinate(sample);
|
||||||
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" });
|
||||||
|
const older = karte.snapshot().ziele[1].point;
|
||||||
karte.reportStandort({
|
karte.reportStandort({
|
||||||
easting: karte.snapshot().ziel!.easting + 100,
|
easting: older.easting + 100,
|
||||||
northing: karte.snapshot().ziel!.northing,
|
northing: older.northing,
|
||||||
});
|
});
|
||||||
karte.zoomTo(1.25);
|
karte.zoomTo(1.25);
|
||||||
karte.panTo({ easting: 410_000, northing: 5_510_000 });
|
karte.panTo({ easting: 465_000, northing: 5_580_000 });
|
||||||
karte.centerOnZiel();
|
karte.centerOnZiel("32U MA 67030 78109");
|
||||||
expect(karte.snapshot().resolution).toBe(7);
|
expect(karte.snapshot().resolution).toBe(7);
|
||||||
expect(karte.snapshot().center).toEqual(karte.snapshot().ziel);
|
expect(karte.snapshot().center).toEqual(older);
|
||||||
|
|
||||||
karte.zoomTo(1.25);
|
karte.zoomTo(1.25);
|
||||||
karte.centerOnStandort();
|
karte.centerOnStandort();
|
||||||
@@ -449,32 +513,64 @@ describe("Standort and distance", () => {
|
|||||||
expect(karte.snapshot().center).toEqual(karte.snapshot().standort);
|
expect(karte.snapshot().center).toEqual(karte.snapshot().standort);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows metres below 1000 m and kilometres from 1000 m, and hides a missing end", async () => {
|
test("shows metres below 1000 m and kilometres from 1000 m, and hides a missing Standort", async () => {
|
||||||
const { karte } = await openSheet({ uebersicht: wideSquare, online: false });
|
const { karte } = await openSheet({ uebersicht: withHole, online: false });
|
||||||
expect(karte.snapshot().distance).toBeNull();
|
|
||||||
karte.enterKoordinate(sample);
|
karte.enterKoordinate(sample);
|
||||||
expect(karte.snapshot().distance).toBeNull();
|
karte.enterKoordinate({ gitter: "32U MA", rechtswert: "81000", hochwert: "78109" });
|
||||||
|
expect(karte.snapshot().ziele.every((ziel) => ziel.entfernung === null)).toBe(true);
|
||||||
|
|
||||||
const ziel = karte.snapshot().ziel!;
|
const nearer = karte.snapshot().ziele[0].point;
|
||||||
karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 500 });
|
const keptCenter = karte.snapshot().center;
|
||||||
expect(karte.snapshot().distance).toEqual({ metres: 500, text: "500 m" });
|
karte.reportStandort({ easting: nearer.easting, northing: nearer.northing + 500 });
|
||||||
|
expect(karte.snapshot().ziele[0].entfernung).toEqual({ metres: 500, text: "500 m" });
|
||||||
|
expect(karte.snapshot().ziele[1].entfernung?.text).not.toBe("500 m");
|
||||||
|
expect(karte.snapshot().center).toEqual(keptCenter);
|
||||||
|
|
||||||
karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 999 });
|
karte.reportStandort({ easting: nearer.easting, northing: nearer.northing + 999 });
|
||||||
expect(karte.snapshot().distance?.text).toBe("999 m");
|
expect(karte.snapshot().ziele[0].entfernung?.text).toBe("999 m");
|
||||||
|
|
||||||
karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 1000 });
|
karte.reportStandort({ easting: nearer.easting, northing: nearer.northing + 1000 });
|
||||||
expect(karte.snapshot().distance?.text).toBe("1 km");
|
expect(karte.snapshot().ziele[0].entfernung?.text).toBe("1 km");
|
||||||
|
|
||||||
karte.reportStandort({ easting: ziel.easting, northing: ziel.northing + 1500 });
|
karte.reportStandort({ easting: nearer.easting, northing: nearer.northing + 1500 });
|
||||||
expect(karte.snapshot().distance?.text).toBe("1,5 km");
|
expect(karte.snapshot().ziele[0].entfernung?.text).toBe("1,5 km");
|
||||||
|
|
||||||
karte.reportStandort({ easting: ziel.easting + 3000, northing: ziel.northing + 4000 });
|
karte.reportStandort({ easting: nearer.easting + 3000, northing: nearer.northing + 4000 });
|
||||||
expect(karte.snapshot().distance).toEqual({ metres: 5000, text: "5 km" });
|
expect(karte.snapshot().ziele[0].entfernung).toEqual({ metres: 5000, text: "5 km" });
|
||||||
expect(karte.snapshot().center).toEqual(ziel);
|
expect(karte.snapshot().ziele[1].entfernung?.metres).not.toBe(5000);
|
||||||
|
expect(karte.snapshot().center).toEqual(keptCenter);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("downloading and removing Hessen", () => {
|
describe("downloading Hessen", () => {
|
||||||
|
test("reports how many Hessen Kacheln are already in the Cache", async () => {
|
||||||
|
const { karte } = await openSheet();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 0, total: 3 });
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a stored heruntergeladen name does not fetch Hessen", async () => {
|
||||||
|
const marks = createMemoryMarks(["Hessen"]);
|
||||||
|
const { karte, clock } = await openSheet({ viewport: oneTile, marks });
|
||||||
|
karte.zoomTo(10);
|
||||||
|
karte.panTo({ easting: 250, northing: 50 });
|
||||||
|
karte.setOnline(true);
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
karte.setOnline(false);
|
||||||
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 1, total: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a Kachel already in the Cache counts as arrived Hessen", async () => {
|
||||||
|
const { karte } = await openSheet({ cache: createMemoryCache([TILE_A]) });
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 1, total: 3 });
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
async function view(easting: number, viewport: Viewport = oneTile) {
|
async function view(easting: number, viewport: Viewport = oneTile) {
|
||||||
const opened = await openSheet({ viewport });
|
const opened = await openSheet({ viewport });
|
||||||
opened.karte.zoomTo(10);
|
opened.karte.zoomTo(10);
|
||||||
@@ -483,141 +579,203 @@ describe("downloading and removing Hessen", () => {
|
|||||||
return opened;
|
return opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
test("starting Hessen marks it heruntergeladen and fetches the current view first", async () => {
|
test("starting Hessen fetches only the missing Kacheln, current view first", async () => {
|
||||||
const { karte, clock } = await view(250);
|
const { karte, clock } = await view(250);
|
||||||
karte.startGebiet("Hessen");
|
karte.startHessen();
|
||||||
const started = hessen(karte.snapshot());
|
expect(karte.snapshot().hessen).toEqual({ arrived: 0, total: 3 });
|
||||||
expect(started.heruntergeladen).toBe(true);
|
expect(karte.snapshot().visit).toEqual({ arrived: 0, total: 3 });
|
||||||
expect(started.arrived).toBe(0);
|
|
||||||
expect(started.total).toBe(3);
|
|
||||||
|
|
||||||
await clock.tick();
|
await clock.tick();
|
||||||
expect(appearance(karte, TILE_C)).toBe("drawn");
|
expect(appearance(karte, TILE_C)).toBe("drawn");
|
||||||
expect(hessen(karte.snapshot()).arrived).toBe(1);
|
expect(karte.snapshot().visit).toEqual({ arrived: 1, total: 3 });
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 1, total: 3 });
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
expect(appearance(karte, TILE_A)).not.toBe("drawn");
|
expect(appearance(karte, TILE_A)).not.toBe("drawn");
|
||||||
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("drawn");
|
|
||||||
expect(hessen(karte.snapshot()).arrived).toBe(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Kacheln already in the Cache stay usable while Hessen is still downloading", async () => {
|
test("a finished Hessen fill leaves no visit progress", async () => {
|
||||||
const { karte } = await openSheet({
|
const { karte, clock } = await view(250);
|
||||||
|
karte.startHessen();
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 3, total: 3 });
|
||||||
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("drawn");
|
||||||
|
karte.panTo({ easting: 150, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_B)).toBe("drawn");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a second Hessen start while that fill is unfinished adds nothing", async () => {
|
||||||
|
const { karte, clock } = await view(250);
|
||||||
|
karte.startHessen();
|
||||||
|
await clock.tick();
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 1, total: 3 });
|
||||||
|
karte.startHessen();
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 1, total: 3 });
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 3, total: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("closing the app stops the Hessen fill and opening does not continue it", async () => {
|
||||||
|
const { karte, clock } = await view(250);
|
||||||
|
karte.startHessen();
|
||||||
|
karte.setAppOpen(false);
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
||||||
|
|
||||||
|
karte.setAppOpen(true);
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("drawn");
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
karte.setOnline(false);
|
||||||
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
||||||
|
|
||||||
|
karte.setOnline(true);
|
||||||
|
karte.startHessen();
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 0, total: 2 });
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("drawn");
|
||||||
|
await clock.tick();
|
||||||
|
karte.panTo({ easting: 150, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_B)).toBe("drawn");
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("looking around fetches only the missing Kacheln of the current view", async () => {
|
||||||
|
const { karte, clock } = await view(250);
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 1, total: 3 });
|
||||||
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-downloading");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("losing the network pauses the Hessen fill and regaining it continues", async () => {
|
||||||
|
const { karte, clock } = await view(250);
|
||||||
|
karte.startHessen();
|
||||||
|
await clock.tick();
|
||||||
|
karte.setOnline(false);
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("drawn");
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 1, total: 3 });
|
||||||
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 1, total: 3 });
|
||||||
|
karte.setOnline(true);
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("blank-downloading");
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_A)).toBe("drawn");
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 2, total: 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("starting Hessen fetches every prepared scale, the current scale first", async () => {
|
||||||
|
const opened = await openSheet({
|
||||||
|
viewport: oneTile,
|
||||||
|
uebersicht: {
|
||||||
|
tileSizePx: 10,
|
||||||
|
resolutions: [10, 20],
|
||||||
|
blaetter: [{ minE: 0, minN: 0, maxE: 100, maxN: 100 }],
|
||||||
|
gebiete: [{ name: "Hessen", covers: "all" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
opened.karte.zoomTo(10);
|
||||||
|
opened.karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
opened.karte.setOnline(true);
|
||||||
|
expect(opened.karte.snapshot().hessen).toEqual({ arrived: 0, total: 2 });
|
||||||
|
opened.karte.startHessen();
|
||||||
|
expect(opened.karte.snapshot().visit).toEqual({ arrived: 0, total: 2 });
|
||||||
|
await opened.clock.tick();
|
||||||
|
expect(appearance(opened.karte, "0/0/0")).toBe("drawn");
|
||||||
|
opened.karte.zoomTo(20);
|
||||||
|
expect(appearance(opened.karte, "1/0/0")).not.toBe("drawn");
|
||||||
|
await opened.clock.tick();
|
||||||
|
expect(appearance(opened.karte, "1/0/0")).toBe("drawn");
|
||||||
|
expect(opened.karte.snapshot().visit).toBeNull();
|
||||||
|
expect(opened.karte.snapshot().hessen).toEqual({ arrived: 2, total: 2 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("looking around fetches the current view before the rest of Hessen", async () => {
|
||||||
|
const opened = await openSheet({
|
||||||
|
viewport: oneTile,
|
||||||
|
uebersicht: {
|
||||||
|
tileSizePx: 10,
|
||||||
|
resolutions: [10, 20],
|
||||||
|
blaetter: [{ minE: 0, minN: 0, maxE: 300, maxN: 100 }],
|
||||||
|
gebiete: [{ name: "Hessen", covers: "all" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
opened.karte.zoomTo(10);
|
||||||
|
opened.karte.panTo({ easting: 50, northing: 50 });
|
||||||
|
opened.karte.setOnline(true);
|
||||||
|
opened.karte.startHessen();
|
||||||
|
opened.karte.panTo({ easting: 250, northing: 50 });
|
||||||
|
await opened.clock.tick();
|
||||||
|
await opened.clock.tick();
|
||||||
|
expect(appearance(opened.karte, "0/2/0")).toBe("drawn");
|
||||||
|
opened.karte.setOnline(false);
|
||||||
|
opened.karte.zoomTo(20);
|
||||||
|
expect(appearance(opened.karte, "1/1/0")).toBe("blank-not-on-device");
|
||||||
|
opened.karte.zoomTo(10);
|
||||||
|
opened.karte.panTo({ easting: 150, northing: 50 });
|
||||||
|
expect(appearance(opened.karte, "0/1/0")).toBe("blank-not-on-device");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Hessen skips Kacheln already in the Cache and keeps them drawn", async () => {
|
||||||
|
const { karte, clock } = await openSheet({
|
||||||
viewport: threeTiles,
|
viewport: threeTiles,
|
||||||
cache: createMemoryCache([TILE_A]),
|
cache: createMemoryCache([TILE_A]),
|
||||||
online: true,
|
|
||||||
});
|
});
|
||||||
karte.zoomTo(10);
|
karte.zoomTo(10);
|
||||||
karte.panTo({ easting: 150, northing: 50 });
|
karte.panTo({ easting: 150, northing: 50 });
|
||||||
karte.startGebiet("Hessen");
|
karte.setOnline(true);
|
||||||
|
karte.startHessen();
|
||||||
expect(appearance(karte, TILE_A)).toBe("drawn");
|
expect(appearance(karte, TILE_A)).toBe("drawn");
|
||||||
expect(appearance(karte, TILE_B)).toBe("blank-downloading");
|
expect(appearance(karte, TILE_B)).toBe("blank-downloading");
|
||||||
expect(hessen(karte.snapshot()).arrived).toBe(1);
|
expect(karte.snapshot().hessen).toEqual({ arrived: 1, total: 3 });
|
||||||
|
expect(karte.snapshot().visit).toEqual({ arrived: 0, total: 2 });
|
||||||
|
await clock.tick();
|
||||||
|
await clock.tick();
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 3, total: 3 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a dropped connection keeps finished Kacheln and pauses the rest", async () => {
|
test("deleting the Cache leaves the Ziel", async () => {
|
||||||
const { karte, clock } = await view(250);
|
const { karte } = await openSheet({ uebersicht: wideSquare, online: true });
|
||||||
karte.startGebiet("Hessen");
|
karte.enterKoordinate(sample);
|
||||||
await clock.tick();
|
const ziele = karte.snapshot().ziele;
|
||||||
karte.setOnline(false);
|
|
||||||
expect(appearance(karte, TILE_C)).toBe("drawn");
|
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
|
||||||
await clock.tick();
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
|
||||||
karte.setOnline(true);
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("blank-downloading");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("while Hessen is not heruntergeladen, looking around fetches only the current view", async () => {
|
|
||||||
const { karte, clock } = await view(250);
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("blank-downloading");
|
|
||||||
expect(hessen(karte.snapshot()).heruntergeladen).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("removing Hessen stops the rest and keeps a Kachel that still lies in Usingen", async () => {
|
|
||||||
const { karte, clock } = await view(250);
|
|
||||||
await clock.tick();
|
|
||||||
karte.startGebiet("Usingen");
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.startGebiet("Hessen");
|
|
||||||
await clock.tick();
|
|
||||||
karte.setOnline(false);
|
|
||||||
karte.removeGebiet("Hessen");
|
|
||||||
|
|
||||||
expect(hessen(karte.snapshot()).heruntergeladen).toBe(false);
|
|
||||||
expect(karte.snapshot().gebiete.find((gebiet) => gebiet.name === "Usingen")!.heruntergeladen).toBe(
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("drawn");
|
|
||||||
karte.panTo({ easting: 150, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_B)).toBe("drawn");
|
|
||||||
|
|
||||||
karte.setOnline(true);
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.panTo({ easting: 250, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_C)).not.toBe("drawn");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("removing Wehrheim keeps the overlap and drops the Kachel only Wehrheim held", async () => {
|
|
||||||
const { karte, clock } = await openSheet({ viewport: threeTiles, online: true });
|
|
||||||
karte.zoomTo(10);
|
|
||||||
karte.panTo({ easting: 150, northing: 50 });
|
|
||||||
karte.startGebiet("Usingen");
|
|
||||||
karte.startGebiet("Wehrheim");
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.setOnline(false);
|
|
||||||
karte.removeGebiet("Wehrheim");
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("drawn");
|
|
||||||
expect(appearance(karte, TILE_B)).toBe("drawn");
|
|
||||||
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
|
||||||
expect(karte.snapshot().gebiete.find((gebiet) => gebiet.name === "Wehrheim")!.heruntergeladen).toBe(
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deleting the whole Cache clears marks and does not start Hessen again", async () => {
|
|
||||||
const { karte, clock } = await view(250);
|
|
||||||
karte.startGebiet("Hessen");
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
await clock.tick();
|
|
||||||
karte.deleteCache();
|
karte.deleteCache();
|
||||||
expect(hessen(karte.snapshot()).heruntergeladen).toBe(false);
|
expect(karte.snapshot().ziele).toEqual(ziele);
|
||||||
expect(hessen(karte.snapshot()).arrived).toBe(0);
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleting the Cache removes every Kachel and does not start a fetch", async () => {
|
||||||
|
const { karte, clock } = await view(250);
|
||||||
|
karte.startHessen();
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("drawn");
|
||||||
|
karte.deleteCache();
|
||||||
|
expect(karte.snapshot().visit).toBeNull();
|
||||||
|
expect(karte.snapshot().hessen).toEqual({ arrived: 0, total: 3 });
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
||||||
|
await clock.tick();
|
||||||
|
expect(appearance(karte, TILE_C)).toBe("blank-not-on-device");
|
||||||
|
karte.panTo({ easting: 260, northing: 50 });
|
||||||
expect(appearance(karte, TILE_C)).toBe("blank-downloading");
|
expect(appearance(karte, TILE_C)).toBe("blank-downloading");
|
||||||
await clock.tick();
|
await clock.tick();
|
||||||
expect(appearance(karte, TILE_C)).toBe("drawn");
|
expect(appearance(karte, TILE_C)).toBe("drawn");
|
||||||
expect(hessen(karte.snapshot()).heruntergeladen).toBe(false);
|
|
||||||
karte.panTo({ easting: 50, northing: 50 });
|
|
||||||
expect(appearance(karte, TILE_A)).not.toBe("drawn");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Hessen can be started again after it is removed", async () => {
|
|
||||||
const { karte, clock } = await view(50);
|
|
||||||
karte.startGebiet("Hessen");
|
|
||||||
await clock.tick();
|
|
||||||
karte.setOnline(false);
|
karte.setOnline(false);
|
||||||
karte.removeGebiet("Hessen");
|
karte.panTo({ easting: 50, northing: 50 });
|
||||||
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
expect(appearance(karte, TILE_A)).toBe("blank-not-on-device");
|
||||||
karte.setOnline(true);
|
|
||||||
karte.startGebiet("Hessen");
|
|
||||||
expect(hessen(karte.snapshot()).heruntergeladen).toBe(true);
|
|
||||||
await clock.tick();
|
|
||||||
expect(appearance(karte, TILE_A)).toBe("drawn");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+106
-100
@@ -1,7 +1,6 @@
|
|||||||
import { maxResolutionMeters } from "../config";
|
import { maxResolutionMeters } from "../config";
|
||||||
import { parseKoordinate } from "./koordinate";
|
import { parseKoordinate } from "./koordinate";
|
||||||
import type {
|
import type {
|
||||||
GebietStatus,
|
|
||||||
KachelView,
|
KachelView,
|
||||||
KarteOptions,
|
KarteOptions,
|
||||||
KarteSession,
|
KarteSession,
|
||||||
@@ -125,6 +124,15 @@ function formatDistance(metres: number): string {
|
|||||||
return `${kilometres.toFixed(1).replace(".", ",")} km`;
|
return `${kilometres.toFixed(1).replace(".", ",")} km`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entfernungTo(
|
||||||
|
standort: Point | null,
|
||||||
|
point: Point,
|
||||||
|
): { metres: number; text: string } | null {
|
||||||
|
if (!standort) return null;
|
||||||
|
const metres = Math.hypot(point.easting - standort.easting, point.northing - standort.northing);
|
||||||
|
return { metres, text: formatDistance(metres) };
|
||||||
|
}
|
||||||
|
|
||||||
function isAbort(error: unknown): boolean {
|
function isAbort(error: unknown): boolean {
|
||||||
return error instanceof DOMException && error.name === "AbortError";
|
return error instanceof DOMException && error.name === "AbortError";
|
||||||
}
|
}
|
||||||
@@ -140,17 +148,16 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
const stored = new Set(loaded);
|
const stored = new Set(loaded);
|
||||||
const cached = new Set(loaded.filter((id) => catalog.has(id)));
|
const cached = new Set(loaded.filter((id) => catalog.has(id)));
|
||||||
const markState = await marks.read();
|
const markState = await marks.read();
|
||||||
const heruntergeladen = new Set(
|
|
||||||
markState.heruntergeladen.filter((name) =>
|
|
||||||
uebersicht.gebiete.some((gebiet) => gebiet.name === name),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const totals = new Map(uebersicht.gebiete.map((gebiet) => [gebiet.name, 0]));
|
const hessenIds: string[] = [];
|
||||||
for (const entry of catalog.values()) {
|
for (const [id, entry] of catalog) {
|
||||||
for (const name of entry.gebiete) {
|
if (entry.gebiete.includes("Hessen")) hessenIds.push(id);
|
||||||
totals.set(name, (totals.get(name) ?? 0) + 1);
|
}
|
||||||
}
|
|
||||||
|
function hessenArrived(): number {
|
||||||
|
let arrived = 0;
|
||||||
|
for (const id of hessenIds) if (cached.has(id)) arrived += 1;
|
||||||
|
return arrived;
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewport = { ...options.viewport };
|
let viewport = { ...options.viewport };
|
||||||
@@ -160,26 +167,16 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
let seenStandort = standort !== null;
|
let seenStandort = standort !== null;
|
||||||
let personMovedView = false;
|
let personMovedView = false;
|
||||||
let personEnteredZiel = false;
|
let personEnteredZiel = false;
|
||||||
let ziel: Point | null = null;
|
let ziele = restoreZiele(markState.ziele);
|
||||||
let koordinateNotice: string | null = null;
|
let koordinateNotice: string | null = null;
|
||||||
let koordinate = { gitter: "32U MA", rechtswert: "", hochwert: "" };
|
let koordinate = { gitter: "32U MA", rechtswert: "", hochwert: "" };
|
||||||
let queue: string[] = [];
|
let queue: string[] = [];
|
||||||
|
let hessenFill: string[] | null = null;
|
||||||
|
let fetchesHeld = false;
|
||||||
const inFlight = new Map<string, AbortController>();
|
const inFlight = new Map<string, AbortController>();
|
||||||
const failed = new Set<string>();
|
const failed = new Set<string>();
|
||||||
const listeners = new Set<() => void>();
|
const listeners = new Set<() => void>();
|
||||||
|
|
||||||
function arrivedCounts(): Map<string, number> {
|
|
||||||
const counts = new Map(uebersicht.gebiete.map((gebiet) => [gebiet.name, 0]));
|
|
||||||
for (const id of cached) {
|
|
||||||
const entry = catalog.get(id);
|
|
||||||
if (!entry) continue;
|
|
||||||
for (const name of entry.gebiete) {
|
|
||||||
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return counts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(resolution: number): number {
|
function clamp(resolution: number): number {
|
||||||
const maxResolution = maxResolutionMeters(viewport, config);
|
const maxResolution = maxResolutionMeters(viewport, config);
|
||||||
return Math.min(Math.max(resolution, config.minResolutionMeters), maxResolution);
|
return Math.min(Math.max(resolution, config.minResolutionMeters), maxResolution);
|
||||||
@@ -211,8 +208,23 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
for (const listener of listeners) listener();
|
for (const listener of listeners) listener();
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistMarks(): void {
|
function persistZiele(): void {
|
||||||
void marks.write({ heruntergeladen: [...heruntergeladen] });
|
void marks.write({ heruntergeladen: [], ziele: ziele.map((item) => item.koordinate) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreZiele(canonical: string[]): { koordinate: string; point: Point }[] {
|
||||||
|
const restored: { koordinate: string; point: Point }[] = [];
|
||||||
|
for (const koordinate of canonical) {
|
||||||
|
const parts = /^(\d{1,2}[C-HJ-NP-X] [A-HJ-NP-Z]{2}) (\d{5}) (\d{5})$/.exec(koordinate);
|
||||||
|
if (!parts) continue;
|
||||||
|
const parsed = parseKoordinate(
|
||||||
|
{ gitter: parts[1], rechtswert: parts[2], hochwert: parts[3] },
|
||||||
|
(point) => uebersicht.blaetter.some((blatt) => coversPoint(blatt, point)),
|
||||||
|
);
|
||||||
|
if (!parsed.ok || parsed.koordinate !== koordinate) continue;
|
||||||
|
restored.push({ koordinate, point: parsed.point });
|
||||||
|
}
|
||||||
|
return restored;
|
||||||
}
|
}
|
||||||
|
|
||||||
function kachelViews(): KachelView[] {
|
function kachelViews(): KachelView[] {
|
||||||
@@ -228,7 +240,11 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
views.push({ id: cell.id, appearance: "drawn" });
|
views.push({ id: cell.id, appearance: "drawn" });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const willFetch = appOpen && online && !failed.has(cell.id);
|
const willFetch =
|
||||||
|
appOpen &&
|
||||||
|
online &&
|
||||||
|
!failed.has(cell.id) &&
|
||||||
|
(queue.includes(cell.id) || inFlight.has(cell.id));
|
||||||
views.push({
|
views.push({
|
||||||
id: cell.id,
|
id: cell.id,
|
||||||
appearance: willFetch ? "blank-downloading" : "blank-not-on-device",
|
appearance: willFetch ? "blank-downloading" : "blank-not-on-device",
|
||||||
@@ -238,7 +254,15 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
return views;
|
return views;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function releaseFetches(): void {
|
||||||
|
fetchesHeld = false;
|
||||||
|
}
|
||||||
|
|
||||||
function rebuild(): void {
|
function rebuild(): void {
|
||||||
|
if (fetchesHeld) {
|
||||||
|
queue = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rect = viewRect(center, resolution, viewport);
|
const rect = viewRect(center, resolution, viewport);
|
||||||
const scale = scaleIndex(uebersicht.resolutions, resolution);
|
const scale = scaleIndex(uebersicht.resolutions, resolution);
|
||||||
const viewNow = cellsInView(scale, rect, uebersicht.resolutions, uebersicht.tileSizePx)
|
const viewNow = cellsInView(scale, rect, uebersicht.resolutions, uebersicht.tileSizePx)
|
||||||
@@ -247,17 +271,6 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
for (const id of failed) {
|
for (const id of failed) {
|
||||||
if (!viewNow.includes(id)) failed.delete(id);
|
if (!viewNow.includes(id)) failed.delete(id);
|
||||||
}
|
}
|
||||||
const viewAny = new Set<string>();
|
|
||||||
for (let zi = 0; zi < uebersicht.resolutions.length; zi += 1) {
|
|
||||||
for (const cell of cellsInView(zi, rect, uebersicht.resolutions, uebersicht.tileSizePx)) {
|
|
||||||
if (catalog.has(cell.id)) viewAny.add(cell.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const gebietIds: string[] = [];
|
|
||||||
for (const [id, entry] of catalog) {
|
|
||||||
if (cached.has(id)) continue;
|
|
||||||
if (entry.gebiete.some((name) => heruntergeladen.has(name))) gebietIds.push(id);
|
|
||||||
}
|
|
||||||
const ordered: string[] = [];
|
const ordered: string[] = [];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const push = (id: string) => {
|
const push = (id: string) => {
|
||||||
@@ -266,10 +279,9 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
ordered.push(id);
|
ordered.push(id);
|
||||||
};
|
};
|
||||||
for (const id of viewNow) push(id);
|
for (const id of viewNow) push(id);
|
||||||
for (const id of gebietIds) {
|
if (hessenFill) {
|
||||||
if (viewAny.has(id)) push(id);
|
for (const id of hessenFill) push(id);
|
||||||
}
|
}
|
||||||
for (const id of gebietIds) push(id);
|
|
||||||
queue = ordered;
|
queue = ordered;
|
||||||
pump();
|
pump();
|
||||||
}
|
}
|
||||||
@@ -292,6 +304,7 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
cached.add(id);
|
cached.add(id);
|
||||||
stored.add(id);
|
stored.add(id);
|
||||||
await cache.put(id, bytes);
|
await cache.put(id, bytes);
|
||||||
|
if (hessenFill?.every((tile) => cached.has(tile))) hessenFill = null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!isAbort(error)) failed.add(id);
|
if (!isAbort(error)) failed.add(id);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -310,21 +323,6 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function snapshot(): KarteSnapshot {
|
function snapshot(): KarteSnapshot {
|
||||||
const arrived = arrivedCounts();
|
|
||||||
const gebiete: GebietStatus[] = uebersicht.gebiete.map((gebiet) => ({
|
|
||||||
name: gebiet.name,
|
|
||||||
heruntergeladen: heruntergeladen.has(gebiet.name),
|
|
||||||
arrived: arrived.get(gebiet.name) ?? 0,
|
|
||||||
total: totals.get(gebiet.name) ?? 0,
|
|
||||||
}));
|
|
||||||
const distance =
|
|
||||||
ziel && standort
|
|
||||||
? {
|
|
||||||
metres: Math.hypot(ziel.easting - standort.easting, ziel.northing - standort.northing),
|
|
||||||
text: "",
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
if (distance) distance.text = formatDistance(distance.metres);
|
|
||||||
const views = kachelViews();
|
const views = kachelViews();
|
||||||
const missingWhileOffline =
|
const missingWhileOffline =
|
||||||
!online && views.some((kachel) => kachel.appearance === "blank-not-on-device");
|
!online && views.some((kachel) => kachel.appearance === "blank-not-on-device");
|
||||||
@@ -333,18 +331,25 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
center: { ...center },
|
center: { ...center },
|
||||||
resolution,
|
resolution,
|
||||||
northUp: true,
|
northUp: true,
|
||||||
ziel: ziel ? { ...ziel } : null,
|
ziele: ziele.map((item) => ({
|
||||||
|
koordinate: item.koordinate,
|
||||||
|
point: { ...item.point },
|
||||||
|
entfernung: entfernungTo(standort, item.point),
|
||||||
|
})),
|
||||||
standort: standort ? { ...standort } : null,
|
standort: standort ? { ...standort } : null,
|
||||||
distance,
|
|
||||||
notice,
|
notice,
|
||||||
koordinate: { ...koordinate },
|
koordinate: { ...koordinate },
|
||||||
gebiete,
|
hessen: { arrived: hessenArrived(), total: hessenIds.length },
|
||||||
|
visit: visitOf(),
|
||||||
kacheln: views,
|
kacheln: views,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function knownGebiet(name: string): boolean {
|
function visitOf(): { arrived: number; total: number } | null {
|
||||||
return uebersicht.gebiete.some((gebiet) => gebiet.name === name);
|
if (!hessenFill) return null;
|
||||||
|
let arrived = 0;
|
||||||
|
for (const id of hessenFill) if (cached.has(id)) arrived += 1;
|
||||||
|
return { arrived, total: hessenFill.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
const session: KarteSession = {
|
const session: KarteSession = {
|
||||||
@@ -354,18 +359,21 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
return () => listeners.delete(listener);
|
return () => listeners.delete(listener);
|
||||||
},
|
},
|
||||||
setViewport(next) {
|
setViewport(next) {
|
||||||
|
releaseFetches();
|
||||||
viewport = { ...next };
|
viewport = { ...next };
|
||||||
resolution = clamp(resolution);
|
resolution = clamp(resolution);
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
panTo(next) {
|
panTo(next) {
|
||||||
|
releaseFetches();
|
||||||
personMovedView = true;
|
personMovedView = true;
|
||||||
center = { ...next };
|
center = { ...next };
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
zoomTo(next) {
|
zoomTo(next) {
|
||||||
|
releaseFetches();
|
||||||
resolution = clamp(next);
|
resolution = clamp(next);
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
@@ -379,6 +387,7 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
}
|
}
|
||||||
standort = { ...next };
|
standort = { ...next };
|
||||||
if (!seenStandort && !personMovedView && !personEnteredZiel) {
|
if (!seenStandort && !personMovedView && !personEnteredZiel) {
|
||||||
|
releaseFetches();
|
||||||
center = { ...next };
|
center = { ...next };
|
||||||
resolution = clamp(config.openResolutionMeters);
|
resolution = clamp(config.openResolutionMeters);
|
||||||
rebuild();
|
rebuild();
|
||||||
@@ -388,8 +397,11 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
},
|
},
|
||||||
setAppOpen(open) {
|
setAppOpen(open) {
|
||||||
appOpen = open;
|
appOpen = open;
|
||||||
if (!open) abortInFlight(() => true);
|
if (!open) {
|
||||||
else {
|
hessenFill = null;
|
||||||
|
abortInFlight(() => true);
|
||||||
|
} else {
|
||||||
|
releaseFetches();
|
||||||
failed.clear();
|
failed.clear();
|
||||||
rebuild();
|
rebuild();
|
||||||
}
|
}
|
||||||
@@ -399,6 +411,7 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
online = next;
|
online = next;
|
||||||
if (!next) abortInFlight(() => true);
|
if (!next) abortInFlight(() => true);
|
||||||
else {
|
else {
|
||||||
|
releaseFetches();
|
||||||
failed.clear();
|
failed.clear();
|
||||||
rebuild();
|
rebuild();
|
||||||
}
|
}
|
||||||
@@ -413,78 +426,71 @@ export async function openKarte(options: KarteOptions): Promise<KarteSession> {
|
|||||||
publish();
|
publish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (ziele.some((item) => item.koordinate === parsed.koordinate)) {
|
||||||
|
koordinateNotice = "Diese Koordinate ist schon ein Ziel.";
|
||||||
|
publish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
koordinateNotice = null;
|
koordinateNotice = null;
|
||||||
koordinate = {
|
koordinate = {
|
||||||
gitter: input.gitter,
|
gitter: input.gitter,
|
||||||
rechtswert: input.rechtswert,
|
rechtswert: input.rechtswert,
|
||||||
hochwert: input.hochwert,
|
hochwert: input.hochwert,
|
||||||
};
|
};
|
||||||
ziel = parsed.point;
|
ziele = [{ koordinate: parsed.koordinate, point: parsed.point }, ...ziele];
|
||||||
personEnteredZiel = true;
|
personEnteredZiel = true;
|
||||||
|
releaseFetches();
|
||||||
center = { ...parsed.point };
|
center = { ...parsed.point };
|
||||||
resolution = clamp(config.openResolutionMeters);
|
resolution = clamp(config.openResolutionMeters);
|
||||||
|
persistZiele();
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
|
deleteZiel(koordinate) {
|
||||||
|
const next = ziele.filter((item) => item.koordinate !== koordinate);
|
||||||
|
if (next.length === ziele.length) return;
|
||||||
|
ziele = next;
|
||||||
|
persistZiele();
|
||||||
|
publish();
|
||||||
|
},
|
||||||
centerOnStandort() {
|
centerOnStandort() {
|
||||||
if (!standort) return;
|
if (!standort) return;
|
||||||
|
releaseFetches();
|
||||||
personMovedView = true;
|
personMovedView = true;
|
||||||
center = { ...standort };
|
center = { ...standort };
|
||||||
resolution = clamp(config.openResolutionMeters);
|
resolution = clamp(config.openResolutionMeters);
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
centerOnZiel() {
|
centerOnZiel(koordinate) {
|
||||||
if (!ziel) return;
|
const found = ziele.find((item) => item.koordinate === koordinate);
|
||||||
|
if (!found) return;
|
||||||
|
releaseFetches();
|
||||||
personMovedView = true;
|
personMovedView = true;
|
||||||
center = { ...ziel };
|
center = { ...found.point };
|
||||||
resolution = clamp(config.openResolutionMeters);
|
resolution = clamp(config.openResolutionMeters);
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
startGebiet(name) {
|
startHessen() {
|
||||||
if (!knownGebiet(name)) return;
|
if (hessenFill) return;
|
||||||
heruntergeladen.add(name);
|
const missing = hessenIds.filter((id) => !cached.has(id));
|
||||||
persistMarks();
|
if (missing.length === 0) return;
|
||||||
rebuild();
|
releaseFetches();
|
||||||
publish();
|
hessenFill = missing;
|
||||||
},
|
|
||||||
removeGebiet(name) {
|
|
||||||
if (!knownGebiet(name)) return;
|
|
||||||
heruntergeladen.delete(name);
|
|
||||||
const removeIds: string[] = [];
|
|
||||||
for (const id of cached) {
|
|
||||||
const entry = catalog.get(id);
|
|
||||||
if (!entry?.gebiete.includes(name)) continue;
|
|
||||||
const kept = entry.gebiete.some(
|
|
||||||
(gebiet) => gebiet !== name && heruntergeladen.has(gebiet),
|
|
||||||
);
|
|
||||||
if (!kept) removeIds.push(id);
|
|
||||||
}
|
|
||||||
for (const id of removeIds) {
|
|
||||||
cached.delete(id);
|
|
||||||
stored.delete(id);
|
|
||||||
}
|
|
||||||
abortInFlight((id) => {
|
|
||||||
const entry = catalog.get(id);
|
|
||||||
if (!entry?.gebiete.includes(name)) return false;
|
|
||||||
return !entry.gebiete.some((gebiet) => gebiet !== name && heruntergeladen.has(gebiet));
|
|
||||||
});
|
|
||||||
if (removeIds.length > 0) void cache.delete(removeIds);
|
|
||||||
persistMarks();
|
|
||||||
rebuild();
|
rebuild();
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
deleteCache() {
|
deleteCache() {
|
||||||
|
fetchesHeld = true;
|
||||||
|
hessenFill = null;
|
||||||
abortInFlight(() => true);
|
abortInFlight(() => true);
|
||||||
const ids = [...stored];
|
const ids = [...stored];
|
||||||
cached.clear();
|
cached.clear();
|
||||||
stored.clear();
|
stored.clear();
|
||||||
heruntergeladen.clear();
|
|
||||||
failed.clear();
|
failed.clear();
|
||||||
|
queue = [];
|
||||||
void cache.delete(ids);
|
void cache.delete(ids);
|
||||||
persistMarks();
|
|
||||||
rebuild();
|
|
||||||
publish();
|
publish();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+19
-16
@@ -33,30 +33,33 @@ export type KachelAppearance =
|
|||||||
|
|
||||||
export type KachelView = { id: string; appearance: KachelAppearance };
|
export type KachelView = { id: string; appearance: KachelAppearance };
|
||||||
|
|
||||||
export type GebietStatus = {
|
|
||||||
name: string;
|
|
||||||
heruntergeladen: boolean;
|
|
||||||
arrived: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type KoordinateText = {
|
export type KoordinateText = {
|
||||||
gitter: string;
|
gitter: string;
|
||||||
rechtswert: string;
|
rechtswert: string;
|
||||||
hochwert: string;
|
hochwert: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** What the screen shows. Distance text is German (`500 m`, `1,5 km`). */
|
/** Straight-line Entfernung. Text is German (`500 m`, `1,5 km`). */
|
||||||
|
export type Entfernung = { metres: number; text: string };
|
||||||
|
|
||||||
|
/** One named place. `koordinate` is the canonical writing, newest list first. */
|
||||||
|
export type Ziel = {
|
||||||
|
koordinate: string;
|
||||||
|
point: Point;
|
||||||
|
entfernung: Entfernung | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** What the screen shows. */
|
||||||
export type KarteSnapshot = {
|
export type KarteSnapshot = {
|
||||||
center: Point;
|
center: Point;
|
||||||
resolution: number;
|
resolution: number;
|
||||||
northUp: true;
|
northUp: true;
|
||||||
ziel: Point | null;
|
ziele: Ziel[];
|
||||||
standort: Point | null;
|
standort: Point | null;
|
||||||
distance: { metres: number; text: string } | null;
|
|
||||||
notice: string | null;
|
notice: string | null;
|
||||||
koordinate: KoordinateText;
|
koordinate: KoordinateText;
|
||||||
gebiete: GebietStatus[];
|
hessen: { arrived: number; total: number };
|
||||||
|
visit: { arrived: number; total: number } | null;
|
||||||
kacheln: KachelView[];
|
kacheln: KachelView[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,8 +70,8 @@ export interface CachePort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MarksPort {
|
export interface MarksPort {
|
||||||
read(): Promise<{ heruntergeladen: string[] }>;
|
read(): Promise<{ heruntergeladen: string[]; ziele: string[] }>;
|
||||||
write(state: { heruntergeladen: string[] }): Promise<void>;
|
write(state: { heruntergeladen: string[]; ziele: string[] }): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetches Kachel bytes. The session decides when, and stores them in the Cache. */
|
/** Fetches Kachel bytes. The session decides when, and stores them in the Cache. */
|
||||||
@@ -99,9 +102,9 @@ export type KarteSession = {
|
|||||||
setAppOpen(open: boolean): void;
|
setAppOpen(open: boolean): void;
|
||||||
setOnline(online: boolean): void;
|
setOnline(online: boolean): void;
|
||||||
enterKoordinate(input: KoordinateText): void;
|
enterKoordinate(input: KoordinateText): void;
|
||||||
|
deleteZiel(koordinate: string): void;
|
||||||
centerOnStandort(): void;
|
centerOnStandort(): void;
|
||||||
centerOnZiel(): void;
|
centerOnZiel(koordinate: string): void;
|
||||||
startGebiet(name: string): void;
|
startHessen(): void;
|
||||||
removeGebiet(name: string): void;
|
|
||||||
deleteCache(): void;
|
deleteCache(): void;
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-6
@@ -78,11 +78,11 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fold-toggle,
|
.fold-toggle,
|
||||||
.cache-toggle {
|
.einstellungen-toggle {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cache-toggle[aria-expanded="true"] {
|
.einstellungen-toggle[aria-expanded="true"] {
|
||||||
background: #1e3328;
|
background: #1e3328;
|
||||||
color: #e7e1d4;
|
color: #e7e1d4;
|
||||||
}
|
}
|
||||||
@@ -153,6 +153,18 @@ button.secondary {
|
|||||||
color: #1e3328;
|
color: #1e3328;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.hessen-download {
|
||||||
|
background: #b42318;
|
||||||
|
border-color: #b42318;
|
||||||
|
color: #e7e1d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background: #e7e1d4;
|
||||||
|
color: #6f675c;
|
||||||
|
border-color: #c4bbaa;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
@@ -162,16 +174,30 @@ button.secondary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gebiet p,
|
.gebiet p,
|
||||||
.distance,
|
|
||||||
.notice,
|
.notice,
|
||||||
.boot-error {
|
.boot-error {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.distance {
|
.ziel-list {
|
||||||
font-size: 18px;
|
list-style: none;
|
||||||
font-weight: 700;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ziel-list li {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ziel-list .ziel-open {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
|
|||||||
+21
-3
@@ -163,9 +163,8 @@ export function createFieldMap(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const markers = new VectorSource();
|
const markers = new VectorSource();
|
||||||
const ziel = new Feature({ geometry: new OlPoint([0, 0]) });
|
const zielStyle = markStyle(ZIEL);
|
||||||
const standort = new Feature({ geometry: new OlPoint([0, 0]) });
|
const standort = new Feature({ geometry: new OlPoint([0, 0]) });
|
||||||
ziel.setStyle(markStyle(ZIEL));
|
|
||||||
standort.setStyle(markStyle(STANDORT));
|
standort.setStyle(markStyle(STANDORT));
|
||||||
|
|
||||||
const opening = session.snapshot();
|
const opening = session.snapshot();
|
||||||
@@ -268,7 +267,26 @@ export function createFieldMap(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
apply(snapshot) {
|
apply(snapshot) {
|
||||||
place(markers, ziel, snapshot.ziel);
|
const wanted = new Set(snapshot.ziele.map((ziel) => ziel.koordinate));
|
||||||
|
for (const feature of [...markers.getFeatures()]) {
|
||||||
|
if (feature === standort) continue;
|
||||||
|
const id = feature.getId();
|
||||||
|
if (typeof id !== "string" || !wanted.has(id)) markers.removeFeature(feature);
|
||||||
|
}
|
||||||
|
for (const ziel of snapshot.ziele) {
|
||||||
|
const existing = markers.getFeatureById(ziel.koordinate);
|
||||||
|
const geometry = existing?.getGeometry();
|
||||||
|
if (geometry instanceof OlPoint) {
|
||||||
|
geometry.setCoordinates([ziel.point.easting, ziel.point.northing]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const feature = new Feature({
|
||||||
|
geometry: new OlPoint([ziel.point.easting, ziel.point.northing]),
|
||||||
|
});
|
||||||
|
feature.setId(ziel.koordinate);
|
||||||
|
feature.setStyle(zielStyle);
|
||||||
|
markers.addFeature(feature);
|
||||||
|
}
|
||||||
place(markers, standort, snapshot.standort);
|
place(markers, standort, snapshot.standort);
|
||||||
|
|
||||||
const current = map.getView();
|
const current = map.getView();
|
||||||
|
|||||||
+57
-77
@@ -1,9 +1,6 @@
|
|||||||
import type { GebietStatus, KarteSession, KarteSnapshot, Uebersicht } from "../karte/types";
|
import type { KarteSession, KarteSnapshot, Uebersicht } from "../karte/types";
|
||||||
import { createFieldMap } from "./map";
|
import { createFieldMap } from "./map";
|
||||||
|
|
||||||
type ConfirmKind = "hessen" | "cache";
|
|
||||||
type GebietMode = "download" | "remove" | "none";
|
|
||||||
|
|
||||||
export function mountScreen(
|
export function mountScreen(
|
||||||
root: HTMLElement,
|
root: HTMLElement,
|
||||||
session: KarteSession,
|
session: KarteSession,
|
||||||
@@ -20,11 +17,11 @@ export function mountScreen(
|
|||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h1>Karte</h1>
|
<h1>Karte</h1>
|
||||||
<div class="head-actions">
|
<div class="head-actions">
|
||||||
<button type="button" class="secondary fold-toggle" aria-expanded="true" aria-controls="koordinaten">Einklappen</button>
|
<button type="button" class="secondary fold-toggle" aria-expanded="true" aria-controls="ziele">Einklappen</button>
|
||||||
<button type="button" class="secondary cache-toggle" aria-expanded="false" aria-controls="cache">Cache</button>
|
<button type="button" class="secondary einstellungen-toggle" aria-expanded="false" aria-controls="einstellungen">Einstellungen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="koordinaten" class="fold">
|
<div id="ziele" class="fold">
|
||||||
<form>
|
<form>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<label>Gitter <input name="gitter" type="text" autocomplete="off" spellcheck="false" autocapitalize="off" /></label>
|
<label>Gitter <input name="gitter" type="text" autocomplete="off" spellcheck="false" autocapitalize="off" /></label>
|
||||||
@@ -33,16 +30,15 @@ export function mountScreen(
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit">Ziel setzen</button>
|
<button type="submit">Ziel setzen</button>
|
||||||
</form>
|
</form>
|
||||||
|
<ul class="ziel-list"></ul>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="button" class="secondary">Auf Standort</button>
|
<button type="button" class="secondary">Auf Standort</button>
|
||||||
<button type="button" class="secondary">Auf Ziel</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="distance" hidden></p>
|
<div id="einstellungen" class="fold" hidden>
|
||||||
<div id="cache" class="fold" hidden>
|
|
||||||
<div class="gebiet">
|
<div class="gebiet">
|
||||||
<p hidden></p>
|
<p hidden></p>
|
||||||
<button type="button"></button>
|
<button type="button" class="hessen-download">Hessen herunterladen</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="secondary wipe">Ganzen Cache löschen</button>
|
<button type="button" class="secondary wipe">Ganzen Cache löschen</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,17 +57,16 @@ export function mountScreen(
|
|||||||
|
|
||||||
root.append(panel, mapElement, dialog);
|
root.append(panel, mapElement, dialog);
|
||||||
|
|
||||||
const fold = panel.querySelector<HTMLElement>("#koordinaten");
|
const fold = panel.querySelector<HTMLElement>("#ziele");
|
||||||
const foldToggle = panel.querySelector<HTMLButtonElement>(".fold-toggle");
|
const foldToggle = panel.querySelector<HTMLButtonElement>(".fold-toggle");
|
||||||
const cacheFold = panel.querySelector<HTMLElement>("#cache");
|
const einstellungenFold = panel.querySelector<HTMLElement>("#einstellungen");
|
||||||
const cacheToggle = panel.querySelector<HTMLButtonElement>(".cache-toggle");
|
const einstellungenToggle = panel.querySelector<HTMLButtonElement>(".einstellungen-toggle");
|
||||||
const form = panel.querySelector("form");
|
const form = panel.querySelector("form");
|
||||||
const gitter = panel.querySelector<HTMLInputElement>('input[name="gitter"]');
|
const gitter = panel.querySelector<HTMLInputElement>('input[name="gitter"]');
|
||||||
const rechtswert = panel.querySelector<HTMLInputElement>('input[name="rechtswert"]');
|
const rechtswert = panel.querySelector<HTMLInputElement>('input[name="rechtswert"]');
|
||||||
const hochwert = panel.querySelector<HTMLInputElement>('input[name="hochwert"]');
|
const hochwert = panel.querySelector<HTMLInputElement>('input[name="hochwert"]');
|
||||||
const distance = panel.querySelector<HTMLElement>(".distance");
|
const zielList = panel.querySelector<HTMLElement>(".ziel-list");
|
||||||
const standortButton = panel.querySelector<HTMLButtonElement>(".actions button:nth-child(1)");
|
const standortButton = panel.querySelector<HTMLButtonElement>(".actions button");
|
||||||
const zielButton = panel.querySelector<HTMLButtonElement>(".actions button:nth-child(2)");
|
|
||||||
const gebietBlock = panel.querySelector<HTMLElement>(".gebiet");
|
const gebietBlock = panel.querySelector<HTMLElement>(".gebiet");
|
||||||
const gebietText = panel.querySelector<HTMLElement>(".gebiet p");
|
const gebietText = panel.querySelector<HTMLElement>(".gebiet p");
|
||||||
const gebietButton = panel.querySelector<HTMLButtonElement>(".gebiet button");
|
const gebietButton = panel.querySelector<HTMLButtonElement>(".gebiet button");
|
||||||
@@ -85,15 +80,14 @@ export function mountScreen(
|
|||||||
if (
|
if (
|
||||||
!fold ||
|
!fold ||
|
||||||
!foldToggle ||
|
!foldToggle ||
|
||||||
!cacheFold ||
|
!einstellungenFold ||
|
||||||
!cacheToggle ||
|
!einstellungenToggle ||
|
||||||
!form ||
|
!form ||
|
||||||
!gitter ||
|
!gitter ||
|
||||||
!rechtswert ||
|
!rechtswert ||
|
||||||
!hochwert ||
|
!hochwert ||
|
||||||
!distance ||
|
!zielList ||
|
||||||
!standortButton ||
|
!standortButton ||
|
||||||
!zielButton ||
|
|
||||||
!gebietBlock ||
|
!gebietBlock ||
|
||||||
!gebietText ||
|
!gebietText ||
|
||||||
!gebietButton ||
|
!gebietButton ||
|
||||||
@@ -113,8 +107,7 @@ export function mountScreen(
|
|||||||
hochwert.value = opening.koordinate.hochwert;
|
hochwert.value = opening.koordinate.hochwert;
|
||||||
|
|
||||||
const fieldMap = createFieldMap(mapElement, uebersicht, session);
|
const fieldMap = createFieldMap(mapElement, uebersicht, session);
|
||||||
let gebietMode: GebietMode = "none";
|
let pendingWipe = false;
|
||||||
let pending: ConfirmKind | null = null;
|
|
||||||
let kachelKey = kachelnOf(session.snapshot());
|
let kachelKey = kachelnOf(session.snapshot());
|
||||||
|
|
||||||
form.addEventListener("submit", (event) => {
|
form.addEventListener("submit", (event) => {
|
||||||
@@ -130,38 +123,36 @@ export function mountScreen(
|
|||||||
const open = fold.hasAttribute("hidden");
|
const open = fold.hasAttribute("hidden");
|
||||||
fold.toggleAttribute("hidden", !open);
|
fold.toggleAttribute("hidden", !open);
|
||||||
foldToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
foldToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
foldToggle.textContent = open ? "Einklappen" : "Koordinaten";
|
foldToggle.textContent = open ? "Einklappen" : "Ziele";
|
||||||
});
|
});
|
||||||
|
|
||||||
cacheToggle.addEventListener("click", () => {
|
einstellungenToggle.addEventListener("click", () => {
|
||||||
const open = cacheFold.hasAttribute("hidden");
|
const open = einstellungenFold.hasAttribute("hidden");
|
||||||
cacheFold.toggleAttribute("hidden", !open);
|
einstellungenFold.toggleAttribute("hidden", !open);
|
||||||
cacheToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
einstellungenToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
});
|
});
|
||||||
|
|
||||||
standortButton.addEventListener("click", () => session.centerOnStandort());
|
standortButton.addEventListener("click", () => session.centerOnStandort());
|
||||||
zielButton.addEventListener("click", () => session.centerOnZiel());
|
|
||||||
gebietButton.addEventListener("click", () => {
|
gebietButton.addEventListener("click", () => {
|
||||||
if (gebietMode === "download") session.startGebiet("Hessen");
|
if (gebietButton.disabled) return;
|
||||||
if (gebietMode === "remove") ask("hessen");
|
session.startHessen();
|
||||||
});
|
});
|
||||||
wipeButton.addEventListener("click", () => ask("cache"));
|
wipeButton.addEventListener("click", () => askToWipe());
|
||||||
|
|
||||||
confirmButton.addEventListener("click", () => {
|
confirmButton.addEventListener("click", () => {
|
||||||
const kind = pending;
|
const wipe = pendingWipe;
|
||||||
pending = null;
|
pendingWipe = false;
|
||||||
dialog.close();
|
dialog.close();
|
||||||
if (kind === "hessen") session.removeGebiet("Hessen");
|
if (wipe) session.deleteCache();
|
||||||
if (kind === "cache") session.deleteCache();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dismiss = () => {
|
const dismiss = () => {
|
||||||
pending = null;
|
pendingWipe = false;
|
||||||
if (dialog.open) dialog.close();
|
if (dialog.open) dialog.close();
|
||||||
};
|
};
|
||||||
cancelButton.addEventListener("click", dismiss);
|
cancelButton.addEventListener("click", dismiss);
|
||||||
dialog.addEventListener("cancel", () => {
|
dialog.addEventListener("cancel", () => {
|
||||||
pending = null;
|
pendingWipe = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
@@ -176,12 +167,21 @@ export function mountScreen(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderPanel = (snapshot: KarteSnapshot) => {
|
const renderPanel = (snapshot: KarteSnapshot) => {
|
||||||
if (snapshot.distance) {
|
zielList.replaceChildren();
|
||||||
distance.hidden = false;
|
for (const ziel of snapshot.ziele) {
|
||||||
distance.textContent = `Entfernung ${snapshot.distance.text}`;
|
const row = document.createElement("li");
|
||||||
} else {
|
const open = document.createElement("button");
|
||||||
distance.hidden = true;
|
open.type = "button";
|
||||||
distance.textContent = "";
|
open.className = "secondary ziel-open";
|
||||||
|
open.textContent = ziel.entfernung ? `${ziel.koordinate} ${ziel.entfernung.text}` : ziel.koordinate;
|
||||||
|
open.addEventListener("click", () => session.centerOnZiel(ziel.koordinate));
|
||||||
|
const remove = document.createElement("button");
|
||||||
|
remove.type = "button";
|
||||||
|
remove.className = "secondary";
|
||||||
|
remove.textContent = "Löschen";
|
||||||
|
remove.addEventListener("click", () => session.deleteZiel(ziel.koordinate));
|
||||||
|
row.append(open, remove);
|
||||||
|
zielList.append(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.notice) {
|
if (snapshot.notice) {
|
||||||
@@ -192,46 +192,26 @@ export function mountScreen(
|
|||||||
notice.textContent = "";
|
notice.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHessen(snapshot.gebiete.find((gebiet) => gebiet.name === "Hessen"));
|
const missing = snapshot.hessen.arrived < snapshot.hessen.total;
|
||||||
};
|
|
||||||
|
|
||||||
const renderHessen = (hessen: GebietStatus | undefined) => {
|
|
||||||
if (!hessen) {
|
|
||||||
gebietBlock.hidden = true;
|
|
||||||
gebietMode = "none";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gebietBlock.hidden = false;
|
gebietBlock.hidden = false;
|
||||||
if (!hessen.heruntergeladen) {
|
gebietButton.textContent = "Hessen herunterladen";
|
||||||
|
gebietButton.disabled = !missing;
|
||||||
|
gebietButton.classList.toggle("hessen-download", missing);
|
||||||
|
if (snapshot.visit) {
|
||||||
|
gebietText.hidden = false;
|
||||||
|
gebietText.textContent = `Kacheln: ${snapshot.visit.arrived} von ${snapshot.visit.total}`;
|
||||||
|
} else {
|
||||||
gebietText.hidden = true;
|
gebietText.hidden = true;
|
||||||
gebietText.textContent = "";
|
gebietText.textContent = "";
|
||||||
gebietButton.textContent = "Hessen herunterladen";
|
|
||||||
gebietButton.classList.remove("secondary");
|
|
||||||
gebietMode = "download";
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
gebietMode = "remove";
|
|
||||||
gebietButton.textContent = "Hessen entfernen";
|
|
||||||
gebietButton.classList.add("secondary");
|
|
||||||
gebietText.hidden = false;
|
|
||||||
gebietText.textContent =
|
|
||||||
hessen.arrived === hessen.total
|
|
||||||
? "Hessen heruntergeladen"
|
|
||||||
: `Hessen: ${hessen.arrived} von ${hessen.total} Kacheln`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ask = (kind: ConfirmKind) => {
|
const askToWipe = () => {
|
||||||
if (dialog.open) return;
|
if (dialog.open) return;
|
||||||
pending = kind;
|
pendingWipe = true;
|
||||||
if (kind === "hessen") {
|
question.textContent = "Den ganzen Cache löschen?";
|
||||||
question.textContent = "Hessen entfernen?";
|
explain.textContent = "Alle Kacheln werden gelöscht.";
|
||||||
explain.textContent = "Die Kacheln von Hessen werden gelöscht.";
|
confirmButton.textContent = "Löschen";
|
||||||
confirmButton.textContent = "Entfernen";
|
|
||||||
} else {
|
|
||||||
question.textContent = "Den ganzen Cache löschen?";
|
|
||||||
explain.textContent = "Alle Kacheln werden gelöscht.";
|
|
||||||
confirmButton.textContent = "Löschen";
|
|
||||||
}
|
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user