feat(control): аккордеон «Сцены» на пульте управления
Список сцен активной линии в порядке бокового списка редактора; переход с записью в историю. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -221,12 +221,28 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.storyAccordions {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storyAccordionExpanded {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storyAccordionCollapsed {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.storyWrapCollapsed .historyAccordion {
|
.storyWrapCollapsed .historyAccordion {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.historyAccordion {
|
.historyAccordion {
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
isNodeInSideStoryline,
|
isNodeInSideStoryline,
|
||||||
listSideStoryStarts,
|
listSideStoryStarts,
|
||||||
} from '../../shared/graph/sceneGraphLineage';
|
} from '../../shared/graph/sceneGraphLineage';
|
||||||
|
import { listActiveStorylineScenes } from '../../shared/graph/activeStorylineScenes';
|
||||||
import type {
|
import type {
|
||||||
GraphNodeId,
|
GraphNodeId,
|
||||||
MaterialId,
|
MaterialId,
|
||||||
@@ -192,6 +193,7 @@ export function ControlApp() {
|
|||||||
const historyRef = useRef<GraphNodeId[]>([]);
|
const historyRef = useRef<GraphNodeId[]>([]);
|
||||||
const [history, setHistory] = useState<GraphNodeId[]>([]);
|
const [history, setHistory] = useState<GraphNodeId[]>([]);
|
||||||
const [historyOpen, setHistoryOpen] = useState(true);
|
const [historyOpen, setHistoryOpen] = useState(true);
|
||||||
|
const [scenesOpen, setScenesOpen] = useState(false);
|
||||||
const [darknessBrushShape, setDarknessBrushShape] = useState<SceneDarknessBrushShape>('circle');
|
const [darknessBrushShape, setDarknessBrushShape] = useState<SceneDarknessBrushShape>('circle');
|
||||||
const darknessBrushShapeRef = useRef(darknessBrushShape);
|
const darknessBrushShapeRef = useRef(darknessBrushShape);
|
||||||
darknessBrushShapeRef.current = darknessBrushShape;
|
darknessBrushShapeRef.current = darknessBrushShape;
|
||||||
@@ -413,6 +415,11 @@ export function ControlApp() {
|
|||||||
const project = session?.project ?? null;
|
const project = session?.project ?? null;
|
||||||
const currentGraphNodeId = project?.currentGraphNodeId ?? null;
|
const currentGraphNodeId = project?.currentGraphNodeId ?? null;
|
||||||
const currentHistoryIdx = currentGraphNodeId != null ? history.lastIndexOf(currentGraphNodeId) : -1;
|
const currentHistoryIdx = currentGraphNodeId != null ? history.lastIndexOf(currentGraphNodeId) : -1;
|
||||||
|
const historyGraphNodeSet = useMemo(() => new Set(history), [history]);
|
||||||
|
const storylineScenes = useMemo(
|
||||||
|
() => (project ? listActiveStorylineScenes(project) : []),
|
||||||
|
[project],
|
||||||
|
);
|
||||||
const currentScene =
|
const currentScene =
|
||||||
project && session?.currentSceneId ? project.scenes[session.currentSceneId] : undefined;
|
project && session?.currentSceneId ? project.scenes[session.currentSceneId] : undefined;
|
||||||
const visibleNpcTokens = useMemo(
|
const visibleNpcTokens = useMemo(
|
||||||
@@ -2165,8 +2172,17 @@ export function ControlApp() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.spacer12} />
|
<div className={styles.spacer12} />
|
||||||
</>
|
</>
|
||||||
<div className={[styles.storyWrap, historyOpen ? '' : styles.storyWrapCollapsed].filter(Boolean).join(' ')}>
|
<div className={[styles.storyWrap, historyOpen || scenesOpen ? '' : styles.storyWrapCollapsed].filter(Boolean).join(' ')}>
|
||||||
<div className={styles.historyAccordion} data-testid="control-history-accordion">
|
<div className={styles.storyAccordions}>
|
||||||
|
<div
|
||||||
|
className={[
|
||||||
|
styles.historyAccordion,
|
||||||
|
historyOpen ? styles.storyAccordionExpanded : styles.storyAccordionCollapsed,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')}
|
||||||
|
data-testid="control-history-accordion"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.historyAccordionHead}
|
className={styles.historyAccordionHead}
|
||||||
@@ -2214,6 +2230,62 @@ export function ControlApp() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={[
|
||||||
|
styles.historyAccordion,
|
||||||
|
scenesOpen ? styles.storyAccordionExpanded : styles.storyAccordionCollapsed,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')}
|
||||||
|
data-testid="control-scenes-accordion"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.historyAccordionHead}
|
||||||
|
aria-expanded={scenesOpen}
|
||||||
|
onClick={() => setScenesOpen((v) => !v)}
|
||||||
|
>
|
||||||
|
{t('control.scenesAccordion')} {scenesOpen ? '▾' : '▸'}
|
||||||
|
</button>
|
||||||
|
{scenesOpen ? (
|
||||||
|
<div className={styles.storyScroll}>
|
||||||
|
{storylineScenes.map(({ sceneId, graphNodeId }) => {
|
||||||
|
const s = project?.scenes[sceneId];
|
||||||
|
const isCurrent = graphNodeId === currentGraphNodeId;
|
||||||
|
const visited = historyGraphNodeSet.has(graphNodeId);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={sceneId}
|
||||||
|
disabled={!project || isCurrent}
|
||||||
|
className={[styles.historyBtn, isCurrent ? styles.historyBtnCurrent : '']
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')}
|
||||||
|
title={project && !isCurrent ? t('control.gotoScene') : undefined}
|
||||||
|
onClick={() => {
|
||||||
|
if (!project || isCurrent) return;
|
||||||
|
void api.invoke(ipcChannels.project.setCurrentGraphNode, { graphNodeId });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCurrent ? (
|
||||||
|
<div className={styles.historyBadge}>{t('control.currentSceneBadge')}</div>
|
||||||
|
) : visited ? (
|
||||||
|
<div className={styles.historyMuted}>{t('control.passed')}</div>
|
||||||
|
) : null}
|
||||||
|
<EllipsisText
|
||||||
|
text={s?.title ?? String(sceneId)}
|
||||||
|
className={[styles.historyTitle, ellipsisStyles.root].join(' ')}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{storylineScenes.length === 0 ? (
|
||||||
|
<div className={styles.emptyStory}>{t('control.noActiveScene')}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Surface>
|
</Surface>
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,18 @@ void test('ControlApp: история — колонка сверху вниз
|
|||||||
assert.match(css, /\.branchCard[\s\S]*?background:\s*var\(--color-overlay-dark-2\)/);
|
assert.match(css, /\.branchCard[\s\S]*?background:\s*var\(--color-overlay-dark-2\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void test('ControlApp: блок «Сцены» — аккордеон под историей, свёрнут по умолчанию', () => {
|
||||||
|
const src = readControlApp();
|
||||||
|
assert.ok(src.includes('data-testid="control-scenes-accordion"'));
|
||||||
|
assert.ok(src.includes("t('control.scenesAccordion')"));
|
||||||
|
assert.ok(src.includes('const [scenesOpen, setScenesOpen] = useState(false)'));
|
||||||
|
assert.ok(src.includes('listActiveStorylineScenes'));
|
||||||
|
assert.ok(src.includes('storylineScenes.map'));
|
||||||
|
const historyIdx = src.indexOf('data-testid="control-history-accordion"');
|
||||||
|
const scenesIdx = src.indexOf('data-testid="control-scenes-accordion"');
|
||||||
|
assert.ok(historyIdx !== -1 && scenesIdx !== -1 && historyIdx < scenesIdx);
|
||||||
|
});
|
||||||
|
|
||||||
void test('ControlApp: клик по истории добавляет новый шаг, не подавляет запись', () => {
|
void test('ControlApp: клик по истории добавляет новый шаг, не подавляет запись', () => {
|
||||||
const src = readControlApp();
|
const src = readControlApp();
|
||||||
assert.ok(!src.includes('suppressNextHistoryPushRef'));
|
assert.ok(!src.includes('suppressNextHistoryPushRef'));
|
||||||
|
|||||||
@@ -646,6 +646,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
|||||||
'control.brushShapeSquare': 'Квадратная',
|
'control.brushShapeSquare': 'Квадратная',
|
||||||
'control.effectsSound': 'Звук эффектов',
|
'control.effectsSound': 'Звук эффектов',
|
||||||
'control.storyLine': 'ИСТОРИЯ',
|
'control.storyLine': 'ИСТОРИЯ',
|
||||||
|
'control.scenesAccordion': 'СЦЕНЫ',
|
||||||
'control.gotoScene': 'Перейти к этой сцене',
|
'control.gotoScene': 'Перейти к этой сцене',
|
||||||
'control.currentSceneBadge': 'ТЕКУЩАЯ СЦЕНА',
|
'control.currentSceneBadge': 'ТЕКУЩАЯ СЦЕНА',
|
||||||
'control.passed': 'Пройдено',
|
'control.passed': 'Пройдено',
|
||||||
@@ -1297,6 +1298,7 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
|||||||
'control.brushShapeSquare': 'Square',
|
'control.brushShapeSquare': 'Square',
|
||||||
'control.effectsSound': 'Effects sound',
|
'control.effectsSound': 'Effects sound',
|
||||||
'control.storyLine': 'HISTORY',
|
'control.storyLine': 'HISTORY',
|
||||||
|
'control.scenesAccordion': 'SCENES',
|
||||||
'control.gotoScene': 'Go to this scene',
|
'control.gotoScene': 'Go to this scene',
|
||||||
'control.currentSceneBadge': 'CURRENT SCENE',
|
'control.currentSceneBadge': 'CURRENT SCENE',
|
||||||
'control.passed': 'Visited',
|
'control.passed': 'Visited',
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import type { Project, SceneGraphEdge, SceneGraphNode } from '../types';
|
||||||
|
import { asGraphNodeId, asSceneId } from '../types/ids';
|
||||||
|
|
||||||
|
import { getActiveStorylineGraphNodeIds, listActiveStorylineScenes } from './activeStorylineScenes';
|
||||||
|
|
||||||
|
function node(id: string, sceneId: string, extra: Partial<SceneGraphNode> = {}): SceneGraphNode {
|
||||||
|
return {
|
||||||
|
id: asGraphNodeId(id),
|
||||||
|
sceneId: asSceneId(sceneId),
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
isStartScene: false,
|
||||||
|
isSideStoryStart: false,
|
||||||
|
sideStoryLineTitle: '',
|
||||||
|
...extra,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function edge(source: string, target: string): SceneGraphEdge {
|
||||||
|
return { sourceGraphNodeId: asGraphNodeId(source), targetGraphNodeId: asGraphNodeId(target) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimalProject(partial: Partial<Project> & Pick<Project, 'sceneGraphNodes' | 'sceneGraphEdges'>): Project {
|
||||||
|
return {
|
||||||
|
id: partial.id ?? ('p1' as Project['id']),
|
||||||
|
title: partial.title ?? 'Test',
|
||||||
|
scenes: partial.scenes ?? {},
|
||||||
|
sceneListOrder: partial.sceneListOrder ?? [],
|
||||||
|
sceneGraphNodes: partial.sceneGraphNodes,
|
||||||
|
sceneGraphEdges: partial.sceneGraphEdges,
|
||||||
|
currentGraphNodeId: partial.currentGraphNodeId ?? null,
|
||||||
|
currentSceneId: partial.currentSceneId ?? null,
|
||||||
|
materials: partial.materials ?? [],
|
||||||
|
campaignAudio: partial.campaignAudio ?? [],
|
||||||
|
npcs: partial.npcs ?? [],
|
||||||
|
npcGroups: partial.npcGroups ?? [],
|
||||||
|
npcRelations: partial.npcRelations ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void test('listActiveStorylineScenes: main line ordered by sceneListOrder, deduped', () => {
|
||||||
|
const s1 = asSceneId('s1');
|
||||||
|
const s2 = asSceneId('s2');
|
||||||
|
const s3 = asSceneId('s3');
|
||||||
|
const nodes = [
|
||||||
|
node('main', 's1', { isStartScene: true }),
|
||||||
|
node('a', 's2'),
|
||||||
|
node('b', 's3'),
|
||||||
|
node('dup', 's2'),
|
||||||
|
];
|
||||||
|
const edges = [edge('main', 'a'), edge('a', 'b'), edge('a', 'dup')];
|
||||||
|
const project = minimalProject({
|
||||||
|
scenes: {
|
||||||
|
[s1]: { id: s1, title: 'Start' },
|
||||||
|
[s2]: { id: s2, title: 'Mid' },
|
||||||
|
[s3]: { id: s3, title: 'End' },
|
||||||
|
},
|
||||||
|
sceneListOrder: [s3, s1, s2],
|
||||||
|
sceneGraphNodes: nodes,
|
||||||
|
sceneGraphEdges: edges,
|
||||||
|
currentGraphNodeId: asGraphNodeId('a'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = listActiveStorylineScenes(project);
|
||||||
|
assert.deepEqual(
|
||||||
|
list.map((e) => e.sceneId),
|
||||||
|
[s3, s1, s2],
|
||||||
|
);
|
||||||
|
assert.equal(list[2]?.graphNodeId, asGraphNodeId('a'));
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('listActiveStorylineScenes: side line only', () => {
|
||||||
|
const s1 = asSceneId('s1');
|
||||||
|
const s2 = asSceneId('s2');
|
||||||
|
const sSide = asSceneId('sSide');
|
||||||
|
const nodes = [
|
||||||
|
node('main', 's1', { isStartScene: true }),
|
||||||
|
node('side', 'sSide', { isSideStoryStart: true }),
|
||||||
|
node('x', 's2'),
|
||||||
|
];
|
||||||
|
const edges = [edge('side', 'x')];
|
||||||
|
const project = minimalProject({
|
||||||
|
scenes: {
|
||||||
|
[s1]: { id: s1, title: 'Main' },
|
||||||
|
[s2]: { id: s2, title: 'Side step' },
|
||||||
|
[sSide]: { id: sSide, title: 'Side start' },
|
||||||
|
},
|
||||||
|
sceneListOrder: [s1, s2, sSide],
|
||||||
|
sceneGraphNodes: nodes,
|
||||||
|
sceneGraphEdges: edges,
|
||||||
|
currentGraphNodeId: asGraphNodeId('x'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = getActiveStorylineGraphNodeIds(nodes, edges, asGraphNodeId('x'));
|
||||||
|
assert.deepEqual([...ids].sort(), [asGraphNodeId('side'), asGraphNodeId('x')].sort());
|
||||||
|
|
||||||
|
const list = listActiveStorylineScenes(project);
|
||||||
|
assert.deepEqual(
|
||||||
|
list.map((e) => e.sceneId),
|
||||||
|
[s2, sSide],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('listActiveStorylineScenes: current node preferred for duplicate sceneId', () => {
|
||||||
|
const s2 = asSceneId('s2');
|
||||||
|
const nodes = [
|
||||||
|
node('main', 's1', { isStartScene: true }),
|
||||||
|
node('a', 's2'),
|
||||||
|
node('dup', 's2'),
|
||||||
|
];
|
||||||
|
const edges = [edge('main', 'a'), edge('main', 'dup')];
|
||||||
|
const project = minimalProject({
|
||||||
|
scenes: { [asSceneId('s1')]: { id: asSceneId('s1'), title: 'S1' }, [s2]: { id: s2, title: 'S2' } },
|
||||||
|
sceneListOrder: [asSceneId('s1'), s2],
|
||||||
|
sceneGraphNodes: nodes,
|
||||||
|
sceneGraphEdges: edges,
|
||||||
|
currentGraphNodeId: asGraphNodeId('dup'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = listActiveStorylineScenes(project);
|
||||||
|
assert.equal(list.find((e) => e.sceneId === s2)?.graphNodeId, asGraphNodeId('dup'));
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"build:obfuscate": "node scripts/build.mjs --production --obfuscate",
|
"build:obfuscate": "node scripts/build.mjs --production --obfuscate",
|
||||||
"lint": "eslint . --max-warnings 0",
|
"lint": "eslint . --max-warnings 0",
|
||||||
"typecheck": "tsc -p tsconfig.eslint.json --noEmit",
|
"typecheck": "tsc -p tsconfig.eslint.json --noEmit",
|
||||||
"test": "tsx --test app/renderer/shared/ui/controls.tooltip.test.ts app/renderer/shared/ui/secondaryWindows.stability.test.ts app/renderer/npcs/tiptapEditorSafe.test.ts app/renderer/editor/state/projectState.race.test.ts app/renderer/editor/fileDrop.test.ts app/shared/graph/sceneListOrder.test.ts app/renderer/editor/graph/sceneCardById.test.ts app/renderer/editor/i18n/editorMessages.locale.test.ts app/renderer/editor/help/helpLinkify.test.ts app/renderer/editor/sceneDescriptionHtml.test.ts app/shared/graph/storylineExportImport.test.ts app/shared/foundry/foundryGraph.test.ts app/shared/ipc/contracts.mediaRemoval.test.ts app/shared/effectEraserHitTest.test.ts app/shared/fieldEffectEraser.test.ts app/renderer/control/controlApp.effectsPanel.test.ts app/renderer/control/controlApp.audioPerf.networkRegression.test.ts app/renderer/control/controlApp.brushPerf.networkRegression.test.ts app/renderer/shared/useAssetImageUrl.cache.test.ts app/main/sessionIpc.phase6.networkRegression.test.ts app/renderer/shared/sceneOverlay/sceneOverlayHost.test.ts app/renderer/shared/effects/PxiEffectsOverlay.pointer.test.ts app/renderer/shared/traps/trapActivation.test.ts app/renderer/shared/videoSceneMapParity.test.ts app/main/windows/createWindows.editorClose.test.ts app/main/preferences/appPreferences.test.ts app/main/safeConsole.test.ts app/main/windows/bootWindow.test.ts app/main/effects/effectsStore.test.ts app/main/effects/sceneDarknessStore.test.ts app/main/effects/sceneMarkerStore.test.ts app/shared/sceneMarkerEraser.test.ts app/main/sceneTraps/sceneTrapsStore.test.ts app/main/project/assetPrune.test.ts app/main/project/optimizeImageImport.test.ts app/main/project/scenePreviewThumbnail.test.ts app/main/project/fsRetry.test.ts app/main/project/zipRead.test.ts app/main/project/replaceFileAtomic.test.ts app/main/project/zipStore.legacyContract.test.ts app/shared/package.build.test.ts app/shared/license/canonicalJson.test.ts app/shared/license/productKey.test.ts app/shared/license/licenseService.networkRegression.test.ts app/shared/video/videoPlaybackPerf.networkRegression.test.ts app/shared/video/videoPlaybackLoop.networkRegression.test.ts app/main/license/verifyLicenseToken.test.ts app/main/license/machineFingerprint.test.ts app/shared/types/appPlayers.test.ts app/shared/types/npcDisposition.test.ts app/shared/types/npcExternalUrl.test.ts app/main/npcs/npcsOverlayStore.test.ts app/shared/types/sceneGrid.test.ts app/shared/types/sceneGridSnap.test.ts app/shared/types/sceneDarkness.test.ts app/shared/types/materialLegend.test.ts app/main/tokens/tokenGridSnapSessionStore.test.ts app/shared/players/playerTeams.test.ts app/shared/players/launchPlayersSelection.test.ts app/main/players/playersStore.test.ts app/main/players/sceneNpcTokensSessionStore.test.ts app/main/players/scenePlayerTokensSessionStore.test.ts app/main/tokens/sceneTokensSessionStore.test.ts app/shared/ipc/contracts.players.test.ts app/shared/types/containMediaRect.test.ts app/shared/types/scenePreviewRotation.test.ts app/shared/types/tokenPath.test.ts app/shared/types/tokenPathPlayback.test.ts && node --test scripts/build-env.test.mjs scripts/obfuscate-main.test.mjs scripts/release-mac-prep.test.mjs scripts/release-native-prep.test.mjs scripts/verify-packaged-sharp.test.mjs app/main/project/sharpRuntime.test.mjs",
|
"test": "tsx --test app/renderer/shared/ui/controls.tooltip.test.ts app/renderer/shared/ui/secondaryWindows.stability.test.ts app/renderer/npcs/tiptapEditorSafe.test.ts app/renderer/editor/state/projectState.race.test.ts app/renderer/editor/fileDrop.test.ts app/shared/graph/sceneListOrder.test.ts app/shared/graph/activeStorylineScenes.test.ts app/renderer/editor/graph/sceneCardById.test.ts app/renderer/editor/i18n/editorMessages.locale.test.ts app/renderer/editor/help/helpLinkify.test.ts app/renderer/editor/sceneDescriptionHtml.test.ts app/shared/graph/storylineExportImport.test.ts app/shared/foundry/foundryGraph.test.ts app/shared/ipc/contracts.mediaRemoval.test.ts app/shared/effectEraserHitTest.test.ts app/shared/fieldEffectEraser.test.ts app/renderer/control/controlApp.effectsPanel.test.ts app/renderer/control/controlApp.audioPerf.networkRegression.test.ts app/renderer/control/controlApp.brushPerf.networkRegression.test.ts app/renderer/shared/useAssetImageUrl.cache.test.ts app/main/sessionIpc.phase6.networkRegression.test.ts app/renderer/shared/sceneOverlay/sceneOverlayHost.test.ts app/renderer/shared/effects/PxiEffectsOverlay.pointer.test.ts app/renderer/shared/traps/trapActivation.test.ts app/renderer/shared/videoSceneMapParity.test.ts app/main/windows/createWindows.editorClose.test.ts app/main/preferences/appPreferences.test.ts app/main/safeConsole.test.ts app/main/windows/bootWindow.test.ts app/main/effects/effectsStore.test.ts app/main/effects/sceneDarknessStore.test.ts app/main/effects/sceneMarkerStore.test.ts app/shared/sceneMarkerEraser.test.ts app/main/sceneTraps/sceneTrapsStore.test.ts app/main/project/assetPrune.test.ts app/main/project/optimizeImageImport.test.ts app/main/project/scenePreviewThumbnail.test.ts app/main/project/fsRetry.test.ts app/main/project/zipRead.test.ts app/main/project/replaceFileAtomic.test.ts app/main/project/zipStore.legacyContract.test.ts app/shared/package.build.test.ts app/shared/license/canonicalJson.test.ts app/shared/license/productKey.test.ts app/shared/license/licenseService.networkRegression.test.ts app/shared/video/videoPlaybackPerf.networkRegression.test.ts app/shared/video/videoPlaybackLoop.networkRegression.test.ts app/main/license/verifyLicenseToken.test.ts app/main/license/machineFingerprint.test.ts app/shared/types/appPlayers.test.ts app/shared/types/npcDisposition.test.ts app/shared/types/npcExternalUrl.test.ts app/main/npcs/npcsOverlayStore.test.ts app/shared/types/sceneGrid.test.ts app/shared/types/sceneGridSnap.test.ts app/shared/types/sceneDarkness.test.ts app/shared/types/materialLegend.test.ts app/main/tokens/tokenGridSnapSessionStore.test.ts app/shared/players/playerTeams.test.ts app/shared/players/launchPlayersSelection.test.ts app/main/players/playersStore.test.ts app/main/players/sceneNpcTokensSessionStore.test.ts app/main/players/scenePlayerTokensSessionStore.test.ts app/main/tokens/sceneTokensSessionStore.test.ts app/shared/ipc/contracts.players.test.ts app/shared/types/containMediaRect.test.ts app/shared/types/scenePreviewRotation.test.ts app/shared/types/tokenPath.test.ts app/shared/types/tokenPathPlayback.test.ts && node --test scripts/build-env.test.mjs scripts/obfuscate-main.test.mjs scripts/release-mac-prep.test.mjs scripts/release-native-prep.test.mjs scripts/verify-packaged-sharp.test.mjs app/main/project/sharpRuntime.test.mjs",
|
||||||
"format": "prettier . --check",
|
"format": "prettier . --check",
|
||||||
"format:write": "prettier . --write",
|
"format:write": "prettier . --write",
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
|
|||||||
Reference in New Issue
Block a user