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(() => {
sortable.destroy();
});

View File

@@ -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;
}
return $selection;
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');
}
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() {