mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 16:52:31 +00:00
sync and cache file order
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { GPXFile, Track, Waypoint, type AnyGPXTreeElement, type GPXTreeElement } from 'gpx';
|
import { GPXFile, Track, Waypoint, type AnyGPXTreeElement, type GPXTreeElement } from 'gpx';
|
||||||
import { getContext, onDestroy, onMount } from 'svelte';
|
import { afterUpdate, getContext, onDestroy, onMount } from 'svelte';
|
||||||
import Sortable from 'sortablejs/Sortable';
|
import Sortable from 'sortablejs/Sortable';
|
||||||
import type { GPXFileWithStatistics } from '$lib/db';
|
import { settings, type GPXFileWithStatistics } from '$lib/db';
|
||||||
import type { Readable, Writable } from 'svelte/store';
|
import { get, 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 FileListNodeLabel from './FileListNodeLabel.svelte';
|
import FileListNodeLabel from './FileListNodeLabel.svelte';
|
||||||
@@ -50,6 +50,25 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncFileOrder() {
|
||||||
|
if (sortableLevel !== 'file') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentOrder = sortable.toArray();
|
||||||
|
if (currentOrder.length !== $fileOrder.length) {
|
||||||
|
sortable.sort($fileOrder);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < currentOrder.length; i++) {
|
||||||
|
if (currentOrder[i] !== $fileOrder[i]) {
|
||||||
|
sortable.sort($fileOrder);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fileOrder } = settings;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
sortable = Sortable.create(container, {
|
sortable = Sortable.create(container, {
|
||||||
group: {
|
group: {
|
||||||
@@ -61,10 +80,36 @@
|
|||||||
avoidImplicitDeselect: true,
|
avoidImplicitDeselect: true,
|
||||||
onSelect: onSelectChange,
|
onSelect: onSelectChange,
|
||||||
onDeselect: onSelectChange,
|
onDeselect: onSelectChange,
|
||||||
sort: sortableLevel !== 'waypoint'
|
sort: sortableLevel !== 'waypoint',
|
||||||
|
onSort: () => {
|
||||||
|
if (sortableLevel !== 'file') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newFileOrder = sortable.toArray();
|
||||||
|
if (newFileOrder.length !== get(fileOrder).length) {
|
||||||
|
fileOrder.set(newFileOrder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < newFileOrder.length; i++) {
|
||||||
|
if (newFileOrder[i] !== get(fileOrder)[i]) {
|
||||||
|
fileOrder.set(newFileOrder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$: if ($fileOrder && sortable) {
|
||||||
|
syncFileOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
syncFileOrder();
|
||||||
|
});
|
||||||
|
|
||||||
const unsubscribe = selected.subscribe(($selected) => {
|
const unsubscribe = selected.subscribe(($selected) => {
|
||||||
Object.entries(items).forEach(([id, item]) => {
|
Object.entries(items).forEach(([id, item]) => {
|
||||||
if ($selected.has(id) && !item.classList.contains('sortable-selected')) {
|
if ($selected.has(id) && !item.classList.contains('sortable-selected')) {
|
||||||
@@ -98,7 +143,7 @@
|
|||||||
>
|
>
|
||||||
{#if node instanceof Map}
|
{#if node instanceof Map}
|
||||||
{#each node as [fileId, file]}
|
{#each node as [fileId, file]}
|
||||||
<div bind:this={items[fileId]}>
|
<div bind:this={items[fileId]} data-id={fileId}>
|
||||||
<FileListNodeStore {file} />
|
<FileListNodeStore {file} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@@ -2,7 +2,7 @@ import Dexie, { liveQuery } from 'dexie';
|
|||||||
import { GPXFile, GPXStatistics } from 'gpx';
|
import { GPXFile, GPXStatistics } from 'gpx';
|
||||||
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, Immer } from 'immer';
|
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, Immer } 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 { fileOrder, initTargetMapBounds, selectedFiles, updateTargetMapBounds } from './stores';
|
import { initTargetMapBounds, selectedFiles, updateTargetMapBounds } from './stores';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers';
|
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers';
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class Database extends Dexie {
|
|||||||
const db = new Database();
|
const db = new Database();
|
||||||
|
|
||||||
// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber, and updates to the store are pushed to the DB
|
// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber, and updates to the store are pushed to the DB
|
||||||
function dexieSettingStore(setting: string, initial: any): Writable<any> {
|
function dexieSettingStore<T>(setting: string, initial: T): Writable<T> {
|
||||||
let store = writable(initial);
|
let store = writable(initial);
|
||||||
liveQuery(() => db.settings.get(setting)).subscribe(value => {
|
liveQuery(() => db.settings.get(setting)).subscribe(value => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
@@ -94,6 +94,7 @@ export const settings = {
|
|||||||
selectedOverlayTree: dexieSettingStore('selectedOverlayTree', defaultOverlayTree),
|
selectedOverlayTree: dexieSettingStore('selectedOverlayTree', defaultOverlayTree),
|
||||||
directionMarkers: dexieSettingStore('directionMarkers', false),
|
directionMarkers: dexieSettingStore('directionMarkers', false),
|
||||||
distanceMarkers: dexieSettingStore('distanceMarkers', false),
|
distanceMarkers: dexieSettingStore('distanceMarkers', false),
|
||||||
|
fileOrder: dexieSettingStore<string[]>('fileOrder', []),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber
|
// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber
|
||||||
@@ -304,12 +305,12 @@ export const dbUtils = {
|
|||||||
applyToFiles([id], callback);
|
applyToFiles([id], callback);
|
||||||
},
|
},
|
||||||
applyToSelectedFiles: (callback: (file: WritableDraft<GPXFile>) => GPXFile) => {
|
applyToSelectedFiles: (callback: (file: WritableDraft<GPXFile>) => GPXFile) => {
|
||||||
applyToFiles(get(fileOrder).filter(fileId => get(selectedFiles).has(fileId)), callback);
|
applyToFiles(get(settings.fileOrder).filter(fileId => get(selectedFiles).has(fileId)), callback);
|
||||||
},
|
},
|
||||||
duplicateSelectedFiles: () => {
|
duplicateSelectedFiles: () => {
|
||||||
applyGlobal((draft) => {
|
applyGlobal((draft) => {
|
||||||
let ids = getFileIds(get(fileOrder).length);
|
let ids = getFileIds(get(settings.fileOrder).length);
|
||||||
get(fileOrder).forEach((fileId, index) => {
|
get(settings.fileOrder).forEach((fileId, index) => {
|
||||||
if (get(selectedFiles).has(fileId)) {
|
if (get(selectedFiles).has(fileId)) {
|
||||||
let file = draft.get(fileId);
|
let file = draft.get(fileId);
|
||||||
if (file) {
|
if (file) {
|
||||||
|
@@ -5,14 +5,14 @@ import { GPXFile, buildGPX, parseGPX, GPXStatistics, type Coordinates } from 'gp
|
|||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
|
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
|
||||||
import { dbUtils, fileObservers } from './db';
|
import { dbUtils, fileObservers, settings } from './db';
|
||||||
|
|
||||||
export const map = writable<mapboxgl.Map | null>(null);
|
export const map = writable<mapboxgl.Map | null>(null);
|
||||||
|
|
||||||
export const fileOrder = writable<string[]>([]);
|
|
||||||
export const selectedFiles = writable<Set<string>>(new Set());
|
export const selectedFiles = writable<Set<string>>(new Set());
|
||||||
export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
|
export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
|
||||||
|
|
||||||
|
const { fileOrder } = settings;
|
||||||
|
|
||||||
fileObservers.subscribe((files) => { // Update selectedFiles automatically when files are deleted (either by action or by undo-redo)
|
fileObservers.subscribe((files) => { // Update selectedFiles automatically when files are deleted (either by action or by undo-redo)
|
||||||
let deletedFileIds: string[] = [];
|
let deletedFileIds: string[] = [];
|
||||||
get(selectedFiles).forEach((fileId) => {
|
get(selectedFiles).forEach((fileId) => {
|
||||||
|
Reference in New Issue
Block a user