mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-02-06 00:13:09 +00:00
validate settings
This commit is contained in:
@@ -20,9 +20,8 @@
|
|||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
|
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { customBasemapUpdate, isSelected, remove } from './utils';
|
import { remove } from './utils';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { map } from '$lib/components/map/map';
|
|
||||||
import { dndzone } from 'svelte-dnd-action';
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -129,8 +128,8 @@
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$customLayers[layerId] = layer;
|
|
||||||
addLayer(layerId);
|
addLayer(layerId);
|
||||||
|
$customLayers[layerId] = layer;
|
||||||
selectedLayerId = undefined;
|
selectedLayerId = undefined;
|
||||||
setDataFromSelectedLayer();
|
setDataFromSelectedLayer();
|
||||||
}
|
}
|
||||||
@@ -153,9 +152,7 @@
|
|||||||
return $tree;
|
return $tree;
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($currentBasemap === layerId) {
|
if ($currentBasemap !== layerId) {
|
||||||
$customBasemapUpdate++;
|
|
||||||
} else {
|
|
||||||
$currentBasemap = layerId;
|
$currentBasemap = layerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,14 +168,6 @@
|
|||||||
return $tree;
|
return $tree;
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($map && $currentOverlays && isSelected($currentOverlays, layerId)) {
|
|
||||||
try {
|
|
||||||
$map.removeImport(layerId);
|
|
||||||
} catch (e) {
|
|
||||||
// No reliable way to check if the map is ready to remove sources and layers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentOverlays.update(($overlays) => {
|
currentOverlays.update(($overlays) => {
|
||||||
if (!$overlays.overlays.hasOwnProperty('custom')) {
|
if (!$overlays.overlays.hasOwnProperty('custom')) {
|
||||||
$overlays.overlays['custom'] = {};
|
$overlays.overlays['custom'] = {};
|
||||||
|
|||||||
@@ -167,11 +167,11 @@
|
|||||||
{#if isSelected($selectedOverlayTree, selectedOverlay)}
|
{#if isSelected($selectedOverlayTree, selectedOverlay)}
|
||||||
{#if $isLayerFromExtension(selectedOverlay)}
|
{#if $isLayerFromExtension(selectedOverlay)}
|
||||||
{$getLayerName(selectedOverlay)}
|
{$getLayerName(selectedOverlay)}
|
||||||
|
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
||||||
|
{$customLayers[selectedOverlay].name}
|
||||||
{:else}
|
{:else}
|
||||||
{i18n._(`layers.label.${selectedOverlay}`)}
|
{i18n._(`layers.label.${selectedOverlay}`)}
|
||||||
{/if}
|
{/if}
|
||||||
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
|
|
||||||
{$customLayers[selectedOverlay].name}
|
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
|
|||||||
@@ -76,5 +76,3 @@ export function removeAll(node: LayerTreeType, ids: string[]) {
|
|||||||
});
|
});
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const customBasemapUpdate = writable(0);
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
overlays,
|
overlays,
|
||||||
terrainSources,
|
terrainSources,
|
||||||
} from '$lib/assets/layers';
|
} from '$lib/assets/layers';
|
||||||
import { customBasemapUpdate, getLayers } from '$lib/components/map/layer-control/utils';
|
import { getLayers } from '$lib/components/map/layer-control/utils';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
|
|
||||||
const { currentBasemap, currentOverlays, customLayers, opacities, terrainSource } = settings;
|
const { currentBasemap, currentOverlays, customLayers, opacities, terrainSource } = settings;
|
||||||
@@ -52,10 +52,10 @@ export class StyleManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
currentBasemap.subscribe(() => this.updateBasemap());
|
currentBasemap.subscribe(() => this.updateBasemap());
|
||||||
customBasemapUpdate.subscribe(() => this.updateBasemap());
|
|
||||||
currentOverlays.subscribe(() => this.updateOverlays());
|
currentOverlays.subscribe(() => this.updateOverlays());
|
||||||
opacities.subscribe(() => this.updateOverlays());
|
opacities.subscribe(() => this.updateOverlays());
|
||||||
terrainSource.subscribe(() => this.updateTerrain());
|
terrainSource.subscribe(() => this.updateTerrain());
|
||||||
|
customLayers.subscribe(() => this.updateBasemap());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBasemap() {
|
updateBasemap() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { type Database } from '$lib/db';
|
import { type Database } from '$lib/db';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import {
|
import {
|
||||||
|
basemaps,
|
||||||
defaultBasemap,
|
defaultBasemap,
|
||||||
defaultBasemapTree,
|
defaultBasemapTree,
|
||||||
defaultOpacities,
|
defaultOpacities,
|
||||||
@@ -9,7 +10,10 @@ import {
|
|||||||
defaultOverpassQueries,
|
defaultOverpassQueries,
|
||||||
defaultOverpassTree,
|
defaultOverpassTree,
|
||||||
defaultTerrainSource,
|
defaultTerrainSource,
|
||||||
|
overlays,
|
||||||
|
overpassQueryData,
|
||||||
type CustomLayer,
|
type CustomLayer,
|
||||||
|
type LayerTreeType,
|
||||||
} from '$lib/assets/layers';
|
} from '$lib/assets/layers';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { get, writable, type Writable } from 'svelte/store';
|
import { get, writable, type Writable } from 'svelte/store';
|
||||||
@@ -19,10 +23,12 @@ export class Setting<V> {
|
|||||||
private _subscription: { unsubscribe: () => void } | null = null;
|
private _subscription: { unsubscribe: () => void } | null = null;
|
||||||
private _key: string;
|
private _key: string;
|
||||||
private _value: Writable<V>;
|
private _value: Writable<V>;
|
||||||
|
private _validator?: (value: V) => V;
|
||||||
|
|
||||||
constructor(key: string, initial: V) {
|
constructor(key: string, initial: V, validator?: (value: V) => V) {
|
||||||
this._key = key;
|
this._key = key;
|
||||||
this._value = writable(initial);
|
this._value = writable(initial);
|
||||||
|
this._validator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToDatabase(db: Database) {
|
connectToDatabase(db: Database) {
|
||||||
@@ -36,6 +42,9 @@ export class Setting<V> {
|
|||||||
this._value.set(value);
|
this._value.set(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this._validator) {
|
||||||
|
value = this._validator(value);
|
||||||
|
}
|
||||||
this._value.set(value);
|
this._value.set(value);
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
@@ -73,11 +82,13 @@ export class SettingInitOnFirstRead<V> {
|
|||||||
private _key: string;
|
private _key: string;
|
||||||
private _value: Writable<V | undefined>;
|
private _value: Writable<V | undefined>;
|
||||||
private _initial: V;
|
private _initial: V;
|
||||||
|
private _validator?: (value: V) => V;
|
||||||
|
|
||||||
constructor(key: string, initial: V) {
|
constructor(key: string, initial: V, validator?: (value: V) => V) {
|
||||||
this._key = key;
|
this._key = key;
|
||||||
this._value = writable(undefined);
|
this._value = writable(undefined);
|
||||||
this._initial = initial;
|
this._initial = initial;
|
||||||
|
this._validator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToDatabase(db: Database) {
|
connectToDatabase(db: Database) {
|
||||||
@@ -93,6 +104,9 @@ export class SettingInitOnFirstRead<V> {
|
|||||||
this._value.set(value);
|
this._value.set(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this._validator) {
|
||||||
|
value = this._validator(value);
|
||||||
|
}
|
||||||
this._value.set(value);
|
this._value.set(value);
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
@@ -128,37 +142,166 @@ export class SettingInitOnFirstRead<V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getValueValidator<V>(allowed: V[], fallback: V) {
|
||||||
|
const dict = new Set<V>(allowed);
|
||||||
|
return (value: V) => (dict.has(value) ? value : fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrayValidator<V>(allowed: V[]) {
|
||||||
|
const dict = new Set<V>(allowed);
|
||||||
|
return (value: V[]) => value.filter((v) => dict.has(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLayerValidator(allowed: Record<string, any>, fallback: string) {
|
||||||
|
return (layer: string) =>
|
||||||
|
allowed.hasOwnProperty(layer) ||
|
||||||
|
layer.startsWith('custom-') ||
|
||||||
|
layer.startsWith('extension-')
|
||||||
|
? layer
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterLayerTree(t: LayerTreeType, allowed: Record<string, any>): LayerTreeType {
|
||||||
|
const filtered: LayerTreeType = {};
|
||||||
|
Object.entries(t).forEach(([key, value]) => {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
filtered[key] = filterLayerTree(value, allowed);
|
||||||
|
} else if (
|
||||||
|
allowed.hasOwnProperty(key) ||
|
||||||
|
key.startsWith('custom-') ||
|
||||||
|
key.startsWith('extension-')
|
||||||
|
) {
|
||||||
|
filtered[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLayerTreeValidator(allowed: Record<string, any>) {
|
||||||
|
return (value: LayerTreeType) => filterLayerTree(value, allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
type DistanceUnits = 'metric' | 'imperial' | 'nautical';
|
||||||
|
type VelocityUnits = 'speed' | 'pace';
|
||||||
|
type TemperatureUnits = 'celsius' | 'fahrenheit';
|
||||||
|
type AdditionalDataset = 'speed' | 'hr' | 'cad' | 'atemp' | 'power';
|
||||||
|
type ElevationFill = 'slope' | 'surface' | undefined;
|
||||||
|
type RoutingProfile =
|
||||||
|
| 'bike'
|
||||||
|
| 'racing_bike'
|
||||||
|
| 'gravel_bike'
|
||||||
|
| 'mountain_bike'
|
||||||
|
| 'foot'
|
||||||
|
| 'motorcycle'
|
||||||
|
| 'water'
|
||||||
|
| 'railway';
|
||||||
|
type TerrainSource = 'maptiler-dem' | 'mapterhorn';
|
||||||
|
type StreetViewSource = 'mapillary' | 'google';
|
||||||
|
|
||||||
export const settings = {
|
export const settings = {
|
||||||
distanceUnits: new Setting<'metric' | 'imperial' | 'nautical'>('distanceUnits', 'metric'),
|
distanceUnits: new Setting<DistanceUnits>(
|
||||||
velocityUnits: new Setting<'speed' | 'pace'>('velocityUnits', 'speed'),
|
'distanceUnits',
|
||||||
temperatureUnits: new Setting<'celsius' | 'fahrenheit'>('temperatureUnits', 'celsius'),
|
'metric',
|
||||||
|
getValueValidator<DistanceUnits>(['metric', 'imperial', 'nautical'], 'metric')
|
||||||
|
),
|
||||||
|
velocityUnits: new Setting<VelocityUnits>(
|
||||||
|
'velocityUnits',
|
||||||
|
'speed',
|
||||||
|
getValueValidator<VelocityUnits>(['speed', 'pace'], 'speed')
|
||||||
|
),
|
||||||
|
temperatureUnits: new Setting<TemperatureUnits>(
|
||||||
|
'temperatureUnits',
|
||||||
|
'celsius',
|
||||||
|
getValueValidator<TemperatureUnits>(['celsius', 'fahrenheit'], 'celsius')
|
||||||
|
),
|
||||||
elevationProfile: new Setting<boolean>('elevationProfile', true),
|
elevationProfile: new Setting<boolean>('elevationProfile', true),
|
||||||
additionalDatasets: new Setting<string[]>('additionalDatasets', []),
|
additionalDatasets: new Setting<AdditionalDataset[]>(
|
||||||
elevationFill: new Setting<'slope' | 'surface' | undefined>('elevationFill', undefined),
|
'additionalDatasets',
|
||||||
|
[],
|
||||||
|
getArrayValidator<AdditionalDataset>(['speed', 'hr', 'cad', 'atemp', 'power'])
|
||||||
|
),
|
||||||
|
elevationFill: new Setting<ElevationFill>(
|
||||||
|
'elevationFill',
|
||||||
|
undefined,
|
||||||
|
getValueValidator(['slope', 'surface', undefined], undefined)
|
||||||
|
),
|
||||||
treeFileView: new Setting<boolean>('fileView', false),
|
treeFileView: new Setting<boolean>('fileView', false),
|
||||||
minimizeRoutingMenu: new Setting('minimizeRoutingMenu', false),
|
minimizeRoutingMenu: new Setting('minimizeRoutingMenu', false),
|
||||||
routing: new Setting('routing', true),
|
routing: new Setting('routing', true),
|
||||||
routingProfile: new Setting('routingProfile', 'bike'),
|
routingProfile: new Setting<RoutingProfile>(
|
||||||
|
'routingProfile',
|
||||||
|
'bike',
|
||||||
|
getValueValidator<RoutingProfile>(
|
||||||
|
[
|
||||||
|
'bike',
|
||||||
|
'racing_bike',
|
||||||
|
'gravel_bike',
|
||||||
|
'mountain_bike',
|
||||||
|
'foot',
|
||||||
|
'motorcycle',
|
||||||
|
'water',
|
||||||
|
'railway',
|
||||||
|
],
|
||||||
|
'bike'
|
||||||
|
)
|
||||||
|
),
|
||||||
privateRoads: new Setting('privateRoads', false),
|
privateRoads: new Setting('privateRoads', false),
|
||||||
currentBasemap: new Setting('currentBasemap', defaultBasemap),
|
currentBasemap: new Setting(
|
||||||
previousBasemap: new Setting('previousBasemap', defaultBasemap),
|
'currentBasemap',
|
||||||
selectedBasemapTree: new Setting('selectedBasemapTree', defaultBasemapTree),
|
defaultBasemap,
|
||||||
currentOverlays: new SettingInitOnFirstRead('currentOverlays', defaultOverlays),
|
getLayerValidator(basemaps, defaultBasemap)
|
||||||
previousOverlays: new Setting('previousOverlays', defaultOverlays),
|
),
|
||||||
selectedOverlayTree: new Setting('selectedOverlayTree', defaultOverlayTree),
|
previousBasemap: new Setting(
|
||||||
|
'previousBasemap',
|
||||||
|
defaultBasemap,
|
||||||
|
getLayerValidator(Object.keys(basemaps), defaultBasemap)
|
||||||
|
),
|
||||||
|
selectedBasemapTree: new Setting(
|
||||||
|
'selectedBasemapTree',
|
||||||
|
defaultBasemapTree,
|
||||||
|
getLayerTreeValidator(basemaps)
|
||||||
|
),
|
||||||
|
currentOverlays: new SettingInitOnFirstRead(
|
||||||
|
'currentOverlays',
|
||||||
|
defaultOverlays,
|
||||||
|
getLayerTreeValidator(overlays)
|
||||||
|
),
|
||||||
|
previousOverlays: new Setting(
|
||||||
|
'previousOverlays',
|
||||||
|
defaultOverlays,
|
||||||
|
getLayerTreeValidator(overlays)
|
||||||
|
),
|
||||||
|
selectedOverlayTree: new Setting(
|
||||||
|
'selectedOverlayTree',
|
||||||
|
defaultOverlayTree,
|
||||||
|
getLayerTreeValidator(overlays)
|
||||||
|
),
|
||||||
currentOverpassQueries: new SettingInitOnFirstRead(
|
currentOverpassQueries: new SettingInitOnFirstRead(
|
||||||
'currentOverpassQueries',
|
'currentOverpassQueries',
|
||||||
defaultOverpassQueries
|
defaultOverpassQueries,
|
||||||
|
getLayerTreeValidator(overpassQueryData)
|
||||||
|
),
|
||||||
|
selectedOverpassTree: new Setting(
|
||||||
|
'selectedOverpassTree',
|
||||||
|
defaultOverpassTree,
|
||||||
|
getLayerTreeValidator(overpassQueryData)
|
||||||
),
|
),
|
||||||
selectedOverpassTree: new Setting('selectedOverpassTree', defaultOverpassTree),
|
|
||||||
opacities: new Setting('opacities', defaultOpacities),
|
opacities: new Setting('opacities', defaultOpacities),
|
||||||
customLayers: new Setting<Record<string, CustomLayer>>('customLayers', {}),
|
customLayers: new Setting<Record<string, CustomLayer>>('customLayers', {}),
|
||||||
customBasemapOrder: new Setting<string[]>('customBasemapOrder', []),
|
customBasemapOrder: new Setting<string[]>('customBasemapOrder', []),
|
||||||
customOverlayOrder: new Setting<string[]>('customOverlayOrder', []),
|
customOverlayOrder: new Setting<string[]>('customOverlayOrder', []),
|
||||||
terrainSource: new Setting('terrainSource', defaultTerrainSource),
|
terrainSource: new Setting<TerrainSource>(
|
||||||
|
'terrainSource',
|
||||||
|
defaultTerrainSource,
|
||||||
|
getValueValidator(['maptiler-dem', 'mapterhorn'], defaultTerrainSource)
|
||||||
|
),
|
||||||
directionMarkers: new Setting('directionMarkers', false),
|
directionMarkers: new Setting('directionMarkers', false),
|
||||||
distanceMarkers: new Setting('distanceMarkers', false),
|
distanceMarkers: new Setting('distanceMarkers', false),
|
||||||
streetViewSource: new Setting('streetViewSource', 'mapillary'),
|
streetViewSource: new Setting<StreetViewSource>(
|
||||||
|
'streetViewSource',
|
||||||
|
'mapillary',
|
||||||
|
getValueValidator<StreetViewSource>(['mapillary', 'google'], 'mapillary')
|
||||||
|
),
|
||||||
fileOrder: new Setting<string[]>('fileOrder', []),
|
fileOrder: new Setting<string[]>('fileOrder', []),
|
||||||
defaultOpacity: new Setting('defaultOpacity', 0.7),
|
defaultOpacity: new Setting('defaultOpacity', 0.7),
|
||||||
defaultWidth: new Setting('defaultWidth', browser && window.innerWidth < 600 ? 8 : 5),
|
defaultWidth: new Setting('defaultWidth', browser && window.innerWidth < 600 ? 8 : 5),
|
||||||
|
|||||||
Reference in New Issue
Block a user