From ec3eb387e501acd3ef500d73af7204acd07519d2 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Sun, 2 Nov 2025 16:01:17 +0100 Subject: [PATCH] sortable file list, to be fixed --- .../file-list/FileListNodeContent.svelte | 6 + .../file-list/sortable-file-list.ts | 201 +++++++++--------- 2 files changed, 103 insertions(+), 104 deletions(-) diff --git a/website/src/lib/components/file-list/FileListNodeContent.svelte b/website/src/lib/components/file-list/FileListNodeContent.svelte index d4f340363..1d7d63848 100644 --- a/website/src/lib/components/file-list/FileListNodeContent.svelte +++ b/website/src/lib/components/file-list/FileListNodeContent.svelte @@ -53,6 +53,12 @@ ); }); + $effect(() => { + if (sortable) { + sortable.updateElements(); + } + }); + onDestroy(() => { sortable.destroy(); }); diff --git a/website/src/lib/components/file-list/sortable-file-list.ts b/website/src/lib/components/file-list/sortable-file-list.ts index 302ce163d..bc67d8369 100644 --- a/website/src/lib/components/file-list/sortable-file-list.ts +++ b/website/src/lib/components/file-list/sortable-file-list.ts @@ -1,5 +1,5 @@ import { isMac } from '$lib/utils'; -import Sortable, { type Direction } from 'sortablejs'; +import Sortable, { type Direction } from 'sortablejs/Sortable'; import { ListItem, ListLevel, ListRootItem } from './file-list'; import { selection } from '$lib/logic/selection'; import { getFileIds, moveItems } from '$lib/logic/file-actions'; @@ -41,6 +41,7 @@ export class SortableFileList { private _container: HTMLElement; private _sortable: Sortable | null = null; private _elements: { [id: string]: HTMLElement } = {}; + private _updatingSelection: boolean = false; private _unsubscribes: (() => void)[] = []; constructor( @@ -70,75 +71,13 @@ export class SortableFileList { multiDrag: true, multiDragKey: isMac() ? 'Meta' : 'Ctrl', avoidImplicitDeselect: true, - onSelect: (e) => this.updateToSelection(e), - onDeselect: (e) => this.updateToSelection(e), - onStart: () => { - dragging.set(sortableLevel); - }, - onEnd: () => { - dragging.set(null); - }, - onSort: (e: Sortable.SortableEvent) => { - this.updateToFileOrder(); - - const from = Sortable.get(e.from); - const to = Sortable.get(e.to); - - if (!from || !to) { - return; - } - - let fromItem = from._item; - let toItem = to._item; - - console.log('onSort', e); - - if (item === toItem && !(fromItem instanceof ListRootItem)) { - // Event is triggered on source and destination list, only handle it once - let fromItems = []; - let toItems = []; - - if (from._waypointRoot) { - fromItems = [fromItem.extend('waypoints')]; - } else { - let oldIndices: number[] = - e.oldIndicies.length > 0 - ? e.oldIndicies.map((i) => i.index) - : [e.oldIndex]; - oldIndices = oldIndices.filter((i) => i >= 0); - oldIndices.sort((a, b) => a - b); - - fromItems = oldIndices.map((i) => fromItem.extend(i)); - } - - if (from._waypointRoot && to._waypointRoot) { - toItems = [toItem.extend('waypoints')]; - } else { - if (to._waypointRoot) { - toItem = toItem.extend('waypoints'); - } - - let newIndices: number[] = - e.newIndicies.length > 0 - ? e.newIndicies.map((i) => i.index) - : [e.newIndex]; - newIndices = newIndices.filter((i) => i >= 0); - newIndices.sort((a, b) => a - b); - - if (toItem instanceof ListRootItem) { - let newFileIds = getFileIds(newIndices.length); - toItems = newIndices.map((i, index) => { - get(fileOrder).splice(i, 0, newFileIds[index]); - return item.extend(newFileIds[index]); - }); - } else { - toItems = newIndices.map((i) => toItem.extend(i)); - } - } - - moveItems(fromItem, toItem, fromItems, toItems); - } - }, + onSelect: (e: Sortable.SortableEvent) => + setTimeout(() => this.updateToSelection(e), 50), + onDeselect: (e: Sortable.SortableEvent) => + setTimeout(() => this.updateToSelection(e), 50), + onStart: () => dragging.set(sortableLevel), + onEnd: () => dragging.set(null), + onSort: (e: Sortable.SortableEvent) => this.onSort(e), }); Object.defineProperty(this._sortable, '_item', { value: item, @@ -154,43 +93,67 @@ export class SortableFileList { this._unsubscribes.push(fileOrder.subscribe(() => this.updateFromFileOrder())); } - updateToSelection(e: Sortable.SortableEvent) { - console.log('updateToSelection', e); + onSort(e: Sortable.SortableEvent) { + this.updateToFileOrder(); - let changed = this.getChangedIds(); - if (changed.length > 0) { - selection.update(($selection) => { - $selection.clear(); - Object.entries(this._elements).forEach(([id, element]) => { - $selection.set( - this._item.extend(this.getRealId(id)), - element.classList.contains('sortable-selected') - ); - }); + const from = Sortable.get(e.from); + const to = Sortable.get(e.to); - if ( - e.originalEvent && - !( - e.originalEvent.ctrlKey || - e.originalEvent.metaKey || - e.originalEvent.shiftKey - ) && - ($selection.size > 1 || - !$selection.has(this._item.extend(this.getRealId(changed[0])))) - ) { - // Fix bug that sometimes causes a single select to be treated as a multi-select - $selection.clear(); - $selection.set(this._item.extend(this.getRealId(changed[0])), true); + if (!from || !to) { + return; + } + + let fromItem = from._item; + let toItem = to._item; + + if (this._item === toItem && !(fromItem instanceof ListRootItem)) { + // Event is triggered on source and destination list, only handle it once + let fromItems = []; + let toItems = []; + + if (from._waypointRoot) { + fromItems = [fromItem.extend('waypoints')]; + } else { + let oldIndices: number[] = + e.oldIndicies.length > 0 ? e.oldIndicies.map((i) => i.index) : [e.oldIndex]; + oldIndices = oldIndices.filter((i) => i >= 0); + oldIndices.sort((a, b) => a - b); + + fromItems = oldIndices.map((i) => fromItem.extend(i)); + } + + if (from._waypointRoot && to._waypointRoot) { + toItems = [toItem.extend('waypoints')]; + } else { + if (to._waypointRoot) { + toItem = toItem.extend('waypoints'); } - return $selection; - }); + let newIndices: number[] = + e.newIndicies.length > 0 ? e.newIndicies.map((i) => i.index) : [e.newIndex]; + newIndices = newIndices.filter((i) => i >= 0); + newIndices.sort((a, b) => a - b); + + if (toItem instanceof ListRootItem) { + let newFileIds = getFileIds(newIndices.length); + toItems = newIndices.map((i, index) => { + get(fileOrder).splice(i, 0, newFileIds[index]); + return this._item.extend(newFileIds[index]); + }); + } else { + toItems = newIndices.map((i) => toItem.extend(i)); + } + } + + moveItems(fromItem, toItem, fromItems, toItems); } } updateFromSelection() { - console.log('updateFromSelection'); - let changed = this.getChangedIds(); + const changed = this.getChangedIds(); + if (changed.length === 0) { + return; + } const selection_ = get(selection); for (let id of changed) { let element = this._elements[id]; @@ -208,11 +171,45 @@ export class SortableFileList { } } + updateToSelection(e: Sortable.SortableEvent) { + if (this._updatingSelection) { + return; + } + this._updatingSelection = true; + const changed = this.getChangedIds(); + if (changed.length == 0) { + this._updatingSelection = false; + return; + } + selection.update(($selection) => { + $selection.clear(); + Object.entries(this._elements).forEach(([id, element]) => { + $selection.set( + this._item.extend(this.getRealId(id)), + element.classList.contains('sortable-selected') + ); + }); + + if ( + e.originalEvent && + !(e.originalEvent.ctrlKey || e.originalEvent.metaKey || e.originalEvent.shiftKey) && + ($selection.size > 1 || + !$selection.has(this._item.extend(this.getRealId(changed[0])))) + ) { + // Fix bug that sometimes causes a single select to be treated as a multi-select + $selection.clear(); + $selection.set(this._item.extend(this.getRealId(changed[0])), true); + } + + return $selection; + }); + this._updatingSelection = false; + } + updateFromFileOrder() { if (!this._sortable || this._sortableLevel !== ListLevel.FILE) { return; } - console.log('updateFromFileOrder'); const fileOrder_ = get(fileOrder); const sortableOrder = this._sortable.toArray(); @@ -229,7 +226,6 @@ export class SortableFileList { if (!this._sortable || this._sortableLevel !== ListLevel.FILE) { return; } - console.log('updateToFileOrder'); const fileOrder_ = get(fileOrder); const sortableOrder = this._sortable.toArray(); @@ -256,9 +252,6 @@ export class SortableFileList { } } }); - - // syncFileOrder(); - // updateFromSelection(); } destroy() {