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) {
this.attributes = gpx.attributes
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)) : [];
if (gpx.hasOwnProperty('_data')) {
this._data = gpx._data;
@@ -134,6 +134,17 @@ export class GPXFile extends GPXTreeNode<Track>{
this.wpt = [];
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> {
@@ -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
@@ -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
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 {
@@ -985,7 +1075,7 @@ export class Waypoint {
type?: string;
_data: { [key: string]: any } = {};
constructor(waypoint: WaypointType & { _data?: any } | Waypoint, index?: number) {
constructor(waypoint: WaypointType & { _data?: any } | Waypoint) {
this.attributes = waypoint.attributes;
this.ele = waypoint.ele;
this.time = waypoint.time;
@@ -998,9 +1088,6 @@ export class Waypoint {
if (waypoint.hasOwnProperty('_data')) {
this._data = waypoint._data;
}
if (index !== undefined) {
this._data['index'] = index;
}
}
getCoordinates(): Coordinates {
@@ -1032,6 +1119,13 @@ export class Waypoint {
type: this.type,
});
}
// Producers
setHidden(hidden: boolean) {
return produce(this, (draft) => {
draft._data.hidden = hidden;
});
}
}
export class GPXStatistics {

View File

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

View File

@@ -5,8 +5,8 @@
import { fileObservers, settings } from '$lib/db';
import { setContext } from 'svelte';
import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList';
import { copied, pasteSelection, selection } from './Selection';
import { ClipboardPaste, Plus } from 'lucide-svelte';
import { copied, pasteSelection, selectAll, selection } from './Selection';
import { ClipboardPaste, FileStack, Plus } from 'lucide-svelte';
import Shortcut from '$lib/components/Shortcut.svelte';
import { _ } from 'svelte-i18n';
import { createFile } from '$lib/stores';
@@ -66,6 +66,12 @@
<Shortcut key="+" ctrl={true} />
</ContextMenu.Item>
<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
disabled={$copied === undefined ||
$copied.length === 0 ||

View File

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

View File

@@ -12,14 +12,21 @@
import { get, writable, type Readable, type Writable } from 'svelte/store';
import FileListNodeStore from './FileListNodeStore.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 { _ } from 'svelte-i18n';
export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement>
| ReadonlyArray<Readonly<Waypoint>>
| Readonly<Waypoint>;
export let item: ListItem;
export let waypointRoot: boolean = false;
@@ -32,7 +39,9 @@
: node instanceof GPXFile
? waypointRoot
? ListLevel.WAYPOINTS
: ListLevel.TRACK
: item instanceof ListWaypointsItem
? ListLevel.WAYPOINT
: ListLevel.TRACK
: node instanceof Track
? ListLevel.SEGMENT
: ListLevel.WAYPOINT;
@@ -268,10 +277,16 @@
</div>
{/each}
{: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}
<div data-id="waypoints">
<FileListNode node={node.wpt} item={item.extend('waypoints')} />
<FileListNode {node} item={item.extend('waypoints')} />
</div>
{/if}
{:else}
@@ -287,16 +302,10 @@
<FileListNode node={child} item={item.extend(i)} />
</div>
{/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}
</div>
{#if node instanceof GPXFile}
{#if node instanceof GPXFile && item instanceof ListFileItem}
{#if !waypointRoot}
<svelte:self {node} {item} waypointRoot={true} />
{/if}

View File

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

View File

@@ -50,7 +50,6 @@ export class GPXLayer {
fileId: string;
file: Readable<GPXFileWithStatistics | undefined>;
layerColor: string;
hidden: boolean = false;
markers: mapboxgl.Marker[] = [];
selected: boolean = false;
draggable: boolean;
@@ -165,6 +164,15 @@ export class GPXLayer {
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
return;
}
@@ -244,7 +252,11 @@ export class GPXLayer {
}
this.markers.forEach((marker) => {
marker.addTo(this.map);
if (!marker._waypoint._data.hidden) {
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 {
let file = get(this.file)?.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 { 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 { gpxStatistics, initTargetMapBounds, splitAs, updateTargetMapBounds } from './stores';
import { gpxStatistics, initTargetMapBounds, splitAs, updateAllHidden, updateTargetMapBounds } from './stores';
import { mode } from 'mode-watcher';
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays, type CustomLayer, defaultOpacities } from './assets/layers';
import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selection } from '$lib/components/file-list/Selection';
@@ -192,6 +192,10 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
file: gpx,
statistics
});
if (get(selection).hasAnyChildren(new ListFileItem(id))) {
updateAllHidden();
}
}
});
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
function commitFileStateChange(newFileState: ReadonlyMap<string, GPXFile>, patch: Patch[]) {
let changedFileIds = getChangedFileIds(patch);
let updatedFileIds: string[] = [], deletedFileIds: string[] = [];
changedFileIds.forEach(id => {
if (newFileState.has(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[];
updatedFileIds = updatedFiles.map(file => file._data.id);
updateSelection(updatedFiles, deletedFileIds);
return db.transaction('rw', db.fileids, db.files, async () => {
if (updatedFileIds.length > 0) {
await db.fileids.bulkPut(updatedFileIds, updatedFileIds);
@@ -255,14 +312,6 @@ liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
});
return $files;
});
if (deletedFiles.length > 0) {
selection.update(($selection) => {
deletedFiles.forEach((fileId) => {
$selection.deleteChild(fileId);
});
return $selection;
});
}
settings.fileOrder.update((order) => {
newFiles.forEach((fileId) => {
if (!order.includes(fileId)) {
@@ -427,7 +476,7 @@ export const dbUtils = {
let ids = getFileIds(get(settings.fileOrder).length);
let index = 0;
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.FILE) {
@@ -467,7 +516,7 @@ export const dbUtils = {
}
applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.FILE) {
@@ -504,7 +553,7 @@ export const dbUtils = {
wpt: []
};
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.FILE) {
@@ -616,7 +665,7 @@ export const dbUtils = {
}
applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
if (level === ListLevel.FILE) {
let length = file.getNumberOfTrackPoints();
@@ -645,7 +694,7 @@ export const dbUtils = {
extractSelection: () => {
return applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
if (level === ListLevel.FILE) {
if (file.trk.length > 1) {
@@ -678,7 +727,9 @@ export const dbUtils = {
let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) {
t.name = `${track.name} (${segmentIndex + 1})`;
t = produce(t, (t) => {
t.name = `${track.name} (${segmentIndex + 1})`;
});
}
return t;
});
@@ -734,7 +785,9 @@ export const dbUtils = {
let tracks = track.trkseg.map((segment, segmentIndex) => {
let t = track.clone().replaceTrackSegments(0, track.trkseg.length - 1, [segment])[0];
if (track.name) {
t.name = `${track.name} (${segmentIndex + 1})`;
t = produce(t, (t) => {
t.name = `${track.name} (${segmentIndex + 1})`;
});
}
return t;
});
@@ -749,7 +802,7 @@ export const dbUtils = {
split(fileId: string, trackIndex: number, segmentIndex: number, coordinates: Coordinates) {
let splitType = get(splitAs);
return applyGlobal((draft) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let segment = file.trk[trackIndex].trkseg[segmentIndex];
@@ -794,7 +847,7 @@ export const dbUtils = {
}
applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.FILE) {
@@ -824,7 +877,7 @@ export const dbUtils = {
applyGlobal((draft) => {
let allItems = Array.from(itemsAndPoints.keys());
applyToOrderedItemsFromFile(allItems, (fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
for (let item of items) {
@@ -848,7 +901,7 @@ export const dbUtils = {
}
applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) {
let newFile = 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: () => {
if (get(selection).size === 0) {
return;
@@ -876,7 +958,7 @@ export const dbUtils = {
if (level === ListLevel.FILE) {
draft.delete(fileId);
} else {
let file = original(draft)?.get(fileId);
let file = getFile(fileId);
if (file) {
let newFile = file;
if (level === ListLevel.TRACK) {

View File

@@ -344,56 +344,35 @@ export function exportFile(file: GPXFile) {
URL.revokeObjectURL(url);
}
export const anyHidden = 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 const allHidden = writable(false);
export function toggleSelectionVisibility() {
let files = new Set<string>();
get(selection).forEach((item) => {
files.add(item.getFileId());
});
files.forEach((fileId) => {
let layer = gpxLayers.get(fileId);
if (layer) {
layer.toggleVisibility();
export function updateAllHidden() {
let hidden = true;
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId);
if (file) {
for (let item of items) {
if (!hidden) {
return;
}
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();
}
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);
allHidden.set(hidden);
}
selection.subscribe(updateAllHidden);
export const editMetadata = writable(false);
export const editStyle = writable(false);