feat(editor): DnD сцен в список, порядок списка и правки импорта
Можно перетащить изображения/видео в колонку сцен: создаются новые сцены с превью и названием из файла, с полноэкранным прогрессом и отчётом о пропущенных файлах. Добавлен ручной порядок сцен в списке (sceneListOrder) с сохранением в проект; перетаскивание карточек отделено от импорта файлов. Во время сортировки снова работает скролл колесом. Исправлен залипающий disabled у «+ Новая сцена» после создания сцены. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import type { Scene, SceneId } from '../types';
|
||||
|
||||
function sceneCreatedAtSortKey(sceneId: string): number {
|
||||
const last = sceneId.split('_').at(-1) ?? '';
|
||||
const n = Number.parseInt(last, 16);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
/** Порядок по умолчанию: новее выше (как раньше по timestamp в id). */
|
||||
export function defaultSceneListOrder(scenes: Record<SceneId, Scene>): SceneId[] {
|
||||
return (Object.keys(scenes) as SceneId[]).sort(
|
||||
(a, b) => sceneCreatedAtSortKey(b) - sceneCreatedAtSortKey(a),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормализует порядок: убирает отсутствующие id, добавляет новые в конец
|
||||
* (новые без записи в order — по умолчанию в «хвост» относительно сохранённого порядка;
|
||||
* при создании сцены мы сами prepend'им).
|
||||
*/
|
||||
export function reconcileSceneListOrder(
|
||||
scenes: Record<SceneId, Scene>,
|
||||
order: readonly SceneId[] | null | undefined,
|
||||
): SceneId[] {
|
||||
const ids = new Set(Object.keys(scenes) as SceneId[]);
|
||||
if (ids.size === 0) return [];
|
||||
if (!order || order.length === 0) return defaultSceneListOrder(scenes);
|
||||
|
||||
const seen = new Set<SceneId>();
|
||||
const next: SceneId[] = [];
|
||||
for (const id of order) {
|
||||
if (!ids.has(id) || seen.has(id)) continue;
|
||||
next.push(id);
|
||||
seen.add(id);
|
||||
}
|
||||
for (const id of defaultSceneListOrder(scenes)) {
|
||||
if (seen.has(id)) continue;
|
||||
next.push(id);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export function prependSceneListOrder(order: readonly SceneId[], sceneId: SceneId): SceneId[] {
|
||||
return [sceneId, ...order.filter((id) => id !== sceneId)];
|
||||
}
|
||||
|
||||
export function removeFromSceneListOrder(order: readonly SceneId[], sceneId: SceneId): SceneId[] {
|
||||
return order.filter((id) => id !== sceneId);
|
||||
}
|
||||
|
||||
export function moveSceneInListOrder(
|
||||
order: readonly SceneId[],
|
||||
draggedId: SceneId,
|
||||
targetId: SceneId,
|
||||
place: 'before' | 'after',
|
||||
): SceneId[] {
|
||||
if (draggedId === targetId) return [...order];
|
||||
if (!order.includes(draggedId) || !order.includes(targetId)) return [...order];
|
||||
const without = order.filter((id) => id !== draggedId);
|
||||
const targetIndex = without.indexOf(targetId);
|
||||
if (targetIndex < 0) return [...order];
|
||||
const insertAt = place === 'before' ? targetIndex : targetIndex + 1;
|
||||
const next = [...without];
|
||||
next.splice(insertAt, 0, draggedId);
|
||||
return next;
|
||||
}
|
||||
Reference in New Issue
Block a user