mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 15:43:25 +00:00
progress
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
<div class="grow relative">
|
||||
<Menu />
|
||||
<Toolbar />
|
||||
<Map class="h-full" />
|
||||
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
|
||||
<LayerControl />
|
||||
<GPXLayers />
|
||||
<Toaster richColors />
|
||||
@@ -34,9 +34,11 @@
|
||||
<ElevationProfile />
|
||||
</div>
|
||||
</div>
|
||||
{#if $verticalFileView}
|
||||
<FileList orientation="vertical" recursive={true} class="w-60" />
|
||||
{/if}
|
||||
<div class="shrink-0">
|
||||
{#if $verticalFileView}
|
||||
<FileList orientation="vertical" recursive={true} class="w-60" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
|
@@ -21,7 +21,7 @@
|
||||
easing: () => 1
|
||||
};
|
||||
|
||||
const { distanceUnits } = settings;
|
||||
const { distanceUnits, verticalFileView } = settings;
|
||||
let scaleControl = new mapboxgl.ScaleControl({
|
||||
unit: $distanceUnits
|
||||
});
|
||||
@@ -116,6 +116,10 @@
|
||||
$: if ($map) {
|
||||
scaleControl.setUnit($distanceUnits);
|
||||
}
|
||||
|
||||
$: if ($map && !$verticalFileView) {
|
||||
$map.resize();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div {...$$restProps}>
|
||||
@@ -205,11 +209,11 @@
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
div :global(.mapboxgl-ctrl-bottom-left) {
|
||||
.horizontal :global(.mapboxgl-ctrl-bottom-left) {
|
||||
@apply bottom-[42px];
|
||||
}
|
||||
|
||||
div :global(.mapboxgl-ctrl-bottom-right) {
|
||||
.horizontal :global(.mapboxgl-ctrl-bottom-right) {
|
||||
@apply bottom-[42px];
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,8 @@
|
||||
Thermometer,
|
||||
Sun,
|
||||
Moon,
|
||||
Rows3
|
||||
Rows3,
|
||||
Layers3
|
||||
} from 'lucide-svelte';
|
||||
|
||||
import {
|
||||
@@ -31,18 +32,18 @@
|
||||
exportAllFiles,
|
||||
exportSelectedFiles,
|
||||
triggerFileInput,
|
||||
selectFiles,
|
||||
createFile
|
||||
} from '$lib/stores';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { selectAll, selection } from '$lib/components/file-list/Selection';
|
||||
import { derived } from 'svelte/store';
|
||||
import { canUndo, canRedo, dbUtils, fileObservers, settings } from '$lib/db';
|
||||
import { anySelectedLayer } from '$lib/components/layer-control/utils';
|
||||
import { defaultOverlays } from '$lib/assets/layers';
|
||||
import LayerControlSettings from '$lib/components/layer-control/LayerControlSettings.svelte';
|
||||
|
||||
import { resetMode, setMode, systemPrefersMode } from 'mode-watcher';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { anySelectedLayer } from './layer-control/utils';
|
||||
import { defaultOverlays } from '$lib/assets/layers';
|
||||
|
||||
const {
|
||||
distanceUnits,
|
||||
@@ -88,6 +89,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let layerSettingsOpen = false;
|
||||
</script>
|
||||
|
||||
<div class="absolute top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
|
||||
@@ -147,7 +150,7 @@
|
||||
<Shortcut key="Z" ctrl={true} shift={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item on:click={() => $selectFiles.selectAllFiles()}>
|
||||
<Menubar.Item on:click={selectAll}>
|
||||
<span class="w-4 mr-1"></span>
|
||||
{$_('menu.select_all')}
|
||||
<Shortcut key="A" ctrl={true} />
|
||||
@@ -265,6 +268,11 @@
|
||||
</Menubar.RadioGroup>
|
||||
</Menubar.SubContent>
|
||||
</Menubar.Sub>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item on:click={() => (layerSettingsOpen = true)}>
|
||||
<Layers3 size="16" class="mr-1" />
|
||||
{$_('menu.layers')}
|
||||
</Menubar.Item>
|
||||
</Menubar.Content>
|
||||
</Menubar.Menu>
|
||||
</Menubar.Root>
|
||||
@@ -283,6 +291,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LayerControlSettings bind:open={layerSettingsOpen} />
|
||||
|
||||
<svelte:window
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'n' && (e.metaKey || e.ctrlKey)) {
|
||||
@@ -315,7 +325,7 @@
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
|
||||
$selectFiles.selectAllFiles();
|
||||
selectAll();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'F1') {
|
||||
switchBasemaps();
|
||||
|
@@ -20,6 +20,6 @@
|
||||
scrollbarYClasses={orientation === 'vertical' ? '' : ''}
|
||||
>
|
||||
<div class="flex {orientation === 'vertical' ? 'flex-col' : 'flex-row'} {$$props.class ?? ''}">
|
||||
<FileListNode node={$fileObservers} item={new ListRootItem()} />
|
||||
<FileListNode bind:node={$fileObservers} item={new ListRootItem()} />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
import type { Readable } from 'svelte/store';
|
||||
import FileListNodeContent from './FileListNodeContent.svelte';
|
||||
import FileListNodeLabel from './FileListNodeLabel.svelte';
|
||||
import { getContext } from 'svelte';
|
||||
import { afterUpdate, getContext } from 'svelte';
|
||||
import { type ListItem, type ListTrackItem } from './FileList';
|
||||
|
||||
export let node:
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
let recursive = getContext<boolean>('recursive');
|
||||
|
||||
let label =
|
||||
$: label =
|
||||
node instanceof GPXFile
|
||||
? node.metadata.name
|
||||
: node instanceof Track
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { GPXFile, Track, Waypoint, type AnyGPXTreeElement, type GPXTreeElement } from 'gpx';
|
||||
import { afterUpdate, getContext, onDestroy, onMount } from 'svelte';
|
||||
import Sortable from 'sortablejs/Sortable';
|
||||
import { settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { fileObservers, settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { get, type Readable } from 'svelte/store';
|
||||
import FileListNodeStore from './FileListNodeStore.svelte';
|
||||
import FileListNode from './FileListNode.svelte';
|
||||
@@ -50,16 +50,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
const { fileOrder } = settings;
|
||||
function syncFileOrder() {
|
||||
if (sortableLevel !== 'file') {
|
||||
if (!sortable || sortableLevel !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
/*Object.keys(buttons).forEach((fileId) => {
|
||||
if (!get(fileObservers).has(fileId)) {
|
||||
delete buttons[fileId];
|
||||
}
|
||||
});*/
|
||||
if ($fileOrder.length !== $fileObservers.size) {
|
||||
// Files were added or removed
|
||||
fileOrder.set(sortable.toArray());
|
||||
return;
|
||||
}
|
||||
|
||||
const currentOrder = sortable.toArray();
|
||||
if (currentOrder.length !== $fileOrder.length) {
|
||||
@@ -74,8 +75,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const { fileOrder } = settings;
|
||||
|
||||
onMount(() => {
|
||||
sortable = Sortable.create(container, {
|
||||
group: {
|
||||
@@ -109,13 +108,19 @@
|
||||
});
|
||||
});
|
||||
|
||||
$: if ($fileOrder && sortable) {
|
||||
$: if ($fileOrder) {
|
||||
syncFileOrder();
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
syncFileOrder();
|
||||
// TODO: update selection if files are removed
|
||||
if (sortableLevel === 'file') {
|
||||
syncFileOrder();
|
||||
Object.keys(elements).forEach((fileId) => {
|
||||
if (!get(fileObservers).has(fileId)) {
|
||||
delete elements[fileId];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const unsubscribe = selection.subscribe(($selection) => {
|
||||
|
@@ -4,6 +4,21 @@ import { fileObservers } from "$lib/db";
|
||||
|
||||
export const selection = writable<SelectionTreeType>(new SelectionTreeType(new ListRootItem()));
|
||||
|
||||
export function select(fileId: string) {
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
$selection.set(new ListFileItem(fileId), true);
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function addSelect(fileId: string) {
|
||||
selection.update(($selection) => {
|
||||
$selection.toggle(new ListFileItem(fileId));
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function selectAll() {
|
||||
selection.update(($selection) => {
|
||||
get(fileObservers).forEach((_file, fileId) => {
|
||||
|
95
website/src/lib/components/gpx-layer/DistanceMarkers.ts
Normal file
95
website/src/lib/components/gpx-layer/DistanceMarkers.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
import { settings } from "$lib/db";
|
||||
import { gpxStatistics } from "$lib/stores";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
const { distanceMarkers, distanceUnits } = settings;
|
||||
|
||||
export class DistanceMarkers {
|
||||
map: mapboxgl.Map;
|
||||
updateBinded: () => void = this.update.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map) {
|
||||
this.map = map;
|
||||
|
||||
gpxStatistics.subscribe(this.updateBinded);
|
||||
distanceMarkers.subscribe(this.updateBinded);
|
||||
distanceUnits.subscribe(() => {
|
||||
if (get(distanceMarkers)) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
try {
|
||||
if (get(distanceMarkers)) {
|
||||
let distanceSource = this.map.getSource('distance-markers');
|
||||
if (distanceSource) {
|
||||
distanceSource.setData(this.getDistanceMarkersGeoJSON());
|
||||
} else {
|
||||
this.map.addSource('distance-markers', {
|
||||
type: 'geojson',
|
||||
data: this.getDistanceMarkersGeoJSON()
|
||||
});
|
||||
}
|
||||
if (!this.map.getLayer('distance-markers')) {
|
||||
this.map.addLayer({
|
||||
id: 'distance-markers',
|
||||
type: 'symbol',
|
||||
source: 'distance-markers',
|
||||
layout: {
|
||||
'text-field': ['get', 'distance'],
|
||||
'text-size': 12,
|
||||
'text-font': ['Open Sans Regular'],
|
||||
'icon-image': ['get', 'icon'],
|
||||
'icon-padding': 50,
|
||||
'icon-allow-overlap': true,
|
||||
},
|
||||
paint: {
|
||||
'text-halo-width': 0.1,
|
||||
'text-halo-color': 'black'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.map.moveLayer('distance-markers');
|
||||
}
|
||||
} else {
|
||||
if (this.map.getLayer('distance-markers')) {
|
||||
this.map.removeLayer('distance-markers');
|
||||
}
|
||||
}
|
||||
} catch (e) { // No reliable way to check if the map is ready to add sources and layers
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
|
||||
let statistics = get(gpxStatistics);
|
||||
|
||||
let features = [];
|
||||
let currentTargetDistance = 1;
|
||||
for (let i = 0; i < statistics.local.distance.length; i++) {
|
||||
if (statistics.local.distance[i] >= currentTargetDistance * (get(distanceUnits) === 'metric' ? 1 : 1.60934)) {
|
||||
let distance = currentTargetDistance.toFixed(0);
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [statistics.local.points[i].getLongitude(), statistics.local.points[i].getLatitude()]
|
||||
},
|
||||
properties: {
|
||||
distance,
|
||||
icon: distance.length < 3 ? 'circle-white-2' : 'circle-white-3'
|
||||
}
|
||||
} as GeoJSON.Feature);
|
||||
currentTargetDistance += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
import { map, selectFiles, currentTool, Tool } from "$lib/stores";
|
||||
import { map, currentTool, Tool } from "$lib/stores";
|
||||
import { settings, type GPXFileWithStatistics } from "$lib/db";
|
||||
import { get, type Readable } from "svelte/store";
|
||||
import mapboxgl from "mapbox-gl";
|
||||
import { currentWaypoint, waypointPopup } from "./WaypointPopup";
|
||||
import { addSelect, select, selection } from "$lib/components/file-list/Selection";
|
||||
import { ListSegmentItem, type ListItem, ListFileItem, ListTrackItem } from "$lib/components/file-list/FileList";
|
||||
|
||||
let defaultWeight = 6;
|
||||
let defaultWeight = 5;
|
||||
let defaultOpacity = 1;
|
||||
|
||||
const colors = [
|
||||
@@ -37,7 +39,7 @@ function decrementColor(color: string) {
|
||||
colorCount[color]--;
|
||||
}
|
||||
|
||||
const { directionMarkers, distanceMarkers, distanceUnits } = settings;
|
||||
const { directionMarkers } = settings;
|
||||
|
||||
export class GPXLayer {
|
||||
map: mapboxgl.Map;
|
||||
@@ -45,6 +47,7 @@ export class GPXLayer {
|
||||
file: Readable<GPXFileWithStatistics | undefined>;
|
||||
layerColor: string;
|
||||
markers: mapboxgl.Marker[] = [];
|
||||
selected: ListItem[] = [];
|
||||
unsubscribe: Function[] = [];
|
||||
|
||||
updateBinded: () => void = this.update.bind(this);
|
||||
@@ -56,13 +59,17 @@ export class GPXLayer {
|
||||
this.file = file;
|
||||
this.layerColor = getColor();
|
||||
this.unsubscribe.push(file.subscribe(this.updateBinded));
|
||||
this.unsubscribe.push(directionMarkers.subscribe(this.updateBinded));
|
||||
this.unsubscribe.push(distanceMarkers.subscribe(this.updateBinded));
|
||||
this.unsubscribe.push(distanceUnits.subscribe(() => {
|
||||
if (get(distanceMarkers)) {
|
||||
this.unsubscribe.push(selection.subscribe($selection => {
|
||||
let selected = $selection.getChild(fileId)?.getSelected() || [];
|
||||
if (selected.length !== this.selected.length || selected.some((item, index) => item !== this.selected[index])) {
|
||||
this.selected = selected;
|
||||
this.update();
|
||||
if (this.selected.length > 0) {
|
||||
this.moveToFront();
|
||||
}
|
||||
}
|
||||
}));
|
||||
this.unsubscribe.push(directionMarkers.subscribe(this.updateBinded));
|
||||
|
||||
this.map.on('style.load', this.updateBinded);
|
||||
}
|
||||
@@ -74,7 +81,6 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
let source = this.map.getSource(this.fileId);
|
||||
if (source) {
|
||||
source.setData(this.getGeoJSON());
|
||||
@@ -132,43 +138,6 @@ export class GPXLayer {
|
||||
this.map.removeLayer(this.fileId + '-direction');
|
||||
}
|
||||
}
|
||||
|
||||
if (get(distanceMarkers)) {
|
||||
let distanceSource = this.map.getSource(this.fileId + '-distance');
|
||||
if (distanceSource) {
|
||||
distanceSource.setData(this.getDistanceMarkersGeoJSON());
|
||||
} else {
|
||||
this.map.addSource(this.fileId + '-distance', {
|
||||
type: 'geojson',
|
||||
data: this.getDistanceMarkersGeoJSON()
|
||||
});
|
||||
}
|
||||
if (!this.map.getLayer(this.fileId + '-distance')) {
|
||||
this.map.addLayer({
|
||||
id: this.fileId + '-distance',
|
||||
type: 'symbol',
|
||||
source: this.fileId + '-distance',
|
||||
layout: {
|
||||
'text-field': ['get', 'distance'],
|
||||
'text-size': 12,
|
||||
'text-font': ['Open Sans Regular'],
|
||||
'icon-image': ['get', 'icon'],
|
||||
'icon-padding': 50,
|
||||
'icon-allow-overlap': true,
|
||||
},
|
||||
paint: {
|
||||
'text-halo-width': 0.1,
|
||||
'text-halo-color': 'black'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.map.moveLayer(this.fileId + '-distance');
|
||||
}
|
||||
} else {
|
||||
if (this.map.getLayer(this.fileId + '-distance')) {
|
||||
this.map.removeLayer(this.fileId + '-distance');
|
||||
}
|
||||
}
|
||||
} catch (e) { // No reliable way to check if the map is ready to add sources and layers
|
||||
return;
|
||||
}
|
||||
@@ -212,9 +181,6 @@ export class GPXLayer {
|
||||
if (this.map.getLayer(this.fileId + '-direction')) {
|
||||
this.map.removeLayer(this.fileId + '-direction');
|
||||
}
|
||||
if (this.map.getLayer(this.fileId + '-distance')) {
|
||||
this.map.removeLayer(this.fileId + '-distance');
|
||||
}
|
||||
if (this.map.getLayer(this.fileId)) {
|
||||
this.map.removeLayer(this.fileId);
|
||||
}
|
||||
@@ -238,9 +204,6 @@ export class GPXLayer {
|
||||
if (this.map.getLayer(this.fileId + '-direction')) {
|
||||
this.map.moveLayer(this.fileId + '-direction');
|
||||
}
|
||||
if (this.map.getLayer(this.fileId + '-distance')) {
|
||||
this.map.moveLayer(this.fileId + '-distance');
|
||||
}
|
||||
}
|
||||
|
||||
selectOnClick(e: any) {
|
||||
@@ -248,9 +211,9 @@ export class GPXLayer {
|
||||
return;
|
||||
}
|
||||
if (e.originalEvent.shiftKey) {
|
||||
get(selectFiles).addSelect(this.fileId);
|
||||
addSelect(this.fileId);
|
||||
} else {
|
||||
get(selectFiles).select(this.fileId);
|
||||
select(this.fileId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +227,8 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
let data = file.toGeoJSON();
|
||||
|
||||
let trackIndex = 0, segmentIndex = 0;
|
||||
for (let feature of data.features) {
|
||||
if (!feature.properties) {
|
||||
feature.properties = {};
|
||||
@@ -277,43 +242,17 @@ export class GPXLayer {
|
||||
if (!feature.properties.opacity) {
|
||||
feature.properties.opacity = defaultOpacity;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
if (get(selection).has(new ListFileItem(this.fileId)) || get(selection).has(new ListTrackItem(this.fileId, trackIndex)) || get(selection).has(new ListSegmentItem(this.fileId, trackIndex, segmentIndex))) {
|
||||
feature.properties.weight = feature.properties.weight + 2;
|
||||
}
|
||||
|
||||
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
|
||||
let statistics = get(this.file)?.statistics;
|
||||
if (!statistics) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
};
|
||||
}
|
||||
|
||||
let features = [];
|
||||
let currentTargetDistance = 1;
|
||||
for (let i = 0; i < statistics.local.distance.length; i++) {
|
||||
if (statistics.local.distance[i] >= currentTargetDistance * (get(distanceUnits) === 'metric' ? 1 : 1.60934)) {
|
||||
let distance = currentTargetDistance.toFixed(0);
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [statistics.local.points[i].getLongitude(), statistics.local.points[i].getLatitude()]
|
||||
},
|
||||
properties: {
|
||||
distance,
|
||||
icon: distance.length < 3 ? 'circle-white-2' : 'circle-white-3'
|
||||
}
|
||||
} as GeoJSON.Feature);
|
||||
currentTargetDistance += 1;
|
||||
segmentIndex++;
|
||||
if (segmentIndex >= file.trk[trackIndex].trkseg.length) {
|
||||
segmentIndex = 0;
|
||||
trackIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
};
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,9 @@
|
||||
import { get } from 'svelte/store';
|
||||
import WaypointPopup from './WaypointPopup.svelte';
|
||||
import { fileObservers } from '$lib/db';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { DistanceMarkers } from './DistanceMarkers';
|
||||
|
||||
let distanceMarkers: DistanceMarkers;
|
||||
|
||||
$: if ($map && $fileObservers) {
|
||||
gpxLayers.update(($layers) => {
|
||||
@@ -25,13 +27,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
$: $selection.forEach((item) => {
|
||||
let fileId = item.getFileId();
|
||||
// TODO move more precise selection to front?
|
||||
if ($gpxLayers.has(fileId)) {
|
||||
$gpxLayers.get(fileId)?.moveToFront();
|
||||
}
|
||||
});
|
||||
$: if ($map) {
|
||||
distanceMarkers = new DistanceMarkers(get(map));
|
||||
}
|
||||
</script>
|
||||
|
||||
<WaypointPopup />
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import CustomControl from '$lib/components/custom-control/CustomControl.svelte';
|
||||
import LayerTree from './LayerTree.svelte';
|
||||
import LayerControlSettings from './LayerControlSettings.svelte';
|
||||
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
@@ -121,10 +120,6 @@
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<Separator class="w-full" />
|
||||
<div class="p-2">
|
||||
<LayerControlSettings />
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
@@ -1,29 +1,23 @@
|
||||
<script lang="ts">
|
||||
import LayerTree from './LayerTree.svelte';
|
||||
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
import * as Sheet from '$lib/components/ui/sheet';
|
||||
import * as Accordion from '$lib/components/ui/accordion';
|
||||
|
||||
import { Settings } from 'lucide-svelte';
|
||||
|
||||
import { basemapTree, overlayTree } from '$lib/assets/layers';
|
||||
import { settings } from '$lib/db';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
const { selectedBasemapTree, selectedOverlayTree } = settings;
|
||||
|
||||
export let open: boolean;
|
||||
</script>
|
||||
|
||||
<Sheet.Root>
|
||||
<Sheet.Trigger class="w-full">
|
||||
<Button variant="ghost" class="w-full px-1 py-1.5">
|
||||
<Settings size="18" class="mr-2" />
|
||||
{$_('layers.manage')}
|
||||
</Button>
|
||||
</Sheet.Trigger>
|
||||
<Sheet.Root bind:open>
|
||||
<Sheet.Trigger class="hidden" />
|
||||
<Sheet.Content>
|
||||
<Sheet.Header class="h-full">
|
||||
<Sheet.Title>{$_('layers.settings')}</Sheet.Title>
|
||||
|
@@ -12,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
<form>
|
||||
<fieldset class="min-w-64">
|
||||
<fieldset class="min-w-64 mb-1">
|
||||
<CollapsibleTree nohover={true}>
|
||||
<LayerTreeNode {name} node={layerTree} bind:selected {multiple} bind:checked />
|
||||
</CollapsibleTree>
|
||||
|
@@ -26,11 +26,11 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="flex flex-col gap-[3px]">
|
||||
{#each Object.keys(node) as id}
|
||||
{#if typeof node[id] == 'boolean'}
|
||||
{#if node[id]}
|
||||
<div class="flex flex-row items-center gap-2 first:mt-1">
|
||||
<div class="flex flex-row items-center gap-2 first:mt-0.5 h-4">
|
||||
{#if multiple}
|
||||
<Checkbox
|
||||
id="{name}-{id}"
|
||||
|
@@ -352,10 +352,12 @@ export const dbUtils = {
|
||||
applyToFiles([id], callback);
|
||||
},
|
||||
applyToSelection: (callback: (file: WritableDraft<GPXFile>) => GPXFile) => {
|
||||
// TODO
|
||||
applyToFiles(get(selection).forEach(fileId), callback);
|
||||
},
|
||||
duplicateSelection: () => {
|
||||
applyGlobal((draft) => {
|
||||
// TODO
|
||||
let ids = getFileIds(get(settings.fileOrder).length);
|
||||
get(settings.fileOrder).forEach((fileId, index) => {
|
||||
if (get(selection).has(fileId)) {
|
||||
@@ -371,11 +373,15 @@ export const dbUtils = {
|
||||
},
|
||||
deleteSelection: () => {
|
||||
applyGlobal((draft) => {
|
||||
get(selection).forEach((item) => {
|
||||
if (item instanceof ListFileItem) {
|
||||
draft.delete(item.getId());
|
||||
}
|
||||
// TODO: Implement deletion of tracks, segments, waypoints
|
||||
selection.update(($selection) => {
|
||||
$selection.forEach((item) => {
|
||||
if (item instanceof ListFileItem) {
|
||||
draft.delete(item.getId());
|
||||
}
|
||||
// TODO: Implement deletion of tracks, segments, waypoints
|
||||
});
|
||||
$selection.clear();
|
||||
return $selection;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@@ -31,6 +31,7 @@
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"layers": "Map layers...",
|
||||
"distance_markers": "Show distance markers",
|
||||
"direction_markers": "Show direction markers",
|
||||
"about": "About",
|
||||
@@ -109,7 +110,6 @@
|
||||
"structure_tooltip": "Manage the file structure"
|
||||
},
|
||||
"layers": {
|
||||
"manage": "Manage layers",
|
||||
"settings": "Layer settings",
|
||||
"settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.",
|
||||
"selection": "Layer selection",
|
||||
|
Reference in New Issue
Block a user