MapComplete/src/UI/BigComponents/OfflineManagement.svelte

238 lines
7.8 KiB
Svelte

<script lang="ts">
import type { Map as MlMap } from "maplibre-gl"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
import MaplibreMap from "../Map/MaplibreMap.svelte"
import type { MapProperties } from "../../Models/MapProperties"
import ThemeViewState from "../../Models/ThemeViewState"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Loading from "../Base/Loading.svelte"
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
import { Tiles } from "../../Models/TileRange"
import type { Feature, Polygon } from "geojson"
import ShowDataLayer from "../Map/ShowDataLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { DownloadIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Accordion, AccordionItem } from "flowbite-svelte"
import type { AreaDescription } from "../../Logic/OfflineBasemapManager"
import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager"
import Checkbox from "../Base/Checkbox.svelte"
export let state: ThemeViewState & SpecialVisualizationState = undefined
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)
mapProperties.maxzoom.set(focusZ - 1)
mapProperties.zoom.set(Math.min(focusZ - 1, state.mapProperties.zoom.data))
mapProperties.location.set(state.mapProperties.location.data)
mapProperties.allowRotating.set(false)
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)
}
async function download() {
const tile = focusTile.data
await offlineMapManager.autoInstall(tile)
}
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]
})
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 + " " + new Date(area.dataVersion).toLocaleDateString() + " " + Utils.toHumanByteSize(Number(area.size))
}
return f
}
)
)
new ShowDataLayer(map, {
features: new StaticFeatureSource(installedFeature),
layer: new LayerConfig({
id: "downloaded",
source: "special",
lineRendering: [{
color: "blue",
width: {
mappings: [
{
if: `id!~${focusZ}-.*`,
then: "1"
}
]
},
fillColor: {
mappings: [
{
if: `id!~${focusZ}-.*`,
then: "#00000000"
}
]
}
}],
pointRendering: [
{
location: ["point", "centroid"],
label: "{text}",
labelCss: "width: w-min",
labelCssClasses: "bg-white rounded px-2 items-center flex flex-col"
}
]
})
})
new ShowDataLayer(map, {
features: new StaticFeatureSource(focusTileFeature),
layer: new LayerConfig({
id: "focustile",
source: "special",
lineRendering: [{
color: "black"
}],
pointRendering: [
{
location: ["point", "centroid"],
label: "{txt}",
labelCss: "width: max-content",
labelCssClasses: "bg-white rounded px-2 flex"
}
]
})
})
</script>
<div class="flex flex-col h-full max-h-leave-room">
<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">
<Accordion class="" inactiveClass="text-black">
<AccordionItem paddingDefault="p-2">
<div slot="header">Map</div>
<div class="relative leave-room">
<div class="rounded-lg absolute top-0 left-0 h-full w-full">
<MaplibreMap {map} {mapProperties} />
</div>
<div
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>
</AccordionItem>
<AccordionItem paddingDefault="p-2">
<div slot="header">
Offline tile management
</div>
<div class="leave-room">
{Utils.toHumanByteSize(Utils.sum($installed.map(area => area.size)))}
<button on:click={() => {
installed?.data?.forEach(area => del(area))
}}>
<TrashIcon class="w-6" />
Delete all
</button>
<table class="w-full ">
<tr>
<th>Name</th>
<th>Map generation date</th>
<th>Size</th>
<th>Zoom ranges</th>
<th>Actions</th>
</tr>
{#each ($installed ?? []) as area }
<tr>
<td>{area.name}</td>
<td>{area.dataVersion}</td>
<td>{Utils.toHumanByteSize(area.size ?? -1)}</td>
<td>{area.minzoom}
{#if area.maxzoom !== undefined}
- {area.maxzoom}
{:else}
and above
{/if}
</td>
<td>
<button on:click={() => del(area)}>
<TrashIcon class="w-6" />
Delete this map
</button>
</td>
</tr>
{/each}
</table>
</div>
</AccordionItem>
</Accordion>
</div>
{/if}
</div>
<style>
.leave-room {
height: calc(100vh - 18rem);
overflow-x: auto;
width: 100%;
color: var(--foreground-color);
}
</style>