mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-03 09:12:30 +00:00
dexie progress
This commit is contained in:
@@ -123,14 +123,14 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none">
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
||||
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
|
||||
<div bind:this={container} class="flex flex-row gap-1">
|
||||
{#if $fileObservers}
|
||||
{#each $fileObservers.values() as file}
|
||||
{#each $fileObservers.entries() as [fileId, file]}
|
||||
<div
|
||||
bind:this={buttons[get(file)._data.id]}
|
||||
data-id={get(file)._data.id}
|
||||
bind:this={buttons[fileId]}
|
||||
data-id={fileId}
|
||||
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
|
||||
>
|
||||
<FileListItem {file} />
|
||||
|
@@ -4,43 +4,47 @@
|
||||
import Shortcut from './Shortcut.svelte';
|
||||
import { Copy, Trash2 } from 'lucide-svelte';
|
||||
|
||||
import { get, type Readable, type Writable } from 'svelte/store';
|
||||
import { get, type Readable } from 'svelte/store';
|
||||
import { selectedFiles, selectFiles } from '$lib/stores';
|
||||
import { dbUtils } from '$lib/db';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { GPXFile } from 'gpx';
|
||||
import type { FreezedObject } from 'structurajs';
|
||||
import { dbUtils } from '$lib/db';
|
||||
|
||||
export let file: Readable<FreezedObject<GPXFile>> | undefined;
|
||||
export let file: Readable<GPXFile | undefined>;
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
on:contextmenu={() => {
|
||||
if (!get(selectedFiles).has($file?._data.id)) {
|
||||
get(selectFiles).select($file?._data.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<Button variant="outline" class="h-9 px-1.5 py-1 border-none shadow-md">
|
||||
{$file?.metadata.name}
|
||||
</Button>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item on:click={dbUtils.duplicateSelectedFiles}>
|
||||
<Copy size="16" class="mr-1" />
|
||||
{$_('menu.duplicate')}
|
||||
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
|
||||
>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item on:click={dbUtils.deleteSelectedFiles}
|
||||
><Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut key="⌫" ctrl={true} /></ContextMenu.Item
|
||||
>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
{#if $file}
|
||||
<div
|
||||
on:contextmenu={() => {
|
||||
if (!get(selectedFiles).has($file._data.id)) {
|
||||
get(selectFiles).select($file._data.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="h-9 px-1.5 py-1 border-none shadow-md focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
>
|
||||
{$file.metadata.name}
|
||||
</Button>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item on:click={dbUtils.duplicateSelectedFiles}>
|
||||
<Copy size="16" class="mr-1" />
|
||||
{$_('menu.duplicate')}
|
||||
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
|
||||
>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item on:click={dbUtils.deleteSelectedFiles}
|
||||
><Trash2 size="16" class="mr-1" />
|
||||
{$_('menu.delete')}
|
||||
<Shortcut key="⌫" ctrl={true} /></ContextMenu.Item
|
||||
>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { derived, get } from 'svelte/store';
|
||||
import { canUndo, dbUtils, fileObservers, redo, undo } from '$lib/db';
|
||||
import { canUndo, canRedo, dbUtils, fileObservers, redo, undo } from '$lib/db';
|
||||
|
||||
let showDistanceMarkers = false;
|
||||
let showDirectionMarkers = false;
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
|
||||
let undoDisabled = derived(canUndo, ($canUndo) => !$canUndo);
|
||||
let redoDisabled = derived(canUndo, ($canUndo) => !$canUndo);
|
||||
let redoDisabled = derived(canRedo, ($canRedo) => !$canRedo);
|
||||
</script>
|
||||
|
||||
<div class="absolute top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
|
||||
|
@@ -39,31 +39,36 @@ function decrementColor(color: string) {
|
||||
|
||||
export class GPXLayer {
|
||||
map: mapboxgl.Map;
|
||||
file: Readable<FreezedObject<GPXFile>>;
|
||||
fileId: string;
|
||||
file: Readable<FreezedObject<GPXFile> | undefined>;
|
||||
layerColor: string;
|
||||
popup: mapboxgl.Popup;
|
||||
popupElement: HTMLElement;
|
||||
markers: mapboxgl.Marker[] = [];
|
||||
unsubscribe: () => void;
|
||||
|
||||
addBinded: () => void = this.add.bind(this);
|
||||
updateBinded: () => void = this.update.bind(this);
|
||||
selectOnClickBinded: (e: any) => void = this.selectOnClick.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map, file: Readable<FreezedObject<GPXFile>>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
|
||||
constructor(map: mapboxgl.Map, fileId: string, file: Readable<FreezedObject<GPXFile> | undefined>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
|
||||
this.map = map;
|
||||
this.file = file;
|
||||
this.fileId = get(file)._data.id;
|
||||
this.fileId = fileId;
|
||||
this.file = file
|
||||
this.layerColor = getColor();
|
||||
this.popup = popup;
|
||||
this.popupElement = popupElement;
|
||||
this.unsubscribe = file.subscribe(this.updateData.bind(this));
|
||||
this.unsubscribe = file.subscribe(this.update.bind(this));
|
||||
|
||||
this.add();
|
||||
this.map.on('style.load', this.addBinded);
|
||||
this.map.on('style.load', this.updateBinded);
|
||||
}
|
||||
|
||||
add() {
|
||||
update() {
|
||||
let file = get(this.file);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
let addedSource = false;
|
||||
if (!this.map.getSource(this.fileId)) {
|
||||
let data = this.getGeoJSON();
|
||||
|
||||
@@ -71,6 +76,7 @@ export class GPXLayer {
|
||||
type: 'geojson',
|
||||
data
|
||||
});
|
||||
addedSource = true;
|
||||
}
|
||||
|
||||
if (!this.map.getLayer(this.fileId)) {
|
||||
@@ -93,16 +99,16 @@ export class GPXLayer {
|
||||
this.map.on('mouseenter', this.fileId, toPointerCursor);
|
||||
this.map.on('mouseleave', this.fileId, toDefaultCursor);
|
||||
}
|
||||
}
|
||||
|
||||
updateData() {
|
||||
let source = this.map.getSource(this.fileId);
|
||||
if (source) {
|
||||
source.setData(this.getGeoJSON());
|
||||
if (!addedSource) {
|
||||
let source = this.map.getSource(this.fileId);
|
||||
if (source) {
|
||||
source.setData(this.getGeoJSON());
|
||||
}
|
||||
}
|
||||
|
||||
let markerIndex = 0;
|
||||
get(this.file).wpt.forEach((waypoint) => { // Update markers
|
||||
file.wpt.forEach((waypoint) => { // Update markers
|
||||
if (markerIndex < this.markers.length) {
|
||||
this.markers[markerIndex].setLngLat(waypoint.getCoordinates());
|
||||
} else {
|
||||
@@ -131,7 +137,7 @@ export class GPXLayer {
|
||||
this.map.off('click', this.fileId, this.selectOnClickBinded);
|
||||
this.map.off('mouseenter', this.fileId, toPointerCursor);
|
||||
this.map.off('mouseleave', this.fileId, toDefaultCursor);
|
||||
this.map.off('style.load', this.addBinded);
|
||||
this.map.off('style.load', this.updateBinded);
|
||||
|
||||
this.map.removeLayer(this.fileId);
|
||||
this.map.removeSource(this.fileId);
|
||||
@@ -146,7 +152,9 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
moveToFront() {
|
||||
this.map.moveLayer(this.fileId);
|
||||
if (this.map.getLayer(this.fileId)) {
|
||||
this.map.moveLayer(this.fileId);
|
||||
}
|
||||
}
|
||||
|
||||
selectOnClick(e: any) {
|
||||
@@ -161,7 +169,15 @@ export class GPXLayer {
|
||||
}
|
||||
|
||||
getGeoJSON(): GeoJSON.FeatureCollection {
|
||||
let data = get(this.file).toGeoJSON();
|
||||
let file = get(this.file);
|
||||
if (!file) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
};
|
||||
}
|
||||
|
||||
let data = file.toGeoJSON();
|
||||
for (let feature of data.features) {
|
||||
if (!feature.properties) {
|
||||
feature.properties = {};
|
||||
|
@@ -22,7 +22,7 @@
|
||||
// add layers for new files
|
||||
$fileObservers.forEach((file, fileId) => {
|
||||
if (!$layers.has(fileId)) {
|
||||
$layers.set(fileId, new GPXLayer(get(map), file, popup, popupElement));
|
||||
$layers.set(fileId, new GPXLayer(get(map), fileId, file, popup, popupElement));
|
||||
}
|
||||
});
|
||||
return $layers;
|
||||
|
@@ -61,7 +61,7 @@
|
||||
if (selectedFileObserver) {
|
||||
routingControls.set(
|
||||
selectedId,
|
||||
new RoutingControls(get(map), selectedFileObserver, popup, popupElement)
|
||||
new RoutingControls(get(map), selectedId, selectedFileObserver, popup, popupElement)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { distance, type Coordinates, type GPXFile, TrackPoint, TrackSegment } from "gpx";
|
||||
import { get, type Writable } from "svelte/store";
|
||||
import { get, type Readable, type Writable } from "svelte/store";
|
||||
import { computeAnchorPoints } from "./Simplify";
|
||||
import mapboxgl from "mapbox-gl";
|
||||
import { route } from "./Routing";
|
||||
@@ -11,7 +11,8 @@ import { dbUtils } from "$lib/db";
|
||||
|
||||
export class RoutingControls {
|
||||
map: mapboxgl.Map;
|
||||
file: Writable<GPXFile>;
|
||||
fileId: string = '';
|
||||
file: Readable<GPXFile | undefined>;
|
||||
anchors: AnchorWithMarker[] = [];
|
||||
shownAnchors: AnchorWithMarker[] = [];
|
||||
popup: mapboxgl.Popup;
|
||||
@@ -24,8 +25,9 @@ export class RoutingControls {
|
||||
updateTemporaryAnchorBinded: (e: any) => void = this.updateTemporaryAnchor.bind(this);
|
||||
appendAnchorBinded: (e: mapboxgl.MapMouseEvent) => void = this.appendAnchor.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map, file: Writable<GPXFile>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
|
||||
constructor(map: mapboxgl.Map, fileId: string, file: Writable<GPXFile>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
|
||||
this.map = map;
|
||||
this.fileId = fileId;
|
||||
this.file = file;
|
||||
this.popup = popup;
|
||||
this.popupElement = popupElement;
|
||||
@@ -46,13 +48,18 @@ export class RoutingControls {
|
||||
this.map.on('zoom', this.toggleAnchorsForZoomLevelAndBoundsBinded);
|
||||
this.map.on('move', this.toggleAnchorsForZoomLevelAndBoundsBinded);
|
||||
this.map.on('click', this.appendAnchorBinded);
|
||||
this.map.on('mousemove', get(this.file)._data.id, this.showTemporaryAnchorBinded);
|
||||
this.map.on('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
||||
|
||||
this.unsubscribe = this.file.subscribe(this.updateControls.bind(this));
|
||||
}
|
||||
|
||||
updateControls() { // Update the markers when the file changes
|
||||
let segments = get(this.file).getSegments();
|
||||
let file = get(this.file);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
let segments = file.getSegments();
|
||||
|
||||
let anchorIndex = 0;
|
||||
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
||||
@@ -104,7 +111,7 @@ export class RoutingControls {
|
||||
this.map.off('zoom', this.toggleAnchorsForZoomLevelAndBoundsBinded);
|
||||
this.map.off('move', this.toggleAnchorsForZoomLevelAndBoundsBinded);
|
||||
this.map.off('click', this.appendAnchorBinded);
|
||||
this.map.off('mousemove', get(this.file)._data.id, this.showTemporaryAnchorBinded);
|
||||
this.map.off('mousemove', this.fileId, this.showTemporaryAnchorBinded);
|
||||
this.map.off('mousemove', this.updateTemporaryAnchorBinded);
|
||||
|
||||
this.unsubscribe();
|
||||
@@ -290,17 +297,17 @@ export class RoutingControls {
|
||||
let [previousAnchor, nextAnchor] = this.getNeighbouringAnchors(anchor);
|
||||
|
||||
if (previousAnchor === null && nextAnchor === null) { // Only one point, remove it
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[anchor.segmentIndex];
|
||||
segment.replace(0, 0, []);
|
||||
});
|
||||
} else if (previousAnchor === null) { // First point, remove trackpoints until nextAnchor
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[anchor.segmentIndex];
|
||||
segment.replace(0, nextAnchor.point._data.index - 1, []);
|
||||
});
|
||||
} else if (nextAnchor === null) { // Last point, remove trackpoints from previousAnchor
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[anchor.segmentIndex];
|
||||
segment.replace(previousAnchor.point._data.index + 1, segment.trkpt.length - 1, []);
|
||||
});
|
||||
@@ -323,7 +330,7 @@ export class RoutingControls {
|
||||
|
||||
if (!lastAnchor) {
|
||||
// TODO, create segment if it does not exist
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[0];
|
||||
segment.replace(0, 0, [newPoint]);
|
||||
});
|
||||
@@ -365,7 +372,7 @@ export class RoutingControls {
|
||||
let segment = anchors[0].segment;
|
||||
|
||||
if (anchors.length === 1) { // Only one anchor, update the point in the segment
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[anchors[0].segmentIndex];
|
||||
segment.replace(0, 0, [new TrackPoint({
|
||||
attributes: targetCoordinates[0],
|
||||
@@ -425,7 +432,7 @@ export class RoutingControls {
|
||||
anchor.point._data.zoom = 0; // Make these anchors permanent
|
||||
});
|
||||
|
||||
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
|
||||
dbUtils.applyToFile(this.fileId, (file) => {
|
||||
let segment = file.getSegments()[anchors[0].segmentIndex];
|
||||
segment.replace(start, end, response);
|
||||
});
|
||||
|
Reference in New Issue
Block a user