mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-19 12:14:47 +02:00
fix queue bug
This commit is contained in:
@@ -56,6 +56,10 @@ import {
|
||||
setUpcoming
|
||||
} from '@/audio/playbackController';
|
||||
import { useQueue } from './useQueue';
|
||||
import {
|
||||
removeQueueEntryAt,
|
||||
resolveSelectedQueueAction,
|
||||
} from './queueActions';
|
||||
|
||||
const QUEUE_ROW_HEIGHT = 64;
|
||||
const ART = 42;
|
||||
@@ -399,9 +403,11 @@ export function QueueTray({ onClose }: QueueTrayProps) {
|
||||
|
||||
const remove = useCallback(
|
||||
(localIndex: number) => {
|
||||
const nextEntries = entriesRef.current.filter((_, index) => index !== localIndex);
|
||||
setOptimisticEntries(nextEntries);
|
||||
runAndRefresh(removeFromQueue(baseOffset + localIndex));
|
||||
const action = removeQueueEntryAt(entriesRef.current, localIndex, baseOffset);
|
||||
if (!action) return;
|
||||
|
||||
setOptimisticEntries(action.nextEntries);
|
||||
runAndRefresh(removeFromQueue(action.absoluteIndex, { updateMirror: false }));
|
||||
},
|
||||
[baseOffset, runAndRefresh, setOptimisticEntries]
|
||||
);
|
||||
@@ -420,30 +426,29 @@ export function QueueTray({ onClose }: QueueTrayProps) {
|
||||
setSelectedKeys(new Set());
|
||||
}, []);
|
||||
|
||||
const selectedAbsoluteIndices = useCallback((): number[] => {
|
||||
const indices: number[] = [];
|
||||
entriesRef.current.forEach((entry, index) => {
|
||||
if (visibleSelectedKeys.has(entry.key)) indices.push(baseOffset + index);
|
||||
});
|
||||
return indices;
|
||||
}, [baseOffset, visibleSelectedKeys]);
|
||||
|
||||
const groupPlayNext = useCallback(() => {
|
||||
const selected = new Set(visibleSelectedKeys);
|
||||
const moved = entriesRef.current.filter((entry) => selected.has(entry.key));
|
||||
const rest = entriesRef.current.filter((entry) => !selected.has(entry.key));
|
||||
setOptimisticEntries([...moved, ...rest]);
|
||||
runAndRefresh(requeueManyToTop(selectedAbsoluteIndices()));
|
||||
const action = resolveSelectedQueueAction(entriesRef.current, visibleSelectedKeys, baseOffset);
|
||||
if (action.absoluteIndices.length === 0) {
|
||||
exitEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
setOptimisticEntries(action.entriesWithSelectedFirst);
|
||||
runAndRefresh(requeueManyToTop(action.absoluteIndices));
|
||||
exitEdit();
|
||||
}, [exitEdit, runAndRefresh, selectedAbsoluteIndices, setOptimisticEntries, visibleSelectedKeys]);
|
||||
}, [baseOffset, exitEdit, runAndRefresh, setOptimisticEntries, visibleSelectedKeys]);
|
||||
|
||||
const groupRemove = useCallback(() => {
|
||||
const selected = new Set(visibleSelectedKeys);
|
||||
const nextEntries = entriesRef.current.filter((entry) => !selected.has(entry.key));
|
||||
setOptimisticEntries(nextEntries);
|
||||
runAndRefresh(removeManyFromQueue(selectedAbsoluteIndices()));
|
||||
const action = resolveSelectedQueueAction(entriesRef.current, visibleSelectedKeys, baseOffset);
|
||||
if (action.absoluteIndices.length === 0) {
|
||||
exitEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
setOptimisticEntries(action.entriesWithoutSelected);
|
||||
runAndRefresh(removeManyFromQueue(action.absoluteIndices, { updateMirror: false }));
|
||||
exitEdit();
|
||||
}, [exitEdit, runAndRefresh, selectedAbsoluteIndices, setOptimisticEntries, visibleSelectedKeys]);
|
||||
}, [baseOffset, exitEdit, runAndRefresh, setOptimisticEntries, visibleSelectedKeys]);
|
||||
|
||||
const renderBackdrop = useCallback(
|
||||
(props: BottomSheetBackdropProps) => (
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
removeQueueEntryAt,
|
||||
resolveSelectedQueueAction,
|
||||
type KeyedQueueEntry,
|
||||
} from './queueActions.ts';
|
||||
|
||||
function keyed(keys: string[]): KeyedQueueEntry[] {
|
||||
return keys.map((key) => ({ key }));
|
||||
}
|
||||
|
||||
function entryKeys(entries: readonly KeyedQueueEntry[]): string[] {
|
||||
return entries.map((entry) => entry.key);
|
||||
}
|
||||
|
||||
test('resolves swipe remove before the optimistic mirror mutation', () => {
|
||||
const upcoming = keyed(['A', 'B', 'C']);
|
||||
const action = removeQueueEntryAt(upcoming, 1, 1);
|
||||
|
||||
if (!action) throw new Error('expected a remove action');
|
||||
assert.equal(action.absoluteIndex, 2);
|
||||
assert.deepEqual(entryKeys(action.nextEntries), ['A', 'C']);
|
||||
|
||||
const mirrorAfterNativeCompletionWithSkippedUpdate = action.nextEntries;
|
||||
assert.deepEqual(entryKeys(mirrorAfterNativeCompletionWithSkippedUpdate), ['A', 'C']);
|
||||
});
|
||||
|
||||
test('captures selected absolute indices before optimistic batch mutation', () => {
|
||||
const upcoming = keyed(['A', 'B', 'C', 'D']);
|
||||
const selectedKeys = new Set(['B', 'D']);
|
||||
const action = resolveSelectedQueueAction(upcoming, selectedKeys, 1);
|
||||
|
||||
assert.deepEqual(action.absoluteIndices, [2, 4]);
|
||||
assert.deepEqual(entryKeys(action.entriesWithoutSelected), ['A', 'C']);
|
||||
assert.deepEqual(entryKeys(action.entriesWithSelectedFirst), ['B', 'D', 'A', 'C']);
|
||||
|
||||
const afterOptimisticRemoval = resolveSelectedQueueAction(
|
||||
action.entriesWithoutSelected,
|
||||
selectedKeys,
|
||||
1
|
||||
);
|
||||
assert.deepEqual(afterOptimisticRemoval.absoluteIndices, []);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
export interface KeyedQueueEntry {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface QueueItemRemoveAction<T extends KeyedQueueEntry> {
|
||||
absoluteIndex: number;
|
||||
nextEntries: T[];
|
||||
}
|
||||
|
||||
export interface SelectedQueueAction<T extends KeyedQueueEntry> {
|
||||
absoluteIndices: number[];
|
||||
entriesWithSelectedFirst: T[];
|
||||
entriesWithoutSelected: T[];
|
||||
}
|
||||
|
||||
export function removeQueueEntryAt<T extends KeyedQueueEntry>(
|
||||
entries: readonly T[],
|
||||
localIndex: number,
|
||||
baseOffset: number
|
||||
): QueueItemRemoveAction<T> | null {
|
||||
if (localIndex < 0 || localIndex >= entries.length) return null;
|
||||
|
||||
return {
|
||||
absoluteIndex: baseOffset + localIndex,
|
||||
nextEntries: entries.filter((_, index) => index !== localIndex),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveSelectedQueueAction<T extends KeyedQueueEntry>(
|
||||
entries: readonly T[],
|
||||
selectedKeys: ReadonlySet<string>,
|
||||
baseOffset: number
|
||||
): SelectedQueueAction<T> {
|
||||
const selectedEntries: T[] = [];
|
||||
const remainingEntries: T[] = [];
|
||||
const absoluteIndices: number[] = [];
|
||||
|
||||
entries.forEach((entry, index) => {
|
||||
if (selectedKeys.has(entry.key)) {
|
||||
selectedEntries.push(entry);
|
||||
absoluteIndices.push(baseOffset + index);
|
||||
} else {
|
||||
remainingEntries.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
absoluteIndices,
|
||||
entriesWithSelectedFirst: [...selectedEntries, ...remainingEntries],
|
||||
entriesWithoutSelected: remainingEntries,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user