Feature: improve offline data management

This commit is contained in:
Pieter Vander Vennet 2025-08-01 00:44:08 +02:00
parent 6f44fe31d0
commit 77ef3a3572

View file

@ -18,8 +18,9 @@
import ShowDataLayer from "../Map/ShowDataLayer" import ShowDataLayer from "../Map/ShowDataLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
import { DownloadIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid" import { DownloadIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Accordion, AccordionItem } from "flowbite-svelte"
import ServiceWorkerStatus from "./ServiceWorkerStatus.svelte"
export let state: ThemeViewState & SpecialVisualizationState = undefined export let state: ThemeViewState & SpecialVisualizationState = undefined
@ -35,7 +36,7 @@
const offlineMapManager = new OfflineBasemapManager("https://cache.mapcomplete.org/") const offlineMapManager = new OfflineBasemapManager("https://cache.mapcomplete.org/")
let installedMeta: UIEventSource<AreaDescription[]> = new UIEventSource() let installedMeta: UIEventSource<AreaDescription[]> = new UIEventSource([])
function updateMeta() { function updateMeta() {
@ -43,15 +44,7 @@
} }
async function pingServiceWorker() {
const l = window.location
const sw = await Utils.downloadJson(l.protocol + "//" + l.host + "/service-worker/offline-basemapM/update")
console.log("Service worker has data:", sw)
}
updateMeta() updateMeta()
pingServiceWorker()
let installing = new UIEventSource<string[]>([]) let installing = new UIEventSource<string[]>([])
@ -62,7 +55,6 @@
const descr = OfflineBasemapManager.getAreaDescriptionForMapcomplete(key + ".pmtiles") const descr = OfflineBasemapManager.getAreaDescriptionForMapcomplete(key + ".pmtiles")
await offlineMapManager.installArea(descr) await offlineMapManager.installArea(descr)
updateMeta() updateMeta()
pingServiceWorker()
} catch (e) { } catch (e) {
installing.set(installing.data.filter(k => k !== key)) installing.set(installing.data.filter(k => k !== key))
} finally { } finally {
@ -71,13 +63,14 @@
} }
let installed: Store<Feature<Polygon>> = installedMeta.map(meta => let installed: Store<Feature<Polygon>[]> = installedMeta.map(meta =>
(meta ?? []) (meta ?? [])
.map(area => { .map(area => {
const f = Tiles.asGeojson(area.minzoom, area.x, area.y) const f = Tiles.asGeojson(area.minzoom, area.x, area.y)
f.properties = { f.properties = {
id: area.minzoom + "-" + area.x + "-" + area.y, id: area.minzoom + "-" + area.x + "-" + area.y,
downloaded: "yes" downloaded: "yes",
text: area.name + " " + area.dataVersion + " " + Utils.toHumanByteSize(Number(area.size))
} }
return f return f
} }
@ -104,7 +97,7 @@
async function download() { async function download() {
const areasToInstall = Array.from(offlineMapManager.getInstallCandidates(focusTile.data)) const areasToInstall = Array.from(offlineMapManager.getInstallCandidates(focusTile.data))
for (const area: AreaDescription of areasToInstall) { for (const area of areasToInstall) {
console.log("Attempting to install", area) console.log("Attempting to install", area)
await install(area) await install(area)
} }
@ -135,7 +128,14 @@
] ]
} }
}], }],
pointRendering: null pointRendering: [
{
location: ["point", "centroid"],
label: "{text}",
labelCss: "width: w-min",
labelCssClasses: "bg-white rounded px-2"
}
]
}) })
}) })
@ -173,71 +173,101 @@
{#if $installedMeta === undefined} {#if $installedMeta === undefined}
<Loading /> <Loading />
{:else} {:else}
<div class="relative w-full h-3/4"> <div class="h-full overflow-auto pb-16">
<div class="rounded-lg absolute top-0 left-0 h-full w-full">
<MaplibreMap {map} {mapProperties} /> <Accordion class="" inactiveClass="text-black">
</div> <AccordionItem paddingDefault="p-2">
<div class="absolute top-0 left-0 h-full w-full flex flex-col justify-center items-center pointer-events-none"> <div slot="header">Map</div>
<div class="w-16 h-32 mb-16"></div> <div class="relative leave-room">
{#if $focusTileIsInstalling} <div class="rounded-lg absolute top-0 left-0 h-full w-full">
<div class="normal-background rounded-lg"> <MaplibreMap {map} {mapProperties} />
<Loading> </div>
Data is being downloaded <div
</Loading> class="absolute top-0 left-0 h-full w-full flex flex-col justify-center items-center pointer-events-none">
<div class="w-16 h-32 mb-16"></div>
{#if $focusTileIsInstalling}
<div class="normal-background rounded-lg">
<Loading>
Data is being downloaded
</Loading>
</div>
{:else}
<button class="primary pointer-events-auto" on:click={() => download()}
class:disabled={$focusTileIsInstalled}>
<DownloadIcon class="w-8 h-8" />
Download
</button>
{/if}
</div>
</div> </div>
{:else} </AccordionItem>
<button class="primary pointer-events-auto" on:click={() => download()}
class:disabled={$focusTileIsInstalled}>
<DownloadIcon class="w-8 h-8" />
Download
</button>
{/if}
</div>
</div>
<AccordionSingle> <AccordionItem paddingDefault="p-2">
<div slot="header"> <div slot="header">
Offline tile management Offline tile management
</div> </div>
{Utils.toHumanByteSize(Utils.sum($installedMeta.map(area => area.size)))} <div class="leave-room">
<button on:click={() => {
{Utils.toHumanByteSize(Utils.sum($installedMeta.map(area => area.size)))}
<button on:click={() => {
installedMeta?.data?.forEach(area => del(area)) installedMeta?.data?.forEach(area => del(area))
}}> }}>
<TrashIcon class="w-6" /> <TrashIcon class="w-6" />
Delete all Delete all
</button> </button>
<table class="w-full"> <table class="w-full ">
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Map generation date</th> <th>Map generation date</th>
<th>Size</th> <th>Size</th>
<th>Zoom ranges</th> <th>Zoom ranges</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
{#each ($installedMeta ?? []) as area } {#each ($installedMeta ?? []) as area }
<tr> <tr>
<td>{area.name}</td> <td>{area.name}</td>
<td>{area.dataVersion}</td> <td>{area.dataVersion}</td>
<td>{Utils.toHumanByteSize(area.size ?? -1)}</td> <td>{Utils.toHumanByteSize(area.size ?? -1)}</td>
<td>{area.minzoom} <td>{area.minzoom}
{#if area.maxzoom !== undefined} {#if area.maxzoom !== undefined}
- {area.maxzoom} - {area.maxzoom}
{:else} {:else}
and above and above
{/if} {/if}
</td> </td>
<td> <td>
<button on:click={() => del(area)}> <button on:click={() => del(area)}>
<TrashIcon class="w-6" /> <TrashIcon class="w-6" />
Delete this map Delete this map
</button> </button>
</td> </td>
</tr> </tr>
{/each} {/each}
</table> </table>
</AccordionSingle> </div>
</AccordionItem>
<AccordionItem paddingDefault="p-2">
<div slot="header">
Service worker status
</div>
<div class="leave-room">
<ServiceWorkerStatus />
</div>
</AccordionItem>
</Accordion>
</div>
{/if} {/if}
</div> </div>
<style>
.leave-room {
height: calc(100vh - 18rem);
overflow-x: auto;
width: 100%;
color: var(--foreground-color);
}
</style>