sortable file list, to be fixed

This commit is contained in:
vcoppe
2025-11-02 16:01:17 +01:00
parent 722cf58486
commit ec3eb387e5
2 changed files with 103 additions and 104 deletions

View File

@@ -53,6 +53,12 @@
); );
}); });
$effect(() => {
if (sortable) {
sortable.updateElements();
}
});
onDestroy(() => { onDestroy(() => {
sortable.destroy(); sortable.destroy();
}); });

View File

@@ -1,5 +1,5 @@
import { isMac } from '$lib/utils'; 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 { ListItem, ListLevel, ListRootItem } from './file-list';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
import { getFileIds, moveItems } from '$lib/logic/file-actions'; import { getFileIds, moveItems } from '$lib/logic/file-actions';
@@ -41,6 +41,7 @@ export class SortableFileList {
private _container: HTMLElement; private _container: HTMLElement;
private _sortable: Sortable | null = null; private _sortable: Sortable | null = null;
private _elements: { [id: string]: HTMLElement } = {}; private _elements: { [id: string]: HTMLElement } = {};
private _updatingSelection: boolean = false;
private _unsubscribes: (() => void)[] = []; private _unsubscribes: (() => void)[] = [];
constructor( constructor(
@@ -70,75 +71,13 @@ export class SortableFileList {
multiDrag: true, multiDrag: true,
multiDragKey: isMac() ? 'Meta' : 'Ctrl', multiDragKey: isMac() ? 'Meta' : 'Ctrl',
avoidImplicitDeselect: true, avoidImplicitDeselect: true,
onSelect: (e) => this.updateToSelection(e), onSelect: (e: Sortable.SortableEvent) =>
onDeselect: (e) => this.updateToSelection(e), setTimeout(() => this.updateToSelection(e), 50),
onStart: () => { onDeselect: (e: Sortable.SortableEvent) =>
dragging.set(sortableLevel); setTimeout(() => this.updateToSelection(e), 50),
}, onStart: () => dragging.set(sortableLevel),
onEnd: () => { onEnd: () => dragging.set(null),
dragging.set(null); onSort: (e: Sortable.SortableEvent) => this.onSort(e),
},
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);
}
},
}); });
Object.defineProperty(this._sortable, '_item', { Object.defineProperty(this._sortable, '_item', {
value: item, value: item,
@@ -154,43 +93,67 @@ export class SortableFileList {
this._unsubscribes.push(fileOrder.subscribe(() => this.updateFromFileOrder())); this._unsubscribes.push(fileOrder.subscribe(() => this.updateFromFileOrder()));
} }
updateToSelection(e: Sortable.SortableEvent) { onSort(e: Sortable.SortableEvent) {
console.log('updateToSelection', e); this.updateToFileOrder();
let changed = this.getChangedIds(); const from = Sortable.get(e.from);
if (changed.length > 0) { const to = Sortable.get(e.to);
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 ( if (!from || !to) {
e.originalEvent && return;
!( }
e.originalEvent.ctrlKey ||
e.originalEvent.metaKey || let fromItem = from._item;
e.originalEvent.shiftKey let toItem = to._item;
) &&
($selection.size > 1 || if (this._item === toItem && !(fromItem instanceof ListRootItem)) {
!$selection.has(this._item.extend(this.getRealId(changed[0])))) // Event is triggered on source and destination list, only handle it once
) { let fromItems = [];
// Fix bug that sometimes causes a single select to be treated as a multi-select let toItems = [];
$selection.clear();
$selection.set(this._item.extend(this.getRealId(changed[0])), true); 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() { updateFromSelection() {
console.log('updateFromSelection'); const changed = this.getChangedIds();
let changed = this.getChangedIds(); if (changed.length === 0) {
return;
}
const selection_ = get(selection); const selection_ = get(selection);
for (let id of changed) { for (let id of changed) {
let element = this._elements[id]; 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() { updateFromFileOrder() {
if (!this._sortable || this._sortableLevel !== ListLevel.FILE) { if (!this._sortable || this._sortableLevel !== ListLevel.FILE) {
return; return;
} }
console.log('updateFromFileOrder');
const fileOrder_ = get(fileOrder); const fileOrder_ = get(fileOrder);
const sortableOrder = this._sortable.toArray(); const sortableOrder = this._sortable.toArray();
@@ -229,7 +226,6 @@ export class SortableFileList {
if (!this._sortable || this._sortableLevel !== ListLevel.FILE) { if (!this._sortable || this._sortableLevel !== ListLevel.FILE) {
return; return;
} }
console.log('updateToFileOrder');
const fileOrder_ = get(fileOrder); const fileOrder_ = get(fileOrder);
const sortableOrder = this._sortable.toArray(); const sortableOrder = this._sortable.toArray();
@@ -256,9 +252,6 @@ export class SortableFileList {
} }
} }
}); });
// syncFileOrder();
// updateFromSelection();
} }
destroy() { destroy() {