Files
DndGamePlayer/app/shared/graph/sceneListOrder.ts
T
Ivan Fontosh 35a6e979eb feat(editor): DnD сцен в список, порядок списка и правки импорта
Можно перетащить изображения/видео в колонку сцен: создаются новые сцены
с превью и названием из файла, с полноэкранным прогрессом и отчётом о
пропущенных файлах.

Добавлен ручной порядок сцен в списке (sceneListOrder) с сохранением в
проект; перетаскивание карточек отделено от импорта файлов. Во время
сортировки снова работает скролл колесом.

Исправлен залипающий disabled у «+ Новая сцена» после создания сцены.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 10:32:50 +08:00

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;
}