35a6e979eb
Можно перетащить изображения/видео в колонку сцен: создаются новые сцены с превью и названием из файла, с полноэкранным прогрессом и отчётом о пропущенных файлах. Добавлен ручной порядок сцен в списке (sceneListOrder) с сохранением в проект; перетаскивание карточек отделено от импорта файлов. Во время сортировки снова работает скролл колесом. Исправлен залипающий disabled у «+ Новая сцена» после создания сцены. Co-authored-by: Cursor <cursoragent@cursor.com>
67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
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;
|
|
}
|