hide any file item and undo-redo it

This commit is contained in:
vcoppe
2024-07-02 20:04:17 +02:00
parent 874eea271e
commit c5dea0aeda
9 changed files with 316 additions and 143 deletions

View File

@@ -123,7 +123,7 @@ export class GPXFile extends GPXTreeNode<Track>{
if (gpx) { if (gpx) {
this.attributes = gpx.attributes this.attributes = gpx.attributes
this.metadata = gpx.metadata; this.metadata = gpx.metadata;
this.wpt = gpx.wpt ? gpx.wpt.map((waypoint, index) => new Waypoint(waypoint, index)) : []; this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : [];
this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : []; this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : [];
if (gpx.hasOwnProperty('_data')) { if (gpx.hasOwnProperty('_data')) {
this._data = gpx._data; this._data = gpx._data;
@@ -134,6 +134,17 @@ export class GPXFile extends GPXTreeNode<Track>{
this.wpt = []; this.wpt = [];
this.trk = [new Track()]; this.trk = [new Track()];
} }
this.trk.forEach((track, trackIndex) => {
track._data['trackIndex'] = trackIndex;
track.trkseg.forEach((segment, segmentIndex) => {
segment._data['trackIndex'] = trackIndex;
segment._data['segmentIndex'] = segmentIndex;
});
});
this.wpt.forEach((waypoint, waypointIndex) => {
waypoint._data['index'] = waypointIndex;
});
} }
get children(): ReadonlyArray<Track> { get children(): ReadonlyArray<Track> {
@@ -367,6 +378,63 @@ export class GPXFile extends GPXTreeNode<Track>{
} }
}); });
} }
setHidden(hidden: boolean, trackIndices?: number[], segmentIndices?: number[]) {
return produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let allHidden = hidden;
let trk = og.trk.map((track, index) => {
if (trackIndices === undefined || trackIndices.includes(index)) {
return track.setHidden(hidden, segmentIndices);
} else {
allHidden = allHidden && (track._data.hidden === true);
return track;
}
});
draft.trk = freeze(trk); // Pre-freeze the array, faster as well
let wpt = og.wpt.map((waypoint) => {
if (trackIndices === undefined && segmentIndices === undefined) {
return waypoint.setHidden(hidden);
} else {
allHidden = allHidden && (waypoint._data.hidden === true);
return waypoint;
}
});
draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well
if (trackIndices === undefined && segmentIndices === undefined) {
draft._data.hiddenWpt = hidden;
}
draft._data.hidden = allHidden;
});
}
setHiddenWaypoints(hidden: boolean, waypointIndices?: number[]) {
return produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let allHiddenWpt = hidden;
let wpt = og.wpt.map((waypoint, index) => {
if (waypointIndices === undefined || waypointIndices.includes(index)) {
return waypoint.setHidden(hidden);
} else {
allHiddenWpt = allHiddenWpt && (waypoint._data.hidden === true);
return waypoint;
}
});
draft.wpt = freeze(wpt); // Pre-freeze the array, faster as well
let allHiddenTrk = true;
og.trk.forEach((track) => {
allHiddenTrk = allHiddenTrk && (track._data.hidden === true);
});
draft._data.hiddenWpt = allHiddenWpt;
draft._data.hidden = allHiddenTrk && allHiddenWpt;
});
}
}; };
// A class that represents a Track in a GPX file // A class that represents a Track in a GPX file
@@ -573,7 +641,24 @@ export class Track extends GPXTreeNode<TrackSegment> {
} }
}); });
} }
};
setHidden(hidden: boolean, segmentIndices?: number[]) {
return produce(this, (draft) => {
let og = getOriginal(draft); // Read as much as possible from the original object because it is faster
let allHidden = hidden;
let trkseg = og.trkseg.map((segment, index) => {
if (segmentIndices === undefined || segmentIndices.includes(index)) {
return segment.setHidden(hidden);
} else {
allHidden = allHidden && (segment._data.hidden === true);
return segment;
}
});
draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well
draft._data.hidden = allHidden;
});
}
}
// A class that represents a TrackSegment in a GPX file // A class that represents a TrackSegment in a GPX file
export class TrackSegment extends GPXTreeLeaf { export class TrackSegment extends GPXTreeLeaf {
@@ -881,6 +966,11 @@ export class TrackSegment extends GPXTreeLeaf {
} }
}); });
} }
setHidden(hidden: boolean) {
return produce(this, (draft) => {
draft._data.hidden = hidden;
});
}
}; };
export class TrackPoint { export class TrackPoint {
@@ -985,7 +1075,7 @@ export class Waypoint {
type?: string; type?: string;
_data: { [key: string]: any } = {}; _data: { [key: string]: any } = {};
constructor(waypoint: WaypointType & { _data?: any } | Waypoint, index?: number) { constructor(waypoint: WaypointType & { _data?: any } | Waypoint) {
this.attributes = waypoint.attributes; this.attributes = waypoint.attributes;
this.ele = waypoint.ele; this.ele = waypoint.ele;
this.time = waypoint.time; this.time = waypoint.time;
@@ -998,9 +1088,6 @@ export class Waypoint {
if (waypoint.hasOwnProperty('_data')) { if (waypoint.hasOwnProperty('_data')) {
this._data = waypoint._data; this._data = waypoint._data;
} }
if (index !== undefined) {
this._data['index'] = index;
}
} }
getCoordinates(): Coordinates { getCoordinates(): Coordinates {
@@ -1032,6 +1119,13 @@ export class Waypoint {
type: this.type, type: this.type,
}); });
} }
// Producers
setHidden(hidden: boolean) {
return produce(this, (draft) => {
draft._data.hidden = hidden;
});
}
} }
export class GPXStatistics { export class GPXStatistics {

View File

@@ -48,11 +48,8 @@
triggerFileInput, triggerFileInput,
createFile, createFile,
loadFiles, loadFiles,
toggleSelectionVisibility,
updateSelectionFromKey, updateSelectionFromKey,
showSelection, allHidden,
hideSelection,
anyHidden,
editMetadata, editMetadata,
editStyle, editStyle,
exportState, exportState,
@@ -231,15 +228,15 @@
</Menubar.Item> </Menubar.Item>
<Menubar.Item <Menubar.Item
on:click={() => { on:click={() => {
if ($anyHidden) { if ($allHidden) {
showSelection(); dbUtils.setHiddenToSelection(false);
} else { } else {
hideSelection(); dbUtils.setHiddenToSelection(true);
} }
}} }}
disabled={$selection.size == 0} disabled={$selection.size == 0}
> >
{#if $anyHidden} {#if $allHidden}
<Eye size="16" class="mr-1" /> <Eye size="16" class="mr-1" />
{$_('menu.unhide')} {$_('menu.unhide')}
{:else} {:else}
@@ -249,7 +246,7 @@
<Shortcut key="H" ctrl={true} /> <Shortcut key="H" ctrl={true} />
</Menubar.Item> </Menubar.Item>
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Item on:click={selectAll}> <Menubar.Item on:click={selectAll} disabled={$fileObservers.size == 0}>
<FileStack size="16" class="mr-1" /> <FileStack size="16" class="mr-1" />
{$_('menu.select_all')} {$_('menu.select_all')}
<Shortcut key="A" ctrl={true} /> <Shortcut key="A" ctrl={true} />
@@ -543,7 +540,11 @@
$verticalFileView = !$verticalFileView; $verticalFileView = !$verticalFileView;
e.preventDefault(); e.preventDefault();
} else if (e.key === 'h' && (e.metaKey || e.ctrlKey)) { } else if (e.key === 'h' && (e.metaKey || e.ctrlKey)) {
toggleSelectionVisibility(); if ($allHidden) {
dbUtils.setHiddenToSelection(false);
} else {
dbUtils.setHiddenToSelection(true);
}
e.preventDefault(); e.preventDefault();
} else if (e.key === 'F1') { } else if (e.key === 'F1') {
switchBasemaps(); switchBasemaps();

View File

@@ -5,8 +5,8 @@
import { fileObservers, settings } from '$lib/db'; import { fileObservers, settings } from '$lib/db';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList'; import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList';
import { copied, pasteSelection, selection } from './Selection'; import { copied, pasteSelection, selectAll, selection } from './Selection';
import { ClipboardPaste, Plus } from 'lucide-svelte'; import { ClipboardPaste, FileStack, Plus } from 'lucide-svelte';
import Shortcut from '$lib/components/Shortcut.svelte'; import Shortcut from '$lib/components/Shortcut.svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { createFile } from '$lib/stores'; import { createFile } from '$lib/stores';
@@ -66,6 +66,12 @@
<Shortcut key="+" ctrl={true} /> <Shortcut key="+" ctrl={true} />
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Separator /> <ContextMenu.Separator />
<ContextMenu.Item on:click={selectAll} disabled={$fileObservers.size === 0}>
<FileStack size="16" class="mr-1" />
{$_('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item <ContextMenu.Item
disabled={$copied === undefined || disabled={$copied === undefined ||
$copied.length === 0 || $copied.length === 0 ||

View File

@@ -14,8 +14,10 @@
import FileListNodeLabel from './FileListNodeLabel.svelte'; import FileListNodeLabel from './FileListNodeLabel.svelte';
import { afterUpdate, getContext } from 'svelte'; import { afterUpdate, getContext } from 'svelte';
import { import {
ListFileItem,
ListTrackSegmentItem, ListTrackSegmentItem,
ListWaypointItem, ListWaypointItem,
ListWaypointsItem,
type ListItem, type ListItem,
type ListTrackItem type ListTrackItem
} from './FileList'; } from './FileList';
@@ -34,7 +36,7 @@
let collapsible: CollapsibleTreeNode; let collapsible: CollapsibleTreeNode;
$: label = $: label =
node instanceof GPXFile node instanceof GPXFile && item instanceof ListFileItem
? node.metadata.name ? node.metadata.name
: node instanceof Track : node instanceof Track
? node.name ?? `${$_('gpx.track')} ${(item as ListTrackItem).trackIndex + 1}` ? node.name ?? `${$_('gpx.track')} ${(item as ListTrackItem).trackIndex + 1}`
@@ -42,7 +44,7 @@
? `${$_('gpx.segment')} ${(item as ListTrackSegmentItem).segmentIndex + 1}` ? `${$_('gpx.segment')} ${(item as ListTrackSegmentItem).segmentIndex + 1}`
: node instanceof Waypoint : node instanceof Waypoint
? node.name ?? `${$_('gpx.waypoint')} ${(item as ListWaypointItem).waypointIndex + 1}` ? node.name ?? `${$_('gpx.waypoint')} ${(item as ListWaypointItem).waypointIndex + 1}`
: Array.isArray(node) && node.length > 0 && node[0] instanceof Waypoint : node instanceof GPXFile && item instanceof ListWaypointsItem
? $_('gpx.waypoints') ? $_('gpx.waypoints')
: ''; : '';

View File

@@ -12,14 +12,21 @@
import { get, writable, type Readable, type Writable } from 'svelte/store'; import { get, writable, type Readable, type Writable } from 'svelte/store';
import FileListNodeStore from './FileListNodeStore.svelte'; import FileListNodeStore from './FileListNodeStore.svelte';
import FileListNode from './FileListNode.svelte'; import FileListNode from './FileListNode.svelte';
import { ListLevel, ListRootItem, allowedMoves, moveItems, type ListItem } from './FileList'; import {
ListFileItem,
ListLevel,
ListRootItem,
ListWaypointsItem,
allowedMoves,
moveItems,
type ListItem
} from './FileList';
import { selection } from './Selection'; import { selection } from './Selection';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
export let node: export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>> | Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement> | GPXTreeElement<AnyGPXTreeElement>
| ReadonlyArray<Readonly<Waypoint>>
| Readonly<Waypoint>; | Readonly<Waypoint>;
export let item: ListItem; export let item: ListItem;
export let waypointRoot: boolean = false; export let waypointRoot: boolean = false;
@@ -32,6 +39,8 @@
: node instanceof GPXFile : node instanceof GPXFile
? waypointRoot ? waypointRoot
? ListLevel.WAYPOINTS ? ListLevel.WAYPOINTS
: item instanceof ListWaypointsItem
? ListLevel.WAYPOINT
: ListLevel.TRACK : ListLevel.TRACK
: node instanceof Track : node instanceof Track
? ListLevel.SEGMENT ? ListLevel.SEGMENT
@@ -268,10 +277,16 @@
</div> </div>
{/each} {/each}
{:else if node instanceof GPXFile} {:else if node instanceof GPXFile}
{#if waypointRoot} {#if item instanceof ListWaypointsItem}
{#each node.wpt as wpt, i (wpt)}
<div data-id={i} class="ml-1">
<FileListNode node={wpt} item={item.extend(i)} />
</div>
{/each}
{:else if waypointRoot}
{#if node.wpt.length > 0} {#if node.wpt.length > 0}
<div data-id="waypoints"> <div data-id="waypoints">
<FileListNode node={node.wpt} item={item.extend('waypoints')} /> <FileListNode {node} item={item.extend('waypoints')} />
</div> </div>
{/if} {/if}
{:else} {:else}
@@ -287,16 +302,10 @@
<FileListNode node={child} item={item.extend(i)} /> <FileListNode node={child} item={item.extend(i)} />
</div> </div>
{/each} {/each}
{:else if Array.isArray(node) && node.length > 0 && node[0] instanceof Waypoint}
{#each node as wpt, i (wpt)}
<div data-id={i} class="ml-1">
<FileListNode node={wpt} item={item.extend(i)} />
</div>
{/each}
{/if} {/if}
</div> </div>
{#if node instanceof GPXFile} {#if node instanceof GPXFile && item instanceof ListFileItem}
{#if !waypointRoot} {#if !waypointRoot}
<svelte:self {node} {item} waypointRoot={true} /> <svelte:self {node} {item} waypointRoot={true} />
{/if} {/if}

View File

@@ -38,15 +38,7 @@
} from './Selection'; } from './Selection';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { import { allHidden, editMetadata, editStyle, gpxLayers, map } from '$lib/stores';
anyHidden,
editMetadata,
editStyle,
gpxLayers,
hideSelection,
map,
showSelection
} from '$lib/stores';
import { import {
GPXTreeElement, GPXTreeElement,
Track, Track,
@@ -189,9 +181,18 @@
{:else if item.level === ListLevel.WAYPOINT} {:else if item.level === ListLevel.WAYPOINT}
<MapPin size="16" class="mr-1 shrink-0" /> <MapPin size="16" class="mr-1 shrink-0" />
{/if} {/if}
<span class="grow select-none truncate {$verticalFileView ? 'mr-2' : ''}"> <span class="grow select-none truncate {$verticalFileView ? 'last:mr-2' : ''}">
{label} {label}
</span> </span>
{#if (item.level !== ListLevel.WAYPOINTS && node._data.hidden) || (item.level === ListLevel.WAYPOINTS && node._data.hiddenWpt)}
<EyeOff
size="12"
class="shrink-0 mt-1 ml-1 {$verticalFileView ? 'mr-2' : ''} {item.level ===
ListLevel.SEGMENT || item.level === ListLevel.WAYPOINT
? 'mr-3'
: ''}"
/>
{/if}
</span> </span>
</Button> </Button>
</ContextMenu.Trigger> </ContextMenu.Trigger>
@@ -206,17 +207,17 @@
<PaintBucket size="16" class="mr-1" /> <PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')} {$_('menu.style.button')}
</ContextMenu.Item> </ContextMenu.Item>
{#if item instanceof ListFileItem} {/if}
<ContextMenu.Item <ContextMenu.Item
on:click={() => { on:click={() => {
if ($anyHidden) { if ($allHidden) {
showSelection(); dbUtils.setHiddenToSelection(false);
} else { } else {
hideSelection(); dbUtils.setHiddenToSelection(true);
} }
}} }}
> >
{#if $anyHidden} {#if $allHidden}
<Eye size="16" class="mr-1" /> <Eye size="16" class="mr-1" />
{$_('menu.unhide')} {$_('menu.unhide')}
{:else} {:else}
@@ -225,9 +226,7 @@
{/if} {/if}
<Shortcut key="H" ctrl={true} /> <Shortcut key="H" ctrl={true} />
</ContextMenu.Item> </ContextMenu.Item>
{/if}
<ContextMenu.Separator /> <ContextMenu.Separator />
{/if}
{#if $verticalFileView} {#if $verticalFileView}
{#if item instanceof ListFileItem} {#if item instanceof ListFileItem}
<ContextMenu.Item <ContextMenu.Item

View File

@@ -50,7 +50,6 @@ export class GPXLayer {
fileId: string; fileId: string;
file: Readable<GPXFileWithStatistics | undefined>; file: Readable<GPXFileWithStatistics | undefined>;
layerColor: string; layerColor: string;
hidden: boolean = false;
markers: mapboxgl.Marker[] = []; markers: mapboxgl.Marker[] = [];
selected: boolean = false; selected: boolean = false;
draggable: boolean; draggable: boolean;
@@ -165,6 +164,15 @@ export class GPXLayer {
this.map.removeLayer(this.fileId + '-direction'); this.map.removeLayer(this.fileId + '-direction');
} }
} }
let visibleItems: [number, number][] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
if (!segment._data.hidden) {
visibleItems.push([trackIndex, segmentIndex]);
}
});
this.map.setFilter(this.fileId, ['any', ...visibleItems.map(([trackIndex, segmentIndex]) => ['all', ['==', 'trackIndex', trackIndex], ['==', 'segmentIndex', segmentIndex]])], { validate: false });
} catch (e) { // No reliable way to check if the map is ready to add sources and layers } catch (e) { // No reliable way to check if the map is ready to add sources and layers
return; return;
} }
@@ -244,7 +252,11 @@ export class GPXLayer {
} }
this.markers.forEach((marker) => { this.markers.forEach((marker) => {
if (!marker._waypoint._data.hidden) {
marker.addTo(this.map); marker.addTo(this.map);
} else {
marker.remove();
}
}); });
} }
@@ -366,17 +378,6 @@ export class GPXLayer {
} }
} }
toggleVisibility() {
this.hidden = !this.hidden;
if (this.hidden) {
this.map.setLayoutProperty(this.fileId, 'visibility', 'none');
this.markers.forEach(marker => marker.remove());
} else {
this.map.setLayoutProperty(this.fileId, 'visibility', 'visible');
this.markers.forEach(marker => marker.addTo(this.map));
}
}
getGeoJSON(): GeoJSON.FeatureCollection { getGeoJSON(): GeoJSON.FeatureCollection {
let file = get(this.file)?.file; let file = get(this.file)?.file;
if (!file) { if (!file) {

View File

@@ -2,7 +2,7 @@ import Dexie, { liveQuery } from 'dexie';
import { GPXFile, GPXStatistics, Track, TrackSegment, Waypoint, TrackPoint, type Coordinates, distance, type LineStyleExtension } from 'gpx'; import { GPXFile, GPXStatistics, Track, TrackSegment, Waypoint, TrackPoint, type Coordinates, distance, type LineStyleExtension } from 'gpx';
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, freeze, produceWithPatches, original, produce } from 'immer'; import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, freeze, produceWithPatches, original, produce } from 'immer';
import { writable, get, derived, type Readable, type Writable } from 'svelte/store'; import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
import { gpxStatistics, initTargetMapBounds, splitAs, updateTargetMapBounds } from './stores'; import { gpxStatistics, initTargetMapBounds, splitAs, updateAllHidden, updateTargetMapBounds } from './stores';
import { mode } from 'mode-watcher'; import { mode } from 'mode-watcher';
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer, defaultOpacities } from './assets/layers'; import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer, defaultOpacities } from './assets/layers';
import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection'; import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
@@ -192,6 +192,10 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
file: gpx, file: gpx,
statistics statistics
}); });
if (get(selection).hasAnyChildren(new ListFileItem(id))) {
updateAllHidden();
}
} }
}); });
return { return {
@@ -203,10 +207,61 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
}; };
} }
function updateSelection(updatedFiles: GPXFile[], deletedFileIds: string[]) {
let removedItems: ListItem[] = [];
applyToOrderedItemsFromFile(get(selection).getSelected(), (fileId, level, items) => {
let file = updatedFiles.find((file) => file._data.id === fileId);
if (file) {
items.forEach((item) => {
if (item instanceof ListTrackItem) {
let newTrackIndex = file.trk.findIndex((track) => track._data.trackIndex === item.getTrackIndex());
if (newTrackIndex === -1) {
removedItems.push(item);
}
} else if (item instanceof ListTrackSegmentItem) {
let newTrackIndex = file.trk.findIndex((track) => track._data.trackIndex === item.getTrackIndex());
if (newTrackIndex === -1) {
removedItems.push(item);
} else {
let newSegmentIndex = file.trk[newTrackIndex].trkseg.findIndex((segment) => segment._data.segmentIndex === item.getSegmentIndex());
if (newSegmentIndex === -1) {
removedItems.push(item);
}
}
} else if (item instanceof ListWaypointItem) {
let newWaypointIndex = file.wpt.findIndex((wpt) => wpt._data.index === item.getWaypointIndex());
if (newWaypointIndex === -1) {
removedItems.push(item);
}
}
});
} else if (deletedFileIds.includes(fileId)) {
items.forEach((item) => {
removedItems.push(item);
});
}
});
if (removedItems.length > 0) {
selection.update(($selection) => {
removedItems.forEach((item) => {
if (item instanceof ListFileItem) {
$selection.deleteChild(item.getFileId());
} else {
$selection.set(item, false);
}
});
return $selection;
});
}
}
// Commit the changes to the file state to the database // Commit the changes to the file state to the database
function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) { function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) {
let changedFileIds = getChangedFileIds(patch); let changedFileIds = getChangedFileIds(patch);
let updatedFileIds: string[] = [], deletedFileIds: string[] = []; let updatedFileIds: string[] = [], deletedFileIds: string[] = [];
changedFileIds.forEach(id => { changedFileIds.forEach(id => {
if (newFileState.has(id)) { if (newFileState.has(id)) {
updatedFileIds.push(id); updatedFileIds.push(id);
@@ -218,6 +273,8 @@ function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch
let updatedFiles = updatedFileIds.map(id => newFileState.get(id)).filter(file => file !== undefined) as GPXFile[]; let updatedFiles = updatedFileIds.map(id => newFileState.get(id)).filter(file => file !== undefined) as GPXFile[];
updatedFileIds = updatedFiles.map(file => file._data.id); updatedFileIds = updatedFiles.map(file => file._data.id);
updateSelection(updatedFiles, deletedFileIds);
return db.transaction('rw', db.fileids, db.files, async () => { return db.transaction('rw', db.fileids, db.files, async () => {
if (updatedFileIds.length > 0) { if (updatedFileIds.length > 0) {
await db.fileids.bulkPut(updatedFileIds, updatedFileIds); await db.fileids.bulkPut(updatedFileIds, updatedFileIds);
@@ -255,14 +312,6 @@ liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
}); });
return $files; return $files;
}); });
if (deletedFiles.length > 0) {
selection.update(($selection) => {
deletedFiles.forEach((fileId) => {
$selection.deleteChild(fileId);
});
return $selection;
});
}
settings.fileOrder.update((order) => { settings.fileOrder.update((order) => {
newFiles.forEach((fileId) => { newFiles.forEach((fileId) => {
if (!order.includes(fileId)) { if (!order.includes(fileId)) {
@@ -427,7 +476,7 @@ export const dbUtils = {
let ids = getFileIds(get(settings.fileOrder).length); let ids = getFileIds(get(settings.fileOrder).length);
let index = 0; let index = 0;
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
@@ -467,7 +516,7 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
@@ -504,7 +553,7 @@ export const dbUtils = {
wpt: [] wpt: []
}; };
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
@@ -616,7 +665,7 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
let length = file.getNumberOfTrackPoints(); let length = file.getNumberOfTrackPoints();
@@ -645,7 +694,7 @@ export const dbUtils = {
extractSelection: () => { extractSelection: () => {
return applyGlobal((draft) => { return applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
if (file.trk.length > 1) { if (file.trk.length > 1) {
@@ -678,7 +727,9 @@ export const dbUtils = {
let tracks = track.trkseg.map((segment, segmentIndex) => { let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0]; let t = track.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) { if (track.name) {
t = produce(t, (t) => {
t.name = `${track.name} (${segmentIndex + 1})`; t.name = `${track.name} (${segmentIndex + 1})`;
});
} }
return t; return t;
}); });
@@ -734,7 +785,9 @@ export const dbUtils = {
let tracks = track.trkseg.map((segment, segmentIndex) => { let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.clone().replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0]; let t = track.clone().replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) { if (track.name) {
t = produce(t, (t) => {
t.name = `${track.name} (${segmentIndex + 1})`; t.name = `${track.name} (${segmentIndex + 1})`;
});
} }
return t; return t;
}); });
@@ -749,7 +802,7 @@ export const dbUtils = {
split(fileId: string, trackIndex: number, segmentIndex: number, coordinates: Coordinates) { split(fileId: string, trackIndex: number, segmentIndex: number, coordinates: Coordinates) {
let splitType = get(splitAs); let splitType = get(splitAs);
return applyGlobal((draft) => { return applyGlobal((draft) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let segment = file.trk[trackIndex].trkseg[segmentIndex]; let segment = file.trk[trackIndex].trkseg[segmentIndex];
@@ -794,7 +847,7 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
@@ -824,7 +877,7 @@ export const dbUtils = {
applyGlobal((draft) => { applyGlobal((draft) => {
let allItems = Array.from(itemsAndPoints.keys()); let allItems = Array.from(itemsAndPoints.keys());
applyToOrderedItemsFromFile(allItems, (fileId, level, items) => { applyToOrderedItemsFromFile(allItems, (fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
for (let item of items) { for (let item of items) {
@@ -848,7 +901,7 @@ export const dbUtils = {
} }
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) { if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
@@ -867,6 +920,35 @@ export const dbUtils = {
}); });
}); });
}, },
setHiddenToSelection: (hidden: boolean) => {
if (get(selection).size === 0) {
return;
}
applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.FILE) {
newFile = file.setHidden(hidden);
} else if (level === ListLevel.TRACK) {
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
newFile = newFile.setHidden(hidden, trackIndices);
} else if (level === ListLevel.SEGMENT) {
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
newFile = newFile.setHidden(hidden, trackIndices, segmentIndices);
} else if (level === ListLevel.WAYPOINTS) {
newFile = newFile.setHiddenWaypoints(hidden);
} else if (level === ListLevel.WAYPOINT) {
let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex());
newFile = newFile.setHiddenWaypoints(hidden, waypointIndices);
}
draft.set(newFile._data.id, freeze(newFile));
}
});
});
},
deleteSelection: () => { deleteSelection: () => {
if (get(selection).size === 0) { if (get(selection).size === 0) {
return; return;
@@ -876,7 +958,7 @@ export const dbUtils = {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
draft.delete(fileId); draft.delete(fileId);
} else { } else {
let file = original(draft)?.get(fileId); let file = getFile(fileId);
if (file) { if (file) {
let newFile = file; let newFile = file;
if (level === ListLevel.TRACK) { if (level === ListLevel.TRACK) {

View File

@@ -344,56 +344,35 @@ export function exportFile(file: GPXFile) {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
export const anyHidden = writable(false); export const allHidden = writable(false);
function updateAnyHidden() {
anyHidden.set(get(selection).getSelected().some((item) => {
let layer = gpxLayers.get(item.getFileId());
return layer && layer.hidden;
}));
}
selection.subscribe(updateAnyHidden);
export function toggleSelectionVisibility() { export function updateAllHidden() {
let files = new Set<string>(); let hidden = true;
get(selection).forEach((item) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
files.add(item.getFileId()); let file = getFile(fileId);
}); if (file) {
files.forEach((fileId) => { for (let item of items) {
let layer = gpxLayers.get(fileId); if (!hidden) {
if (layer) { return;
layer.toggleVisibility(); }
if (item instanceof ListFileItem) {
hidden = hidden && (file._data.hidden === true);
} else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
hidden = hidden && (file.trk[item.getTrackIndex()]._data.hidden === true);
} else if (item instanceof ListTrackSegmentItem && item.getTrackIndex() < file.trk.length && item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length) {
hidden = hidden && (file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true);
} else if (item instanceof ListWaypointsItem) {
hidden = hidden && (file._data.hiddenWpt === true);
} else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
hidden = hidden && (file.wpt[item.getWaypointIndex()]._data.hidden === true);
}
}
} }
}); });
updateAnyHidden(); allHidden.set(hidden);
}
export function hideSelection() {
let files = new Set<string>();
get(selection).forEach((item) => {
files.add(item.getFileId());
});
files.forEach((fileId) => {
let layer = gpxLayers.get(fileId);
if (layer && !layer.hidden) {
layer.toggleVisibility();
}
});
anyHidden.set(true);
}
export function showSelection() {
let files = new Set<string>();
get(selection).forEach((item) => {
files.add(item.getFileId());
});
files.forEach((fileId) => {
let layer = gpxLayers.get(fileId);
if (layer && layer.hidden) {
layer.toggleVisibility();
}
});
anyHidden.set(false);
} }
selection.subscribe(updateAllHidden);
export const editMetadata = writable(false); export const editMetadata = writable(false);
export const editStyle = writable(false); export const editStyle = writable(false);