diff --git a/db/migrations/1733093484_updated_settings.go b/db/migrations/1733093484_updated_settings.go new file mode 100644 index 00000000..1b7ebdc1 --- /dev/null +++ b/db/migrations/1733093484_updated_settings.go @@ -0,0 +1,54 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + // add + new_terrain := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "mvxf9llv", + "name": "terrain", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": [], + "onlyDomains": [] + } + }`), new_terrain); err != nil { + return err + } + collection.Schema.AddField(new_terrain) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("mvxf9llv") + + return dao.SaveCollection(collection) + }) +} diff --git a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte index 3fdbf5e5..8db08aaf 100644 --- a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte +++ b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte @@ -17,6 +17,7 @@ import { createEventDispatcher, onDestroy, onMount } from "svelte"; import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule"; import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control"; + import type { Settings } from "$lib/models/settings"; export let trails: Trail[] = []; export let markers: M.Marker[] = []; @@ -27,6 +28,7 @@ export let showGrid: boolean = false; export let showStyleSwitcher: boolean = true; export let showFullscreen: boolean = false; + export let showTerrain: boolean = false; export let fitBounds: "animate" | "instant" | "off" = "instant"; export let elevationProfileContainer: string | HTMLDivElement | undefined = @@ -194,6 +196,11 @@ const bounds = data[activeTrail] ? (data[activeTrail].bbox as M.LngLatBoundsLike) : getBounds(); + + if (!bounds) { + return; + } + map!.fitBounds(bounds, { animate: animate, padding: { @@ -336,8 +343,6 @@ dispatch("select", trail); const index = trails.findIndex((t) => t.id == trail.id); if (index == -1) { - console.log("here"); - return; } activeTrail = index; @@ -425,30 +430,37 @@ ) ).ElevationProfileControl; - const mapStyles = [ - { - text: "Open Street Maps", - value: "/styles/osm.json", - thumbnail: "https://tile.openstreetmap.org/1/0/0.png", - }, - { - text: "Open Topo Maps", - value: "/styles/otm.json", - thumbnail: "https://tile.opentopomap.org/1/0/0.png", - }, - { - text: "Carto Light", - value: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json", - thumbnail: - "https://basemaps.cartocdn.com/light_all/1/0/0@2x.png", - }, - { - text: "Carto Dark", - value: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json", - thumbnail: - "https://basemaps.cartocdn.com/dark_all/1/0/0@2x.png", - }, - ]; + const mapStyles: { text: string; value: string; thumbnail?: string }[] = + [ + ...(($page.data.settings as Settings).tilesets ?? []).map( + (t) => ({ + text: t.name, + value: t.url, + }), + ), + { + text: "Open Street Maps", + value: "/styles/osm.json", + thumbnail: "https://tile.openstreetmap.org/1/0/0.png", + }, + { + text: "Open Topo Maps", + value: "/styles/otm.json", + thumbnail: "https://tile.opentopomap.org/1/0/0.png", + }, + { + text: "Carto Light", + value: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json", + thumbnail: + "https://basemaps.cartocdn.com/light_all/1/0/0@2x.png", + }, + { + text: "Carto Dark", + value: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json", + thumbnail: + "https://basemaps.cartocdn.com/dark_all/1/0/0@2x.png", + }, + ]; const preferredMapStyleIndex = mapStyles.findIndex( (s) => s.text === localStorage.getItem("layer"), ); @@ -490,7 +502,9 @@ selectedIndex: preferredMapStyleIndex !== -1 ? preferredMapStyleIndex : 0, }); - map.addControl(new M.NavigationControl()); + map.addControl( + new M.NavigationControl({ visualizePitch: showTerrain }), + ); map.addControl( new M.ScaleControl({ maxWidth: 120, @@ -536,10 +550,25 @@ ); } + if (showTerrain) { + map!.addControl( + new M.TerrainControl({ + source: "terrain", + }), + ); + } + map.on("styledata", () => { trails.forEach((t, i) => { addTrailLayer(t, t.id ?? i.toString(), data?.at(i)); }); + + if (showTerrain && $page.data.settings?.terrain) { + map!.addSource("terrain", { + type: "raster-dem", + url: $page.data.settings.terrain, + }); + } }); map.on("moveend", (e) => { diff --git a/web/src/lib/models/settings.ts b/web/src/lib/models/settings.ts index 6131a92e..95ff3236 100644 --- a/web/src/lib/models/settings.ts +++ b/web/src/lib/models/settings.ts @@ -7,6 +7,7 @@ class Settings { location?: { name: string, lat: number, lon: number }; category?: string; tilesets?: {name: string, url: string}[] + terrain?: string; user?: string; constructor( @@ -18,6 +19,7 @@ class Settings { location?: { name: string, lat: number, lon: number } category?: string tilesets?: {name: string, url: string}[] + terrain?: string; } ) { this.unit = unit; @@ -27,6 +29,7 @@ class Settings { this.location = params?.location; this.category = params?.category; this.tilesets = params?.tilesets ?? []; + this.terrain = params?.terrain; } } diff --git a/web/src/lib/util/maplibre_util.ts b/web/src/lib/util/maplibre_util.ts index ae0f3503..01fa5c60 100644 --- a/web/src/lib/util/maplibre_util.ts +++ b/web/src/lib/util/maplibre_util.ts @@ -85,7 +85,7 @@ export function createPopupFromTrail(trail: Trail) { : "/imgs/default_thumbnail.webp"; const popup = new M.Popup({maxWidth: "320px"}); popup.setHTML( - ` + `
  • diff --git a/web/src/lib/vendor/maplibre-elevation-profile/elevationprofile.ts b/web/src/lib/vendor/maplibre-elevation-profile/elevationprofile.ts index 5d3ac4ab..b8df1f6f 100644 --- a/web/src/lib/vendor/maplibre-elevation-profile/elevationprofile.ts +++ b/web/src/lib/vendor/maplibre-elevation-profile/elevationprofile.ts @@ -415,7 +415,6 @@ export class ElevationProfile { Chart.register(...registerables); Chart.register(zoomPlugin); - Chart.register(CrosshairPlugin); this.settings = { ...elevationProfileDefaultOptions, @@ -676,6 +675,7 @@ export class ElevationProfile { }, plugins: [ + CrosshairPlugin, { id: "waypointPlugin", afterDraw: (chart, args, options) => { diff --git a/web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts b/web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts index 24779657..75206818 100644 --- a/web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts +++ b/web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts @@ -7,7 +7,7 @@ import { type ControlPosition, type IControl } from "maplibre-gl"; type MapStyle = { text: string; value: string; - thumbnail: string; + thumbnail?: string; } export type StyleSwitcherControlOptions = { @@ -73,20 +73,30 @@ export class StyleSwitcherControl implements IControl { this.switcherContainer.appendChild(headingDiv) const buttonDiv = document.createElement("ul") - buttonDiv.classList.add("mt-2"); + buttonDiv.classList.add("mt-2", "max-h-64", "overflow-y-scroll"); this.settings.styles.forEach((style, i) => { const styleLi = document.createElement("li"); styleLi.classList.add("flex", "items-center", "gap-x-4", "px-3", "py-2", "cursor-pointer", "hover:bg-menu-item-background-hover") - const styleImg = document.createElement("img"); - styleImg.classList.add("w-12", "h-12", "rounded-md") - styleImg.src = style.thumbnail; + if (style.thumbnail) { + const styleImg = document.createElement("img"); + styleImg.classList.add("w-12", "h-12", "rounded-md") + styleImg.src = style.thumbnail; + styleLi.appendChild(styleImg) + } else { + const styleIconContainer = document.createElement("i"); + styleIconContainer.classList.add("w-12", "h-12", "rounded-md", "bg-blue-200", "flex", "items-center", "justify-center") + const styleIcon = document.createElement("i"); + styleIcon.classList.add("fa", "fa-map-location-dot", "text-xl") + styleIconContainer.appendChild(styleIcon) + styleLi.appendChild(styleIconContainer) + } + const styleName = document.createElement("span"); styleName.classList.add("!text-sm") if (i == this.settings.selectedIndex) { styleName.classList.add("font-semibold") } styleName.textContent = style.text; - styleLi.appendChild(styleImg) styleLi.appendChild(styleName) styleLi.addEventListener("click", () => { diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts index e9923a94..406ecb10 100644 --- a/web/src/routes/+layout.server.ts +++ b/web/src/routes/+layout.server.ts @@ -2,6 +2,7 @@ import '$lib/i18n'; import type { LayoutServerLoad } from './$types'; import { env } from "$env/dynamic/private"; +import type { Settings } from '$lib/models/settings'; export const load: LayoutServerLoad = async ({ locals, url }) => { return { settings: locals.settings, origin: env.ORIGIN } diff --git a/web/src/routes/lists/+page.svelte b/web/src/routes/lists/+page.svelte index 662a6afc..74c53d88 100644 --- a/web/src/routes/lists/+page.svelte +++ b/web/src/routes/lists/+page.svelte @@ -27,7 +27,7 @@ let map: M.Map; let mapWithElevation: MapWithElevationMaplibre; - let markers: any[]; + let markers: M.Marker[]; let showMap: boolean = true; let selectedList: List | null = null; diff --git a/web/src/routes/map/trail/[id]/+page.svelte b/web/src/routes/map/trail/[id]/+page.svelte index 34ccab11..e21ec5c4 100644 --- a/web/src/routes/map/trail/[id]/+page.svelte +++ b/web/src/routes/map/trail/[id]/+page.svelte @@ -15,7 +15,7 @@
    - +
    diff --git a/web/src/routes/settings/display/+page.svelte b/web/src/routes/settings/display/+page.svelte index 2735ed27..e2abcf17 100644 --- a/web/src/routes/settings/display/+page.svelte +++ b/web/src/routes/settings/display/+page.svelte @@ -49,11 +49,14 @@ let customTilesetName: string = ""; let customTilesetURL: string = ""; + let terrainURL: string = ""; onMount(() => { citySearchQuery = settings?.location?.name ?? ""; selectedLanguage = settings?.language || "en"; selectedMapFocus = settings?.mapFocus ?? "trails"; + + terrainURL = settings?.terrain ?? ""; }); async function searchCities(q: string) { @@ -127,6 +130,13 @@ tilesets: settings.tilesets, }); } + + async function handleTerrainAdd() { + await settings_update({ + id: settings!.id, + terrain: terrainURL, + }); + } @@ -189,14 +199,14 @@
    +

    {$_("Terrain")}

    +
    +
    + +
    + +
    {/if}