Files
DndGamePlayer/app/shared/graph/activeStorylineScenes.ts
T
Ivan Fontosh cfb6e62331 feat(control): аккордеон «Сцены» на пульте управления
Список сцен активной линии в порядке бокового списка редактора; переход с записью в историю.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 13:29:12 +08:00

78 lines
2.4 KiB
TypeScript

import type { GraphNodeId, Project, SceneGraphEdge, SceneGraphNode, SceneId } from '../types';
import { reconcileSceneListOrder } from './sceneListOrder';
import {
buildStoryRootMap,
getNodeStoryRoot,
getSideStoryComponentNodeIds,
isSideStoryRoot,
} from './sceneGraphLineage';
export type ActiveStorylineSceneEntry = {
sceneId: SceneId;
graphNodeId: GraphNodeId;
};
/** Узлы графа, принадлежащие активной сюжетной линии текущего положения. */
export function getActiveStorylineGraphNodeIds(
nodes: SceneGraphNode[],
edges: SceneGraphEdge[],
currentGraphNodeId: GraphNodeId,
): Set<GraphNodeId> {
const root = getNodeStoryRoot(nodes, edges, currentGraphNodeId);
if (root === 'main') {
const rootMap = buildStoryRootMap(nodes, edges);
const ids = new Set<GraphNodeId>();
for (const [id, r] of rootMap) {
if (r === 'main') ids.add(id);
}
return ids;
}
if (isSideStoryRoot(root)) {
return getSideStoryComponentNodeIds(nodes, edges, root);
}
return new Set();
}
function pickGraphNodeForScene(
sceneId: SceneId,
storylineNodeIds: Set<GraphNodeId>,
nodes: SceneGraphNode[],
currentGraphNodeId: GraphNodeId | null,
): GraphNodeId | null {
const matching = nodes.filter((n) => storylineNodeIds.has(n.id) && n.sceneId === sceneId);
if (matching.length === 0) return null;
if (currentGraphNodeId && matching.some((n) => n.id === currentGraphNodeId)) {
return currentGraphNodeId;
}
return matching[0]?.id ?? null;
}
/**
* Сцены активной линии в порядке бокового списка редактора; каждая sceneId — один раз.
*/
export function listActiveStorylineScenes(project: Project): ActiveStorylineSceneEntry[] {
const currentGraphNodeId = project.currentGraphNodeId;
if (!currentGraphNodeId) return [];
const storylineNodeIds = getActiveStorylineGraphNodeIds(
project.sceneGraphNodes,
project.sceneGraphEdges,
currentGraphNodeId,
);
if (storylineNodeIds.size === 0) return [];
const order = reconcileSceneListOrder(project.scenes, project.sceneListOrder);
const out: ActiveStorylineSceneEntry[] = [];
for (const sceneId of order) {
const graphNodeId = pickGraphNodeForScene(
sceneId,
storylineNodeIds,
project.sceneGraphNodes,
currentGraphNodeId,
);
if (graphNodeId) out.push({ sceneId, graphNodeId });
}
return out;
}