forked from MapComplete/MapComplete
Feature(offline): rework to use a protocol handler instead of a service worker to intercept as service workers don't always work, simplify code, add option to auto-download
This commit is contained in:
parent
ca819cf8a6
commit
44748051dd
11 changed files with 193 additions and 293 deletions
|
|
@ -8,8 +8,6 @@
|
|||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { AreaDescription } from "../../service-worker/OfflineBasemapManager"
|
||||
import { OfflineBasemapManager } from "../../service-worker/OfflineBasemapManager"
|
||||
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
||||
|
|
@ -20,12 +18,15 @@
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { DownloadIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Accordion, AccordionItem } from "flowbite-svelte"
|
||||
import ServiceWorkerStatus from "./ServiceWorkerStatus.svelte"
|
||||
import type { AreaDescription } from "../../Logic/OfflineBasemapManager"
|
||||
import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager"
|
||||
import Checkbox from "../Base/Checkbox.svelte"
|
||||
|
||||
|
||||
export let state: ThemeViewState & SpecialVisualizationState = undefined
|
||||
let focusZ = Math.max(...Object.keys(OfflineBasemapManager.zoomelevels).map(Number))
|
||||
export let autoDownload = state.autoDownloadOfflineBasemap
|
||||
|
||||
let focusZ = Math.max(...Object.keys(OfflineBasemapManager.zoomelevels).map(Number))
|
||||
let map: UIEventSource<MlMap> = new UIEventSource(undefined)
|
||||
let mapProperties: MapProperties = new MapLibreAdaptor(map)
|
||||
state?.showCurrentLocationOn(map)
|
||||
|
|
@ -35,77 +36,52 @@
|
|||
mapProperties.allowRotating.set(false)
|
||||
|
||||
|
||||
const offlineMapManager = new OfflineBasemapManager("https://cache.mapcomplete.org/")
|
||||
let installedMeta: UIEventSource<AreaDescription[]> = new UIEventSource([])
|
||||
|
||||
|
||||
function updateMeta() {
|
||||
offlineMapManager.updateCachedMeta().then(meta => installedMeta.set(meta))
|
||||
const offlineMapManager = OfflineBasemapManager.singleton
|
||||
let installing: Store<ReadonlyMap<string, object>> = offlineMapManager.installing
|
||||
let installed = offlineMapManager.installedAreas
|
||||
let focusTile: Store<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number
|
||||
} | undefined> = mapProperties.location.mapD(location => Tiles.embedded_tile(location.lat, location.lon, focusZ))
|
||||
let focusTileIsInstalled = focusTile.mapD(tile => offlineMapManager.isInstalled(tile), [installed])
|
||||
let focusTileIsInstalling = focusTile.mapD(tile => {
|
||||
const { x, y, z } = tile
|
||||
return installing.data?.has(`${z}-${x}-${y}.pmtiles`)
|
||||
}, [installing])
|
||||
|
||||
async function del(areaDescr: AreaDescription) {
|
||||
await offlineMapManager.deleteArea(areaDescr)
|
||||
}
|
||||
|
||||
updateMeta()
|
||||
async function download() {
|
||||
const tile = focusTile.data
|
||||
await offlineMapManager.autoInstall(tile)
|
||||
}
|
||||
|
||||
let installing = new UIEventSource<string[]>([])
|
||||
|
||||
async function install(tile: AreaDescription) {
|
||||
const key = `${tile.minzoom}-${tile.x}-${tile.y}`
|
||||
installing.set([...installing.data ?? [], key])
|
||||
try {
|
||||
const descr = OfflineBasemapManager.getAreaDescriptionForMapcomplete(key + ".pmtiles")
|
||||
await offlineMapManager.installArea(descr)
|
||||
updateMeta()
|
||||
} catch (e) {
|
||||
installing.set(installing.data.filter(k => k !== key))
|
||||
} finally {
|
||||
installing.set(installing.data.filter(k => k !== key))
|
||||
let focusTileFeature = focusTile.mapD(({ x, y, z }) => {
|
||||
const f = Tiles.asGeojson(z, x, y)
|
||||
f.properties = {
|
||||
id: "center_point_" + z + "_" + x + "_" + y,
|
||||
txt: "Tile " + x + " " + y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let installed: Store<Feature<Polygon>[]> = installedMeta.map(meta =>
|
||||
return [f]
|
||||
})
|
||||
let installedFeature: Store<Feature<Polygon>[]> = installed.map(meta =>
|
||||
(meta ?? [])
|
||||
.map(area => {
|
||||
const f = Tiles.asGeojson(area.minzoom, area.x, area.y)
|
||||
f.properties = {
|
||||
id: area.minzoom + "-" + area.x + "-" + area.y,
|
||||
downloaded: "yes",
|
||||
text: area.name + " " + area.dataVersion + " " + Utils.toHumanByteSize(Number(area.size))
|
||||
text: area.name + " " + new Date(area.dataVersion).toLocaleDateString() + " " + Utils.toHumanByteSize(Number(area.size))
|
||||
}
|
||||
return f
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
let focusTile: Store<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number
|
||||
} | undefined> = mapProperties.location.mapD(location => Tiles.embedded_tile(location.lat, location.lon, focusZ))
|
||||
|
||||
let focusTileIsInstalled = focusTile.mapD(tile => offlineMapManager.isInstalled(tile), [installedMeta])
|
||||
let focusTileIsInstalling = focusTile.mapD(tile => {
|
||||
const { x, y, z } = tile
|
||||
return installing.data?.some(area => area === `${z}-${x}-${y}.pmtiles`)
|
||||
}, [installing])
|
||||
|
||||
async function del(areaDescr: AreaDescription) {
|
||||
await offlineMapManager.deleteArea(areaDescr)
|
||||
updateMeta()
|
||||
}
|
||||
|
||||
async function download() {
|
||||
const areasToInstall = Array.from(offlineMapManager.getInstallCandidates(focusTile.data))
|
||||
for (const area of areasToInstall) {
|
||||
console.log("Attempting to install", area)
|
||||
await install(area)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(installed),
|
||||
features: new StaticFeatureSource(installedFeature),
|
||||
layer: new LayerConfig({
|
||||
id: "downloaded",
|
||||
source: "special",
|
||||
|
|
@ -133,21 +109,11 @@
|
|||
location: ["point", "centroid"],
|
||||
label: "{text}",
|
||||
labelCss: "width: w-min",
|
||||
labelCssClasses: "bg-white rounded px-2"
|
||||
labelCssClasses: "bg-white rounded px-2 items-center flex flex-col"
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
let focusTileFeature = focusTile.mapD(({ x, y, z }) => {
|
||||
const f = Tiles.asGeojson(z, x, y)
|
||||
f.properties = {
|
||||
id: "center_point_" + z + "_" + x + "_" + y,
|
||||
txt: "Tile " + x + " " + y
|
||||
}
|
||||
return [f]
|
||||
})
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(focusTileFeature),
|
||||
layer: new LayerConfig({
|
||||
|
|
@ -170,7 +136,14 @@
|
|||
|
||||
</script>
|
||||
<div class="flex flex-col h-full max-h-leave-room">
|
||||
{#if $installedMeta === undefined}
|
||||
<Checkbox selected={autoDownload}>Automatically download the basemap when browsing around</Checkbox>
|
||||
<div>
|
||||
If checked, MapComplete will automatically download the basemap to the cache for the area.
|
||||
This results in bigger initial data loads, but requires less internet over the long run.
|
||||
|
||||
If you plan to visit a region with less connectivity, you can also select the area you want to download below.
|
||||
</div>
|
||||
{#if $installed === undefined}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="h-full overflow-auto pb-16">
|
||||
|
|
@ -210,9 +183,9 @@
|
|||
<div class="leave-room">
|
||||
|
||||
|
||||
{Utils.toHumanByteSize(Utils.sum($installedMeta.map(area => area.size)))}
|
||||
{Utils.toHumanByteSize(Utils.sum($installed.map(area => area.size)))}
|
||||
<button on:click={() => {
|
||||
installedMeta?.data?.forEach(area => del(area))
|
||||
installed?.data?.forEach(area => del(area))
|
||||
}}>
|
||||
<TrashIcon class="w-6" />
|
||||
Delete all
|
||||
|
|
@ -225,7 +198,7 @@
|
|||
<th>Zoom ranges</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{#each ($installedMeta ?? []) as area }
|
||||
{#each ($installed ?? []) as area }
|
||||
<tr>
|
||||
<td>{area.name}</td>
|
||||
<td>{area.dataVersion}</td>
|
||||
|
|
@ -250,14 +223,6 @@
|
|||
</div>
|
||||
|
||||
</AccordionItem>
|
||||
<AccordionItem paddingDefault="p-2">
|
||||
<div slot="header">
|
||||
Service worker status
|
||||
</div>
|
||||
<div class="leave-room">
|
||||
<ServiceWorkerStatus />
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue