feat(control): аккордеон «Сцены» на пульте управления
Список сцен активной линии в порядке бокового списка редактора; переход с записью в историю. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user