mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-10-14 11:38:18 +00:00
progress
This commit is contained in:
@@ -15,21 +15,25 @@
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { Trash2 } from '@lucide/svelte';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import type { GeoJSONSource } from 'mapbox-gl';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let cleanType = CleanType.INSIDE;
|
||||
let deleteTrackpoints = true;
|
||||
let deleteWaypoints = true;
|
||||
let rectangleCoordinates: mapboxgl.LngLat[] = [];
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
function updateRectangle() {
|
||||
if (map.current) {
|
||||
let cleanType = $state(CleanType.INSIDE);
|
||||
let deleteTrackpoints = $state(true);
|
||||
let deleteWaypoints = $state(true);
|
||||
let rectangleCoordinates: mapboxgl.LngLat[] = $state([]);
|
||||
|
||||
$effect(() => {
|
||||
if (map.value) {
|
||||
if (rectangleCoordinates.length != 2) {
|
||||
if (map.current.getLayer('rectangle')) {
|
||||
map.current.removeLayer('rectangle');
|
||||
if (map.value.getLayer('rectangle')) {
|
||||
map.value.removeLayer('rectangle');
|
||||
}
|
||||
} else {
|
||||
let data: GeoJSON.Feature = {
|
||||
@@ -48,17 +52,17 @@
|
||||
},
|
||||
properties: {},
|
||||
};
|
||||
let source: GeoJSONSource | undefined = map.current.getSource('rectangle');
|
||||
let source: GeoJSONSource | undefined = map.value.getSource('rectangle');
|
||||
if (source) {
|
||||
source.setData(data);
|
||||
} else {
|
||||
map.current.addSource('rectangle', {
|
||||
map.value.addSource('rectangle', {
|
||||
type: 'geojson',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
if (!map.current.getLayer('rectangle')) {
|
||||
map.current.addLayer({
|
||||
if (!map.value.getLayer('rectangle')) {
|
||||
map.value.addLayer({
|
||||
id: 'rectangle',
|
||||
type: 'fill',
|
||||
source: 'rectangle',
|
||||
@@ -70,11 +74,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: if (rectangleCoordinates) {
|
||||
updateRectangle();
|
||||
}
|
||||
});
|
||||
|
||||
let mousedown = false;
|
||||
function onMouseDown(e: any) {
|
||||
@@ -93,42 +93,42 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (map.current) {
|
||||
setCrosshairCursor(map.current.getCanvas());
|
||||
map.current.on('mousedown', onMouseDown);
|
||||
map.current.on('mousemove', onMouseMove);
|
||||
map.current.on('mouseup', onMouseUp);
|
||||
map.current.on('touchstart', onMouseDown);
|
||||
map.current.on('touchmove', onMouseMove);
|
||||
map.current.on('touchend', onMouseUp);
|
||||
map.current.dragPan.disable();
|
||||
if (map.value) {
|
||||
setCrosshairCursor(map.value.getCanvas());
|
||||
map.value.on('mousedown', onMouseDown);
|
||||
map.value.on('mousemove', onMouseMove);
|
||||
map.value.on('mouseup', onMouseUp);
|
||||
map.value.on('touchstart', onMouseDown);
|
||||
map.value.on('touchmove', onMouseMove);
|
||||
map.value.on('touchend', onMouseUp);
|
||||
map.value.dragPan.disable();
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (map.current) {
|
||||
resetCursor(map.current.getCanvas());
|
||||
map.current.off('mousedown', onMouseDown);
|
||||
map.current.off('mousemove', onMouseMove);
|
||||
map.current.off('mouseup', onMouseUp);
|
||||
map.current.off('touchstart', onMouseDown);
|
||||
map.current.off('touchmove', onMouseMove);
|
||||
map.current.off('touchend', onMouseUp);
|
||||
map.current.dragPan.enable();
|
||||
if (map.value) {
|
||||
resetCursor(map.value.getCanvas());
|
||||
map.value.off('mousedown', onMouseDown);
|
||||
map.value.off('mousemove', onMouseMove);
|
||||
map.value.off('mouseup', onMouseUp);
|
||||
map.value.off('touchstart', onMouseDown);
|
||||
map.value.off('touchmove', onMouseMove);
|
||||
map.value.off('touchend', onMouseUp);
|
||||
map.value.dragPan.enable();
|
||||
|
||||
if (map.current.getLayer('rectangle')) {
|
||||
map.current.removeLayer('rectangle');
|
||||
if (map.value.getLayer('rectangle')) {
|
||||
map.value.removeLayer('rectangle');
|
||||
}
|
||||
if (map.current.getSource('rectangle')) {
|
||||
map.current.removeSource('rectangle');
|
||||
if (map.value.getSource('rectangle')) {
|
||||
map.value.removeSource('rectangle');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$: validSelection = $selection.size > 0;
|
||||
let validSelection = $derived(selection.value.size > 0);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 items-center {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 items-center {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-3">
|
||||
<div class="flex flex-row items-center gap-[6.4px] h-3">
|
||||
<Checkbox id="delete-trkpt" bind:checked={deleteTrackpoints} class="scale-90" />
|
||||
@@ -158,7 +158,7 @@
|
||||
class="w-full"
|
||||
disabled={!validSelection || rectangleCoordinates.length != 2}
|
||||
onclick={() => {
|
||||
dbUtils.cleanSelection(
|
||||
fileActions.cleanSelection(
|
||||
[
|
||||
{
|
||||
lat: Math.min(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
||||
|
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { MountainSnow } from '@lucide/svelte';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let validSelection = $derived($selection.size > 0);
|
||||
let validSelection = $derived(selection.value.size > 0);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
@@ -21,8 +21,8 @@
|
||||
class="whitespace-normal h-fit"
|
||||
disabled={!validSelection}
|
||||
onclick={async () => {
|
||||
if (map.current) {
|
||||
dbUtils.addElevationToSelection(map.current);
|
||||
if (map.value) {
|
||||
fileActions.addElevationToSelection(map.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@@ -1,45 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Ungroup } from '@lucide/svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
ListWaypointItem,
|
||||
ListWaypointsItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
$: validSelection =
|
||||
$selection.size > 0 &&
|
||||
$selection.getSelected().every((item) => {
|
||||
if (
|
||||
item instanceof ListWaypointsItem ||
|
||||
item instanceof ListWaypointItem ||
|
||||
item instanceof ListTrackSegmentItem
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let file = getFile(item.getFileId());
|
||||
if (file) {
|
||||
if (item instanceof ListFileItem) {
|
||||
return file.getSegments().length > 1;
|
||||
} else if (item instanceof ListTrackItem) {
|
||||
if (item.getTrackIndex() < file.trk.length) {
|
||||
return file.trk[item.getTrackIndex()].getSegments().length > 1;
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let validSelection = $derived(
|
||||
selection.value.size > 0 &&
|
||||
selection.value.getSelected().every((item) => {
|
||||
if (
|
||||
item instanceof ListWaypointsItem ||
|
||||
item instanceof ListWaypointItem ||
|
||||
item instanceof ListTrackSegmentItem
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
if (file) {
|
||||
if (item instanceof ListFileItem) {
|
||||
return file.getSegments().length > 1;
|
||||
} else if (item instanceof ListTrackItem) {
|
||||
if (item.getTrackIndex() < file.trk.length) {
|
||||
return file.trk[item.getTrackIndex()].getSegments().length > 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<Button variant="outline" disabled={!validSelection} onclick={dbUtils.extractSelection}>
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<Button variant="outline" disabled={!validSelection} onclick={fileActions.extractSelection}>
|
||||
<Ungroup size="16" class="mr-1" />
|
||||
{i18n._('toolbar.extract.button')}
|
||||
</Button>
|
||||
|
@@ -6,58 +6,58 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ListFileItem, ListTrackItem } from '$lib/components/file-list/FileList';
|
||||
import { ListFileItem, ListTrackItem } from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import { Group } from '@lucide/svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let canMergeTraces = false;
|
||||
let canMergeContents = false;
|
||||
let removeGaps = false;
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
$: if ($selection.size > 1) {
|
||||
canMergeTraces = true;
|
||||
} else if ($selection.size === 1) {
|
||||
let selected = $selection.getSelected()[0];
|
||||
if (selected instanceof ListFileItem) {
|
||||
let file = getFile(selected.getFileId());
|
||||
if (file) {
|
||||
canMergeTraces = file.getSegments().length > 1;
|
||||
} else {
|
||||
canMergeTraces = false;
|
||||
let canMergeTraces = $derived.by(() => {
|
||||
if (selection.value.size > 1) {
|
||||
return true;
|
||||
} else if (selection.value.size === 1) {
|
||||
let selected = selection.value.getSelected()[0];
|
||||
if (selected instanceof ListFileItem) {
|
||||
let file = fileStateCollection.getFile(selected.getFileId());
|
||||
if (file) {
|
||||
return file.getSegments().length > 1;
|
||||
}
|
||||
} else if (selected instanceof ListTrackItem) {
|
||||
let trackIndex = selected.getTrackIndex();
|
||||
let file = fileStateCollection.getFile(selected.getFileId());
|
||||
if (file && trackIndex < file.trk.length) {
|
||||
return file.trk[trackIndex].getSegments().length > 1;
|
||||
}
|
||||
}
|
||||
} else if (selected instanceof ListTrackItem) {
|
||||
let trackIndex = selected.getTrackIndex();
|
||||
let file = getFile(selected.getFileId());
|
||||
if (file && trackIndex < file.trk.length) {
|
||||
canMergeTraces = file.trk[trackIndex].getSegments().length > 1;
|
||||
} else {
|
||||
canMergeTraces = false;
|
||||
}
|
||||
} else {
|
||||
canMergeContents = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$: canMergeContents =
|
||||
$selection.size > 1 &&
|
||||
$selection
|
||||
.getSelected()
|
||||
.some((item) => item instanceof ListFileItem || item instanceof ListTrackItem);
|
||||
let canMergeContents = $derived(
|
||||
selection.value.size > 1 &&
|
||||
selection.value
|
||||
.getSelected()
|
||||
.some((item) => item instanceof ListFileItem || item instanceof ListTrackItem)
|
||||
);
|
||||
|
||||
let mergeType = MergeType.TRACES;
|
||||
let removeGaps = $state(false);
|
||||
let mergeType = $state(MergeType.TRACES);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<RadioGroup.Root bind:value={mergeType}>
|
||||
<Label class="flex flex-row items-center gap-1.5 leading-5">
|
||||
<RadioGroup.Item value={MergeType.TRACES} />
|
||||
@@ -80,7 +80,7 @@
|
||||
disabled={(mergeType === MergeType.TRACES && !canMergeTraces) ||
|
||||
(mergeType === MergeType.CONTENTS && !canMergeContents)}
|
||||
onclick={() => {
|
||||
dbUtils.mergeSelection(
|
||||
fileActions.mergeSelection(
|
||||
mergeType === MergeType.TRACES,
|
||||
mergeType === MergeType.TRACES && $gpxStatistics.global.time.total > 0 && removeGaps
|
||||
);
|
||||
|
@@ -2,22 +2,22 @@
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Slider } from '$lib/components/ui/slider';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListItem,
|
||||
ListRootItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { Funnel } from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { dbUtils, fileObservers } from '$lib/db';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { ramerDouglasPeucker, TrackPoint, type SimplifiedTrackPoint } from 'gpx';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import type { GeoJSONSource } from 'mapbox-gl';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let props: { class?: string } = $props();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
const maxTolerance = 10000;
|
||||
|
||||
let validSelection = $derived(
|
||||
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
let tolerance = $derived(
|
||||
minTolerance * 2 ** (sliderValue[0] / (100 / Math.log2(maxTolerance / minTolerance)))
|
||||
@@ -67,18 +67,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
if (map.current) {
|
||||
let source: GeoJSONSource | undefined = map.current.getSource('simplified');
|
||||
if (map.value) {
|
||||
let source: GeoJSONSource | undefined = map.value.getSource('simplified');
|
||||
if (source) {
|
||||
source.setData(data);
|
||||
} else {
|
||||
map.current.addSource('simplified', {
|
||||
map.value.addSource('simplified', {
|
||||
type: 'geojson',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
if (!map.current.getLayer('simplified')) {
|
||||
map.current.addLayer({
|
||||
if (!map.value.getLayer('simplified')) {
|
||||
map.value.addLayer({
|
||||
id: 'simplified',
|
||||
type: 'line',
|
||||
source: 'simplified',
|
||||
@@ -88,52 +88,52 @@
|
||||
},
|
||||
});
|
||||
} else {
|
||||
map.current.moveLayer('simplified');
|
||||
map.value.moveLayer('simplified');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($fileObservers) {
|
||||
unsubscribes.forEach((unsubscribe, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
unsubscribe();
|
||||
unsubscribes.delete(fileId);
|
||||
}
|
||||
});
|
||||
$fileObservers.forEach((fileStore, fileId) => {
|
||||
if (!unsubscribes.has(fileId)) {
|
||||
let unsubscribe = derived([fileStore, selection], ([fs, sel]) => [
|
||||
fs,
|
||||
sel,
|
||||
]).subscribe(([fs, sel]) => {
|
||||
if (fs) {
|
||||
fs.file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
let segmentItem = new ListTrackSegmentItem(
|
||||
fileId,
|
||||
trackIndex,
|
||||
segmentIndex
|
||||
);
|
||||
if (sel.hasAnyParent(segmentItem)) {
|
||||
let statistics = fs.statistics.getStatisticsFor(segmentItem);
|
||||
simplified.set(segmentItem.getFullId(), [
|
||||
segmentItem,
|
||||
statistics.local.points.length,
|
||||
ramerDouglasPeucker(statistics.local.points, minTolerance),
|
||||
]);
|
||||
update();
|
||||
} else if (simplified.has(segmentItem.getFullId())) {
|
||||
simplified.delete(segmentItem.getFullId());
|
||||
update();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
unsubscribes.set(fileId, unsubscribe);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// $effect(() => {
|
||||
// if ($fileObservers) {
|
||||
// unsubscribes.forEach((unsubscribe, fileId) => {
|
||||
// if (!$fileObservers.has(fileId)) {
|
||||
// unsubscribe();
|
||||
// unsubscribes.delete(fileId);
|
||||
// }
|
||||
// });
|
||||
// $fileObservers.forEach((fileStore, fileId) => {
|
||||
// if (!unsubscribes.has(fileId)) {
|
||||
// let unsubscribe = derived([fileStore, selection], ([fs, sel]) => [
|
||||
// fs,
|
||||
// sel,
|
||||
// ]).subscribe(([fs, sel]) => {
|
||||
// if (fs) {
|
||||
// fs.file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
// let segmentItem = new ListTrackSegmentItem(
|
||||
// fileId,
|
||||
// trackIndex,
|
||||
// segmentIndex
|
||||
// );
|
||||
// if (sel.hasAnyParent(segmentItem)) {
|
||||
// let statistics = fs.statistics.getStatisticsFor(segmentItem);
|
||||
// simplified.set(segmentItem.getFullId(), [
|
||||
// segmentItem,
|
||||
// statistics.local.points.length,
|
||||
// ramerDouglasPeucker(statistics.local.points, minTolerance),
|
||||
// ]);
|
||||
// update();
|
||||
// } else if (simplified.has(segmentItem.getFullId())) {
|
||||
// simplified.delete(segmentItem.getFullId());
|
||||
// update();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// unsubscribes.set(fileId, unsubscribe);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
$effect(() => {
|
||||
if (tolerance) {
|
||||
@@ -142,12 +142,12 @@
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (map.current) {
|
||||
if (map.current.getLayer('simplified')) {
|
||||
map.current.removeLayer('simplified');
|
||||
if (map.value) {
|
||||
if (map.value.getLayer('simplified')) {
|
||||
map.value.removeLayer('simplified');
|
||||
}
|
||||
if (map.current.getSource('simplified')) {
|
||||
map.current.removeSource('simplified');
|
||||
if (map.value.getSource('simplified')) {
|
||||
map.value.removeSource('simplified');
|
||||
}
|
||||
}
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
@@ -164,7 +164,7 @@
|
||||
.map((point) => point.point)
|
||||
);
|
||||
});
|
||||
dbUtils.reduce(itemsAndPoints);
|
||||
fileActions.reduce(itemsAndPoints);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -5,7 +5,6 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import TimePicker from '$lib/components/ui/time-picker/TimePicker.svelte';
|
||||
import { dbUtils, settings } from '$lib/db';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import {
|
||||
distancePerHourToSecondsPerDistance,
|
||||
@@ -17,15 +16,22 @@
|
||||
import { CalendarClock, CirclePlay, CircleStop, CircleX, Timer, Zap } from '@lucide/svelte';
|
||||
import { tick } from 'svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
import { fileActionManager } from '$lib/logic/file-action-manager.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let startDate: DateValue | undefined = undefined;
|
||||
let startTime: string | undefined = undefined;
|
||||
@@ -47,7 +53,7 @@
|
||||
|
||||
function setSpeed(value: number) {
|
||||
let speedValue = getConvertedVelocity(value);
|
||||
if ($velocityUnits === 'speed') {
|
||||
if (velocityUnits.value === 'speed') {
|
||||
speedValue = parseFloat(speedValue.toFixed(2));
|
||||
}
|
||||
speed = speedValue;
|
||||
@@ -80,9 +86,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
||||
setGPXData();
|
||||
}
|
||||
// $: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
||||
// setGPXData();
|
||||
// }
|
||||
|
||||
function getDate(date: DateValue, time: string): Date {
|
||||
if (date === undefined) {
|
||||
@@ -133,12 +139,12 @@
|
||||
}
|
||||
|
||||
let speedValue = speed;
|
||||
if ($velocityUnits === 'pace') {
|
||||
if (velocityUnits.value === 'pace') {
|
||||
speedValue = distancePerHourToSecondsPerDistance(speed);
|
||||
}
|
||||
if ($distanceUnits === 'imperial') {
|
||||
if (distanceUnits.value === 'imperial') {
|
||||
speedValue = milesToKilometers(speedValue);
|
||||
} else if ($distanceUnits === 'nautical') {
|
||||
} else if (distanceUnits.value === 'nautical') {
|
||||
speedValue = nauticalMilesToKilometers(speedValue);
|
||||
}
|
||||
return speedValue;
|
||||
@@ -171,24 +177,26 @@
|
||||
updateEnd();
|
||||
}
|
||||
|
||||
$: canUpdate =
|
||||
$selection.size === 1 && $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
||||
let canUpdate = $derived(
|
||||
selection.value.size === 1 &&
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<Label for="speed" class="flex flex-row">
|
||||
<Zap size="16" class="mr-1" />
|
||||
{#if $velocityUnits === 'speed'}
|
||||
{#if velocityUnits.value === 'speed'}
|
||||
{i18n._('quantities.speed')}
|
||||
{:else}
|
||||
{i18n._('quantities.pace')}
|
||||
{/if}
|
||||
</Label>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
{#if $velocityUnits === 'speed'}
|
||||
{#if velocityUnits.value === 'speed'}
|
||||
<Input
|
||||
id="speed"
|
||||
type="number"
|
||||
@@ -199,11 +207,11 @@
|
||||
onchange={updateDataFromSpeed}
|
||||
/>
|
||||
<span class="text-sm shrink-0">
|
||||
{#if $distanceUnits === 'imperial'}
|
||||
{#if distanceUnits.value === 'imperial'}
|
||||
{i18n._('units.miles_per_hour')}
|
||||
{:else if $distanceUnits === 'metric'}
|
||||
{:else if distanceUnits.value === 'metric'}
|
||||
{i18n._('units.kilometers_per_hour')}
|
||||
{:else if $distanceUnits === 'nautical'}
|
||||
{:else if distanceUnits.value === 'nautical'}
|
||||
{i18n._('units.knots')}
|
||||
{/if}
|
||||
</span>
|
||||
@@ -215,11 +223,11 @@
|
||||
onChange={updateDataFromSpeed}
|
||||
/>
|
||||
<span class="text-sm shrink-0">
|
||||
{#if $distanceUnits === 'imperial'}
|
||||
{#if distanceUnits.value === 'imperial'}
|
||||
{i18n._('units.minutes_per_mile')}
|
||||
{:else if $distanceUnits === 'metric'}
|
||||
{:else if distanceUnits.value === 'metric'}
|
||||
{i18n._('units.minutes_per_kilometer')}
|
||||
{:else if $distanceUnits === 'nautical'}
|
||||
{:else if distanceUnits.value === 'nautical'}
|
||||
{i18n._('units.minutes_per_nautical_mile')}
|
||||
{/if}
|
||||
</span>
|
||||
@@ -260,7 +268,7 @@
|
||||
disabled={!canUpdate}
|
||||
bind:value={startTime}
|
||||
class="w-fit"
|
||||
on:change={updateEnd}
|
||||
onchange={updateEnd}
|
||||
/>
|
||||
</div>
|
||||
<Label class="flex flex-row">
|
||||
@@ -285,7 +293,7 @@
|
||||
disabled={!canUpdate}
|
||||
bind:value={endTime}
|
||||
class="w-fit"
|
||||
on:change={updateStart}
|
||||
onchange={updateStart}
|
||||
/>
|
||||
</div>
|
||||
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
||||
@@ -324,9 +332,9 @@
|
||||
ratio = $gpxStatistics.global.speed.moving / effectiveSpeed;
|
||||
}
|
||||
|
||||
let item = $selection.getSelected()[0];
|
||||
let item = selection.value.getSelected()[0];
|
||||
let fileId = item.getFileId();
|
||||
dbUtils.applyToFile(fileId, (file) => {
|
||||
fileActionManager.applyToFile(fileId, (file) => {
|
||||
if (item instanceof ListFileItem) {
|
||||
if (artificial || !$gpxStatistics.global.time.moving) {
|
||||
file.createArtificialTimestamps(
|
||||
|
@@ -21,65 +21,75 @@
|
||||
SquareArrowUpLeft,
|
||||
SquareArrowOutDownRight,
|
||||
} from '@lucide/svelte';
|
||||
|
||||
import { map, newGPXFile, routingControls, selectFileWhenLoaded } from '$lib/stores';
|
||||
import { dbUtils, getFile, getFileIds, settings } from '$lib/db';
|
||||
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/routing.svelte';
|
||||
|
||||
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/utils.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { RoutingControls } from './RoutingControls';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { fileObservers } from '$lib/db';
|
||||
// import { RoutingControls } from './RoutingControls';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getOrderedSelection, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
type ListItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { TrackPoint } from 'gpx';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions, getFileIds, newGPXFile } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let {
|
||||
minimized = $bindable(false),
|
||||
minimizable = true,
|
||||
popup = undefined,
|
||||
popupElement = undefined,
|
||||
class: className = '',
|
||||
}: {
|
||||
minimized?: boolean;
|
||||
minimizable?: boolean;
|
||||
popup?: mapboxgl.Popup;
|
||||
popupElement?: HTMLDivElement;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
export let minimized = false;
|
||||
export let minimizable = true;
|
||||
export let popup: mapboxgl.Popup | undefined = undefined;
|
||||
export let popupElement: HTMLElement | undefined = undefined;
|
||||
let selectedItem: ListItem | null = null;
|
||||
|
||||
const { privateRoads, routing, routingProfile } = settings;
|
||||
|
||||
$: if ($map && popup && popupElement) {
|
||||
// remove controls for deleted files
|
||||
routingControls.forEach((controls, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
controls.destroy();
|
||||
routingControls.delete(fileId);
|
||||
// $: if (map && popup && popupElement) {
|
||||
// // remove controls for deleted files
|
||||
// routingControls.forEach((controls, fileId) => {
|
||||
// if (!$fileObservers.has(fileId)) {
|
||||
// controls.destroy();
|
||||
// routingControls.delete(fileId);
|
||||
|
||||
if (selectedItem && selectedItem.getFileId() === fileId) {
|
||||
selectedItem = null;
|
||||
}
|
||||
} else if ($map !== controls.map) {
|
||||
controls.updateMap($map);
|
||||
}
|
||||
});
|
||||
// add controls for new files
|
||||
$fileObservers.forEach((file, fileId) => {
|
||||
if (!routingControls.has(fileId)) {
|
||||
routingControls.set(
|
||||
fileId,
|
||||
new RoutingControls($map, fileId, file, popup, popupElement)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (selectedItem && selectedItem.getFileId() === fileId) {
|
||||
// selectedItem = null;
|
||||
// }
|
||||
// } else if ($map !== controls.map) {
|
||||
// controls.updateMap($map);
|
||||
// }
|
||||
// });
|
||||
// // add controls for new files
|
||||
// fileStateCollection.files.forEach((file, fileId) => {
|
||||
// if (!routingControls.has(fileId)) {
|
||||
// routingControls.set(
|
||||
// fileId,
|
||||
// new RoutingControls($map, fileId, file, popup, popupElement)
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
$: validSelection = $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
||||
let validSelection = $derived(
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
|
||||
function createFileWithPoint(e: any) {
|
||||
if ($selection.size === 0) {
|
||||
if (selection.value.size === 0) {
|
||||
let file = newGPXFile();
|
||||
file.replaceTrackPoints(0, 0, 0, 0, [
|
||||
new TrackPoint({
|
||||
@@ -90,22 +100,22 @@
|
||||
}),
|
||||
]);
|
||||
file._data.id = getFileIds(1)[0];
|
||||
dbUtils.add(file);
|
||||
selectFileWhenLoaded(file._data.id);
|
||||
fileActions.add(file);
|
||||
// selectFileWhenLoaded(file._data.id);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCrosshairCursor();
|
||||
$map?.on('click', createFileWithPoint);
|
||||
// setCrosshairCursor();
|
||||
map.value?.on('click', createFileWithPoint);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
resetCursor();
|
||||
$map?.off('click', createFileWithPoint);
|
||||
// resetCursor();
|
||||
map.value?.off('click', createFileWithPoint);
|
||||
|
||||
routingControls.forEach((controls) => controls.destroy());
|
||||
routingControls.clear();
|
||||
// routingControls.forEach((controls) => controls.destroy());
|
||||
// routingControls.clear();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -116,11 +126,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 animate-in animate-out {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 animate-in animate-out {className ?? ''}">
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label class="flex flex-row justify-between items-center gap-2">
|
||||
<span class="flex flex-row items-center gap-1">
|
||||
{#if $routing}
|
||||
{#if routing.value}
|
||||
<Route size="16" />
|
||||
{:else}
|
||||
<RouteOff size="16" />
|
||||
@@ -128,28 +138,28 @@
|
||||
{i18n._('toolbar.routing.use_routing')}
|
||||
</span>
|
||||
<Tooltip label={i18n._('toolbar.routing.use_routing_tooltip')}>
|
||||
<Switch class="scale-90" bind:checked={$routing} />
|
||||
<Switch class="scale-90" bind:checked={routing.value} />
|
||||
<Shortcut slot="extra" key="F5" />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
{#if $routing}
|
||||
{#if routing.value}
|
||||
<div class="flex flex-col gap-3" in:slide>
|
||||
<Label class="flex flex-row justify-between items-center gap-2">
|
||||
<span class="shrink-0 flex flex-row items-center gap-1">
|
||||
{#if $routingProfile.includes('bike') || $routingProfile.includes('motorcycle')}
|
||||
{#if routingProfile.value.includes('bike') || routingProfile.value.includes('motorcycle')}
|
||||
<Bike size="16" />
|
||||
{:else if $routingProfile.includes('foot')}
|
||||
{:else if routingProfile.value.includes('foot')}
|
||||
<Footprints size="16" />
|
||||
{:else if $routingProfile.includes('water')}
|
||||
{:else if routingProfile.value.includes('water')}
|
||||
<Waves size="16" />
|
||||
{:else if $routingProfile.includes('railway')}
|
||||
{:else if routingProfile.value.includes('railway')}
|
||||
<TrainFront size="16" />
|
||||
{/if}
|
||||
{i18n._('toolbar.routing.activity')}
|
||||
</span>
|
||||
<Select.Root type="single" bind:value={$routingProfile}>
|
||||
<Select.Root type="single" bind:value={routingProfile.value}>
|
||||
<Select.Trigger class="h-8 grow">
|
||||
{i18n._(`toolbar.routing.activities.${$routingProfile}`)}
|
||||
{i18n._(`toolbar.routing.activities.${routingProfile.value}`)}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each Object.keys(brouterProfiles) as profile}
|
||||
@@ -167,7 +177,7 @@
|
||||
<TriangleAlert size="16" />
|
||||
{i18n._('toolbar.routing.allow_private')}
|
||||
</span>
|
||||
<Switch class="scale-90" bind:checked={$privateRoads} />
|
||||
<Switch class="scale-90" bind:checked={privateRoads.value} />
|
||||
</Label>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -178,7 +188,7 @@
|
||||
variant="outline"
|
||||
class="flex flex-row gap-1 text-xs px-2"
|
||||
disabled={!validSelection}
|
||||
onclick={dbUtils.reverseSelection}
|
||||
onclick={fileActions.reverseSelection}
|
||||
>
|
||||
<ArrowRightLeft size="12" />{i18n._('toolbar.routing.reverse.button')}
|
||||
</ButtonWithTooltip>
|
||||
@@ -188,10 +198,10 @@
|
||||
class="flex flex-row gap-1 text-xs px-2"
|
||||
disabled={!validSelection}
|
||||
onclick={() => {
|
||||
const selected = getOrderedSelection();
|
||||
const selected = selection.getOrderedSelection();
|
||||
if (selected.length > 0) {
|
||||
const firstFileId = selected[0].getFileId();
|
||||
const firstFile = getFile(firstFileId);
|
||||
const firstFile = fileStateCollection.getFile(firstFileId);
|
||||
if (firstFile) {
|
||||
let start = (() => {
|
||||
if (selected[0] instanceof ListFileItem) {
|
||||
@@ -208,9 +218,9 @@
|
||||
|
||||
if (start !== undefined) {
|
||||
const lastFileId = selected[selected.length - 1].getFileId();
|
||||
routingControls
|
||||
.get(lastFileId)
|
||||
?.appendAnchorWithCoordinates(start.getCoordinates());
|
||||
// routingControls
|
||||
// .get(lastFileId)
|
||||
// ?.appendAnchorWithCoordinates(start.getCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { distance, type Coordinates, TrackPoint, TrackSegment, Track, projectedPoint } from 'gpx';
|
||||
import { get, writable, type Readable } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { route } from './routing.svelte';
|
||||
import { route } from './utils.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { dbUtils, settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { getOrderedSelection, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
import { currentTool, streetViewEnabled, Tool } from '$lib/stores';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { getClosestLinePoint, resetCursor, setGrabbingCursor } from '$lib/utils';
|
||||
import type { GPXFileWithStatistics } from '$lib/logic/statistics';
|
||||
|
||||
// const { streetViewSource } = settings;
|
||||
export const canChangeStart = writable(false);
|
||||
|
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { splitAs, SplitType } from '$lib/components/toolbar/tools/scissors/utils.svelte';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { ListRootItem } from '$lib/components/file-list/FileList';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { ListRootItem } from '$lib/components/file-list/file-list';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Slider } from '$lib/components/ui/slider';
|
||||
@@ -15,8 +14,9 @@
|
||||
import { onDestroy, tick } from 'svelte';
|
||||
import { Crop } from '@lucide/svelte';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { SplitControls } from './SplitControls.svelte';
|
||||
import { SplitControls } from './split-controls';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
@@ -35,7 +35,7 @@
|
||||
});
|
||||
|
||||
let validSelection = $derived(
|
||||
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
||||
$gpxStatistics.local.points.length > 0
|
||||
);
|
||||
|
||||
|
@@ -1,16 +1,12 @@
|
||||
import { TrackPoint, TrackSegment } from 'gpx';
|
||||
import { get } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import {
|
||||
applyToOrderedSelectedItemsFromFile,
|
||||
selection,
|
||||
} from '$lib/components/file-list/Selection';
|
||||
import { ListTrackSegmentItem } from '$lib/components/file-list/FileList';
|
||||
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { tool, Tool } from '$lib/components/toolbar/utils.svelte';
|
||||
import { splitAs } from '$lib/components/toolbar/tools/scissors/utils.svelte';
|
||||
import { Scissors } from 'lucide-static';
|
||||
import { applyToOrderedSelectedItemsFromFile, selection } from '$lib/logic/selection.svelte';
|
||||
|
||||
export class SplitControls {
|
||||
active: boolean = false;
|
||||
@@ -25,10 +21,9 @@ export class SplitControls {
|
||||
constructor(map: mapboxgl.Map) {
|
||||
this.map = map;
|
||||
|
||||
this.unsubscribes.push(selection.subscribe(this.addIfNeeded.bind(this)));
|
||||
this.unsubscribes.push(gpxStatistics.subscribe(this.addIfNeeded.bind(this)));
|
||||
$effect(() => {
|
||||
tool.current, this.addIfNeeded.bind(this);
|
||||
tool.current, selection.value, this.addIfNeeded.bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +59,7 @@ export class SplitControls {
|
||||
if (file) {
|
||||
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
if (
|
||||
get(selection).hasAnyParent(
|
||||
selection.value.hasAnyParent(
|
||||
new ListTrackSegmentItem(fileId, trackIndex, segmentIndex)
|
||||
)
|
||||
) {
|
@@ -1,19 +1,11 @@
|
||||
<script lang="ts" context="module">
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const selectedWaypoint = writable<[Waypoint, string] | undefined>(undefined);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { Waypoint } from 'gpx';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { ListWaypointItem } from '$lib/components/file-list/FileList';
|
||||
import { ListWaypointItem } from '$lib/components/file-list/file-list';
|
||||
import { dbUtils, fileObservers, getFile, settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { get } from 'svelte/store';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
@@ -22,61 +14,21 @@
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { MapPin, CircleX, Save } from '@lucide/svelte';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { selectedWaypoint } from './utils.svelte';
|
||||
|
||||
let name: string;
|
||||
let description: string;
|
||||
let link: string;
|
||||
let longitude: number;
|
||||
let latitude: number;
|
||||
let symbolKey: string;
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const { treeFileView } = settings;
|
||||
let name = $state('');
|
||||
let description = $state('');
|
||||
let link = $state('');
|
||||
let symbolKey = $state('');
|
||||
let longitude = $state(0);
|
||||
let latitude = $state(0);
|
||||
|
||||
$: canCreate = $selection.size > 0;
|
||||
|
||||
$: if ($treeFileView && $selection) {
|
||||
selectedWaypoint.update(() => {
|
||||
if ($selection.size === 1) {
|
||||
let item = $selection.getSelected()[0];
|
||||
if (item instanceof ListWaypointItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
let waypoint = file?.wpt[item.getWaypointIndex()];
|
||||
if (waypoint) {
|
||||
return [waypoint, item.getFileId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
let unsubscribe: (() => void) | undefined = undefined;
|
||||
function updateWaypointData(fileStore: GPXFileWithStatistics | undefined) {
|
||||
if ($selectedWaypoint) {
|
||||
if (fileStore) {
|
||||
if ($selectedWaypoint[0]._data.index < fileStore.file.wpt.length) {
|
||||
$selectedWaypoint[0] = fileStore.file.wpt[$selectedWaypoint[0]._data.index];
|
||||
name = $selectedWaypoint[0].name ?? '';
|
||||
description = $selectedWaypoint[0].desc ?? '';
|
||||
if (
|
||||
$selectedWaypoint[0].cmt !== undefined &&
|
||||
$selectedWaypoint[0].cmt !== $selectedWaypoint[0].desc
|
||||
) {
|
||||
description += '\n\n' + $selectedWaypoint[0].cmt;
|
||||
}
|
||||
link = $selectedWaypoint[0].link?.attributes?.href ?? '';
|
||||
let symbol = $selectedWaypoint[0].sym ?? '';
|
||||
symbolKey = getSymbolKey(symbol) ?? symbol ?? '';
|
||||
longitude = parseFloat($selectedWaypoint[0].getLongitude().toFixed(6));
|
||||
latitude = parseFloat($selectedWaypoint[0].getLatitude().toFixed(6));
|
||||
} else {
|
||||
selectedWaypoint.set(undefined);
|
||||
}
|
||||
} else {
|
||||
selectedWaypoint.set(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
let canCreate = $derived(selection.value.size > 0);
|
||||
|
||||
function resetWaypointData() {
|
||||
name = '';
|
||||
@@ -87,21 +39,6 @@
|
||||
latitude = 0;
|
||||
}
|
||||
|
||||
$: {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
if ($selectedWaypoint) {
|
||||
let fileStore = get(fileObservers).get($selectedWaypoint[1]);
|
||||
if (fileStore) {
|
||||
unsubscribe = fileStore.subscribe(updateWaypointData);
|
||||
}
|
||||
} else {
|
||||
resetWaypointData();
|
||||
}
|
||||
}
|
||||
|
||||
function createOrUpdateWaypoint() {
|
||||
if (typeof latitude === 'string') {
|
||||
latitude = parseFloat(latitude);
|
||||
@@ -124,12 +61,12 @@
|
||||
link: link.length > 0 ? { attributes: { href: link } } : undefined,
|
||||
sym: symbols[symbolKey]?.value ?? '',
|
||||
},
|
||||
$selectedWaypoint
|
||||
? new ListWaypointItem($selectedWaypoint[1], $selectedWaypoint[0]._data.index)
|
||||
selectedWaypoint.wpt && selectedWaypoint.fileId
|
||||
? new ListWaypointItem(selectedWaypoint.fileId, selectedWaypoint.wpt._data.index)
|
||||
: undefined
|
||||
);
|
||||
|
||||
selectedWaypoint.set(undefined);
|
||||
selectedWaypoint.reset();
|
||||
resetWaypointData();
|
||||
}
|
||||
|
||||
@@ -138,49 +75,46 @@
|
||||
longitude = e.lngLat.lng.toFixed(6);
|
||||
}
|
||||
|
||||
$: sortedSymbols = Object.entries(symbols).sort((a, b) => {
|
||||
return i18n._(`gpx.symbol.${a[0]}`).localeCompare(i18n._(`gpx.symbol.${b[0]}`), i18n.lang);
|
||||
});
|
||||
let sortedSymbols = $derived(
|
||||
Object.entries(symbols).sort((a, b) => {
|
||||
return i18n
|
||||
._(`gpx.symbol.${a[0]}`)
|
||||
.localeCompare(i18n._(`gpx.symbol.${b[0]}`), i18n.lang);
|
||||
})
|
||||
);
|
||||
|
||||
onMount(() => {
|
||||
let m = get(map);
|
||||
m?.on('click', setCoordinates);
|
||||
setCrosshairCursor();
|
||||
map.value?.on('click', setCoordinates);
|
||||
// setCrosshairCursor();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
let m = get(map);
|
||||
m?.off('click', setCoordinates);
|
||||
resetCursor();
|
||||
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
map.value?.off('click', setCoordinates);
|
||||
// resetCursor();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-96 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-2">
|
||||
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
||||
<Input
|
||||
bind:value={name}
|
||||
id="name"
|
||||
class="font-semibold h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<Label for="description">{i18n._('menu.metadata.description')}</Label>
|
||||
<Textarea
|
||||
bind:value={description}
|
||||
id="description"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
|
||||
<Select.Root bind:value={symbolKey} type="single">
|
||||
<Select.Trigger
|
||||
id="symbol"
|
||||
class="w-full h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
>
|
||||
{#if symbolKey in symbols}
|
||||
{i18n._(`gpx.symbol.${symbolKey}`)}
|
||||
@@ -212,7 +146,7 @@
|
||||
bind:value={link}
|
||||
id="link"
|
||||
class="h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="grow">
|
||||
@@ -225,7 +159,7 @@
|
||||
min={-90}
|
||||
max={90}
|
||||
class="text-xs h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
</div>
|
||||
<div class="grow">
|
||||
@@ -238,7 +172,7 @@
|
||||
min={-180}
|
||||
max={180}
|
||||
class="text-xs h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,11 +180,11 @@
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
class="grow whitespace-normal h-fit"
|
||||
onclick={createOrUpdateWaypoint}
|
||||
>
|
||||
{#if $selectedWaypoint}
|
||||
{#if selectedWaypoint.wpt}
|
||||
<Save size="16" class="mr-1 shrink-0" />
|
||||
{i18n._('menu.metadata.save')}
|
||||
{:else}
|
||||
@@ -261,7 +195,7 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
selectedWaypoint.set(undefined);
|
||||
selectedWaypoint.reset();
|
||||
resetWaypointData();
|
||||
}}
|
||||
>
|
||||
@@ -269,7 +203,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<Help link={getURLForLanguage(i18n.lang, '/help/toolbar/poi')}>
|
||||
{#if $selectedWaypoint || canCreate}
|
||||
{#if selectedWaypoint.wpt || canCreate}
|
||||
{i18n._('toolbar.waypoint.help')}
|
||||
{:else}
|
||||
{i18n._('toolbar.waypoint.help_no_selection')}
|
@@ -0,0 +1,67 @@
|
||||
import { ListWaypointItem } from '$lib/components/file-list/file-list';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import type { Waypoint } from 'gpx';
|
||||
|
||||
export class WaypointSelection {
|
||||
private _selection: [Waypoint, string] | undefined;
|
||||
|
||||
constructor() {
|
||||
this._selection = $derived.by(() => {
|
||||
if (settings.treeFileView.value && selection.value.size === 1) {
|
||||
let item = selection.value.getSelected()[0];
|
||||
if (item instanceof ListWaypointItem) {
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
let waypoint = file?.wpt[item.getWaypointIndex()];
|
||||
if (waypoint) {
|
||||
return [waypoint, item.getFileId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._selection = undefined;
|
||||
}
|
||||
|
||||
get wpt(): Waypoint | undefined {
|
||||
return this._selection ? this._selection[0] : undefined;
|
||||
}
|
||||
|
||||
get fileId(): string | undefined {
|
||||
return this._selection ? this._selection[1] : undefined;
|
||||
}
|
||||
|
||||
// TODO update the waypoint data if the file changes
|
||||
// function updateWaypointData(fileStore: GPXFileWithStatistics | undefined) {
|
||||
// if (selectedWaypoint.wpt) {
|
||||
// if (fileStore) {
|
||||
// if ($selectedWaypoint[0]._data.index < fileStore.file.wpt.length) {
|
||||
// $selectedWaypoint[0] = fileStore.file.wpt[$selectedWaypoint[0]._data.index];
|
||||
// name = $selectedWaypoint[0].name ?? '';
|
||||
// description = $selectedWaypoint[0].desc ?? '';
|
||||
// if (
|
||||
// $selectedWaypoint[0].cmt !== undefined &&
|
||||
// $selectedWaypoint[0].cmt !== $selectedWaypoint[0].desc
|
||||
// ) {
|
||||
// description += '\n\n' + $selectedWaypoint[0].cmt;
|
||||
// }
|
||||
// link = $selectedWaypoint[0].link?.attributes?.href ?? '';
|
||||
// let symbol = $selectedWaypoint[0].sym ?? '';
|
||||
// symbolKey = getSymbolKey(symbol) ?? symbol ?? '';
|
||||
// longitude = parseFloat($selectedWaypoint[0].getLongitude().toFixed(6));
|
||||
// latitude = parseFloat($selectedWaypoint[0].getLatitude().toFixed(6));
|
||||
// } else {
|
||||
// selectedWaypoint.reset();
|
||||
// }
|
||||
// } else {
|
||||
// selectedWaypoint.reset();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
export const selectedWaypoint = new WaypointSelection();
|
Reference in New Issue
Block a user