feat(scene): keep map tokens with preview rotation
When previewRotationDeg changes, remap non-player, NPC and trap markers (plus live session token overrides) so they stay on the art. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -20,6 +20,10 @@ import {
|
||||
} from '../shared/project/projectZipExtension';
|
||||
import type { Project } from '../shared/types';
|
||||
import { asNpcId } from '../shared/types/ids';
|
||||
import {
|
||||
asPreviewRotationDeg,
|
||||
previewRotationStepsCw,
|
||||
} from '../shared/types/scenePreviewRotation';
|
||||
|
||||
import { EffectsStore, effectsDefaultTool } from './effects/effectsStore';
|
||||
import { SceneDarknessStore } from './effects/sceneDarknessStore';
|
||||
@@ -678,6 +682,7 @@ async function main() {
|
||||
};
|
||||
});
|
||||
registerHandler(ipcChannels.project.updateScene, async ({ sceneId, patch }) => {
|
||||
const before = projectStore.getOpenProject()?.scenes[sceneId];
|
||||
const next = await projectStore.updateScene(sceneId, patch);
|
||||
const project = projectStore.getOpenProject();
|
||||
if (project?.currentSceneId === sceneId && patch.darkenScene !== undefined) {
|
||||
@@ -688,6 +693,29 @@ async function main() {
|
||||
syncSceneTrapsForProject(project);
|
||||
emitSceneTrapsState();
|
||||
}
|
||||
if (
|
||||
project?.currentSceneId === sceneId &&
|
||||
patch.previewRotationDeg !== undefined &&
|
||||
before
|
||||
) {
|
||||
const steps = previewRotationStepsCw(
|
||||
asPreviewRotationDeg(before.previewRotationDeg),
|
||||
asPreviewRotationDeg(patch.previewRotationDeg),
|
||||
);
|
||||
if (steps !== 0) {
|
||||
sceneTokensSessionStore.rotateMapCwSteps(steps);
|
||||
sceneNpcTokensSessionStore.rotateMapCwSteps(steps);
|
||||
scenePlayerTokensSessionStore.rotateMapCwSteps(steps);
|
||||
emitSceneTokensSessionState();
|
||||
emitSceneNpcTokensSessionState();
|
||||
emitScenePlayerTokensSessionState();
|
||||
}
|
||||
}
|
||||
if (project && project.currentSceneId === sceneId && patch.previewRotationDeg !== undefined) {
|
||||
// Trap ids stay the same; runtime statuses remain valid after coordinate remap in project.
|
||||
syncSceneTrapsForProject(project);
|
||||
emitSceneTrapsState();
|
||||
}
|
||||
emitSessionState();
|
||||
return { scene: next };
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type SceneNpcTokensSessionState,
|
||||
} from '../../shared/types/appPlayers';
|
||||
import { normalizeNpcDisposition } from '../../shared/types/npcDisposition';
|
||||
import { rotateMapNormPointByCwSteps } from '../../shared/types/scenePreviewRotation';
|
||||
|
||||
function emptyState(revision = 1): SceneNpcTokensSessionState {
|
||||
return {
|
||||
@@ -63,6 +64,34 @@ export class SceneNpcTokensSessionStore {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/** Rotate session position overrides with the scene preview (CSS rotate steps). */
|
||||
rotateMapCwSteps(steps: number): SceneNpcTokensSessionState {
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
if (n === 0) return this.state;
|
||||
const ids = Object.keys(this.state.byPlacementId);
|
||||
if (ids.length === 0) return this.state;
|
||||
let changed = false;
|
||||
const byPlacementId: SceneNpcTokensSessionState['byPlacementId'] = {};
|
||||
for (const id of ids) {
|
||||
const prev = this.state.byPlacementId[id];
|
||||
if (!prev) continue;
|
||||
if (typeof prev.nx === 'number' && typeof prev.ny === 'number') {
|
||||
const p = rotateMapNormPointByCwSteps(prev.nx, prev.ny, n);
|
||||
byPlacementId[id] = { ...prev, nx: p.nx, ny: p.ny };
|
||||
changed = true;
|
||||
} else {
|
||||
byPlacementId[id] = prev;
|
||||
}
|
||||
}
|
||||
if (!changed) return this.state;
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
byPlacementId,
|
||||
scale: this.state.scale,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: SceneNpcTokensSessionEvent): SceneNpcTokensSessionState {
|
||||
switch (event.kind) {
|
||||
case 'clear':
|
||||
|
||||
@@ -37,3 +37,13 @@ void test('ScenePlayerTokensSessionStore move and reset', () => {
|
||||
assert.equal(s2.visible, false);
|
||||
assert.deepEqual(s2.byPlayerId, {});
|
||||
});
|
||||
|
||||
void test('ScenePlayerTokensSessionStore rotateMapCwSteps', () => {
|
||||
const store = new ScenePlayerTokensSessionStore();
|
||||
store.dispatch({ kind: 'setSelection', playerIds: ['p1'] });
|
||||
store.dispatch({ kind: 'show', sizeN: 0.1 });
|
||||
store.dispatch({ kind: 'move', playerId: 'p1', nx: 0.2, ny: 0.1 });
|
||||
const s = store.rotateMapCwSteps(1);
|
||||
assert.equal(s.byPlayerId.p1?.nx, 0.9);
|
||||
assert.equal(s.byPlayerId.p1?.ny, 0.2);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type ScenePlayerTokensSessionEvent,
|
||||
type ScenePlayerTokensSessionState,
|
||||
} from '../../shared/types/appPlayers';
|
||||
import { rotateMapNormPointByCwSteps } from '../../shared/types/scenePreviewRotation';
|
||||
|
||||
function emptyState(revision = 1): ScenePlayerTokensSessionState {
|
||||
return {
|
||||
@@ -60,6 +61,27 @@ export class ScenePlayerTokensSessionStore {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/** Rotate live player tokens with the scene preview (CSS rotate steps). */
|
||||
rotateMapCwSteps(steps: number): ScenePlayerTokensSessionState {
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
if (n === 0) return this.state;
|
||||
const ids = Object.keys(this.state.byPlayerId);
|
||||
if (ids.length === 0) return this.state;
|
||||
const byPlayerId: ScenePlayerTokensSessionState['byPlayerId'] = {};
|
||||
for (const id of ids) {
|
||||
const prev = this.state.byPlayerId[id];
|
||||
if (!prev) continue;
|
||||
const p = rotateMapNormPointByCwSteps(prev.nx, prev.ny, n);
|
||||
byPlayerId[id] = { ...prev, nx: p.nx, ny: p.ny };
|
||||
}
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
byPlayerId,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: ScenePlayerTokensSessionEvent): ScenePlayerTokensSessionState {
|
||||
switch (event.kind) {
|
||||
case 'clear':
|
||||
|
||||
@@ -67,6 +67,12 @@ import {
|
||||
type NpcDisposition,
|
||||
} from '../../shared/types/npcDisposition';
|
||||
import { DEFAULT_SCENE_GRID, normalizeSceneGrid } from '../../shared/types/sceneGrid';
|
||||
import {
|
||||
asPreviewRotationDeg,
|
||||
previewRotationStepsCw,
|
||||
rotateMapMarkersByCwSteps,
|
||||
rotateSceneTokensByCwSteps,
|
||||
} from '../../shared/types/scenePreviewRotation';
|
||||
import { normalizeSceneTrap } from '../../shared/types/sceneTraps';
|
||||
import type {
|
||||
AssetId,
|
||||
@@ -787,6 +793,24 @@ export class ZipProjectStore {
|
||||
...(patch.layout ? { layout: { ...base.layout, ...patch.layout } } : null),
|
||||
};
|
||||
|
||||
// Keep map markers glued to the art when previewRotationDeg changes (image/video).
|
||||
if (patch.previewRotationDeg !== undefined) {
|
||||
const fromRot = asPreviewRotationDeg(base.previewRotationDeg);
|
||||
const toRot = asPreviewRotationDeg(patch.previewRotationDeg);
|
||||
const steps = previewRotationStepsCw(fromRot, toRot);
|
||||
if (steps !== 0) {
|
||||
if (patch.tokens === undefined) {
|
||||
next.tokens = rotateSceneTokensByCwSteps(base.tokens ?? [], steps);
|
||||
}
|
||||
if (patch.npcTokens === undefined) {
|
||||
next.npcTokens = rotateMapMarkersByCwSteps(base.npcTokens ?? [], steps);
|
||||
}
|
||||
if (patch.traps === undefined) {
|
||||
next.traps = rotateMapMarkersByCwSteps(base.traps ?? [], steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateProject((p) => {
|
||||
const scenes = { ...p.scenes, [sceneId]: next };
|
||||
const sceneListOrder =
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SceneTokensSessionEvent, SceneTokensSessionState } from '../../shared/types';
|
||||
import { rotateMapNormPointByCwSteps } from '../../shared/types/scenePreviewRotation';
|
||||
|
||||
function emptyState(): SceneTokensSessionState {
|
||||
return {
|
||||
@@ -23,6 +24,26 @@ export class SceneTokensSessionStore {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/** Rotate session position overrides with the scene preview (CSS rotate steps). */
|
||||
rotateMapCwSteps(steps: number): SceneTokensSessionState {
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
if (n === 0) return this.state;
|
||||
const ids = Object.keys(this.state.byPlacementId);
|
||||
if (ids.length === 0) return this.state;
|
||||
const byPlacementId: SceneTokensSessionState['byPlacementId'] = {};
|
||||
for (const id of ids) {
|
||||
const prev = this.state.byPlacementId[id];
|
||||
if (!prev) continue;
|
||||
const next = rotateMapNormPointByCwSteps(prev.nx, prev.ny, n);
|
||||
byPlacementId[id] = { nx: next.nx, ny: next.ny };
|
||||
}
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
byPlacementId,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: SceneTokensSessionEvent): SceneTokensSessionState {
|
||||
switch (event.kind) {
|
||||
case 'clear':
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
} from '../../../shared/types';
|
||||
import { getDndApi } from '../../shared/dndApi';
|
||||
import { invalidateAssetUrlCache } from '../../shared/useAssetImageUrl';
|
||||
import { applyPreviewRotationToSceneMarkers } from '../../../shared/types/scenePreviewRotation';
|
||||
|
||||
type ProjectSummary = { id: ProjectId; name: string; updatedAt: string; fileName: string };
|
||||
|
||||
@@ -600,6 +601,25 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
...(patch.media ? { media: { ...scene.media, ...patch.media } } : null),
|
||||
layout: patch.layout ? { ...scene.layout, ...patch.layout } : scene.layout,
|
||||
};
|
||||
if (patch.previewRotationDeg !== undefined) {
|
||||
const remapped = applyPreviewRotationToSceneMarkers(
|
||||
{
|
||||
previewRotationDeg: scene.previewRotationDeg ?? 0,
|
||||
tokens: scene.tokens ?? [],
|
||||
npcTokens: scene.npcTokens ?? [],
|
||||
traps: scene.traps ?? [],
|
||||
},
|
||||
patch.previewRotationDeg,
|
||||
{
|
||||
tokensProvided: patch.tokens !== undefined,
|
||||
npcTokensProvided: patch.npcTokens !== undefined,
|
||||
trapsProvided: patch.traps !== undefined,
|
||||
},
|
||||
);
|
||||
if (patch.tokens === undefined) next.tokens = remapped.tokens;
|
||||
if (patch.npcTokens === undefined) next.npcTokens = remapped.npcTokens;
|
||||
if (patch.traps === undefined) next.traps = remapped.traps;
|
||||
}
|
||||
const scenes = { ...p.scenes, [sceneId]: next };
|
||||
const project: Project = { ...p, scenes };
|
||||
return { ...s, project };
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from './npcs';
|
||||
export * from './sceneDarkness';
|
||||
export * from './sceneGrid';
|
||||
export * from './sceneGridSnap';
|
||||
export * from './scenePreviewRotation';
|
||||
export * from './sceneTraps';
|
||||
export * from './sceneView';
|
||||
export * from './videoPlayback';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
applyPreviewRotationToSceneMarkers,
|
||||
previewRotationStepsCw,
|
||||
rotateMapNormPointByCwSteps,
|
||||
rotateMapNormPointCw90,
|
||||
rotateSceneTokensByCwSteps,
|
||||
} from './scenePreviewRotation';
|
||||
|
||||
void test('previewRotationStepsCw wraps correctly', () => {
|
||||
assert.equal(previewRotationStepsCw(0, 90), 1);
|
||||
assert.equal(previewRotationStepsCw(90, 180), 1);
|
||||
assert.equal(previewRotationStepsCw(0, 270), 3);
|
||||
assert.equal(previewRotationStepsCw(270, 0), 1);
|
||||
assert.equal(previewRotationStepsCw(90, 90), 0);
|
||||
});
|
||||
|
||||
void test('rotateMapNormPointCw90 matches CSS rotate(90deg) y-down', () => {
|
||||
assert.deepEqual(rotateMapNormPointCw90(0, 0), { nx: 1, ny: 0 });
|
||||
assert.deepEqual(rotateMapNormPointCw90(1, 0), { nx: 1, ny: 1 });
|
||||
assert.deepEqual(rotateMapNormPointCw90(1, 1), { nx: 0, ny: 1 });
|
||||
assert.deepEqual(rotateMapNormPointCw90(0, 1), { nx: 0, ny: 0 });
|
||||
assert.deepEqual(rotateMapNormPointCw90(0.25, 0.1), { nx: 0.9, ny: 0.25 });
|
||||
});
|
||||
|
||||
void test('four Cw90 steps return to start', () => {
|
||||
const start = { nx: 0.2, ny: 0.7 };
|
||||
assert.deepEqual(rotateMapNormPointByCwSteps(start.nx, start.ny, 4), start);
|
||||
});
|
||||
|
||||
void test('rotateSceneTokensByCwSteps also turns facing', () => {
|
||||
const [tok] = rotateSceneTokensByCwSteps([{ id: 'a', nx: 0.2, ny: 0.1, rotationDeg: 15 }], 1);
|
||||
assert.ok(tok);
|
||||
assert.equal(tok.nx, 0.9);
|
||||
assert.equal(tok.ny, 0.2);
|
||||
assert.equal(tok.rotationDeg, 105);
|
||||
});
|
||||
|
||||
void test('applyPreviewRotationToSceneMarkers remaps when rotation changes', () => {
|
||||
const scene = {
|
||||
previewRotationDeg: 0 as const,
|
||||
tokens: [{ id: 't', nx: 0.2, ny: 0.1, rotationDeg: 0 }],
|
||||
npcTokens: [{ id: 'n', nx: 0.5, ny: 0.5 }],
|
||||
traps: [{ id: 'tr', nx: 0, ny: 0 }],
|
||||
};
|
||||
const next = applyPreviewRotationToSceneMarkers(scene, 90);
|
||||
assert.deepEqual(next.tokens[0], { id: 't', nx: 0.9, ny: 0.2, rotationDeg: 90 });
|
||||
assert.deepEqual(next.npcTokens[0], { id: 'n', nx: 0.5, ny: 0.5 });
|
||||
assert.deepEqual(next.traps[0], { id: 'tr', nx: 1, ny: 0 });
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
/** Rotate map-normalized markers with scene previewRotationDeg (CSS rotate, y-down). */
|
||||
|
||||
export type PreviewRotationDeg = 0 | 90 | 180 | 270;
|
||||
|
||||
export function asPreviewRotationDeg(raw: unknown): PreviewRotationDeg {
|
||||
return raw === 90 || raw === 180 || raw === 270 ? raw : 0;
|
||||
}
|
||||
|
||||
/** How many 90° clockwise steps take `from` to `to` (0..3). */
|
||||
export function previewRotationStepsCw(from: PreviewRotationDeg, to: PreviewRotationDeg): number {
|
||||
return ((((to - from) / 90) % 4) + 4) % 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* One 90° clockwise step in image/AABB normalized space (origin top-left, y down).
|
||||
* Matches CSS `transform: rotate(90deg)` of the media around its center.
|
||||
*/
|
||||
export function rotateMapNormPointCw90(nx: number, ny: number): { nx: number; ny: number } {
|
||||
return {
|
||||
nx: clamp01(1 - ny),
|
||||
ny: clamp01(nx),
|
||||
};
|
||||
}
|
||||
|
||||
export function rotateMapNormPointByCwSteps(
|
||||
nx: number,
|
||||
ny: number,
|
||||
steps: number,
|
||||
): { nx: number; ny: number } {
|
||||
let x = nx;
|
||||
let y = ny;
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const next = rotateMapNormPointCw90(x, y);
|
||||
x = next.nx;
|
||||
y = next.ny;
|
||||
}
|
||||
return { nx: x, ny: y };
|
||||
}
|
||||
|
||||
function clamp01(v: number): number {
|
||||
if (!Number.isFinite(v)) return 0;
|
||||
return Math.max(0, Math.min(1, v));
|
||||
}
|
||||
|
||||
export function rotateMapMarkersByCwSteps<T extends { nx: number; ny: number }>(
|
||||
items: readonly T[],
|
||||
steps: number,
|
||||
): T[] {
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
if (n === 0) return items.map((item) => ({ ...item }));
|
||||
return items.map((item) => {
|
||||
const p = rotateMapNormPointByCwSteps(item.nx, item.ny, n);
|
||||
return { ...item, nx: p.nx, ny: p.ny };
|
||||
});
|
||||
}
|
||||
|
||||
/** Non-player tokens: move with the map and keep facing relative to the art. */
|
||||
export function rotateSceneTokensByCwSteps<T extends { nx: number; ny: number; rotationDeg: number }>(
|
||||
items: readonly T[],
|
||||
steps: number,
|
||||
): T[] {
|
||||
const n = ((steps % 4) + 4) % 4;
|
||||
if (n === 0) return items.map((item) => ({ ...item }));
|
||||
const delta = n * 90;
|
||||
return items.map((item) => {
|
||||
const p = rotateMapNormPointByCwSteps(item.nx, item.ny, n);
|
||||
return { ...item, nx: p.nx, ny: p.ny, rotationDeg: item.rotationDeg + delta };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply marker remaps when preview rotation changes.
|
||||
* Skips collections explicitly provided in the patch (caller already sent final values).
|
||||
*/
|
||||
export function applyPreviewRotationToSceneMarkers<
|
||||
TScene extends {
|
||||
previewRotationDeg: PreviewRotationDeg;
|
||||
tokens: readonly { nx: number; ny: number; rotationDeg: number }[];
|
||||
npcTokens: readonly { nx: number; ny: number }[];
|
||||
traps: readonly { nx: number; ny: number }[];
|
||||
},
|
||||
>(
|
||||
scene: TScene,
|
||||
nextRotationDeg: PreviewRotationDeg,
|
||||
opts?: {
|
||||
tokensProvided?: boolean;
|
||||
npcTokensProvided?: boolean;
|
||||
trapsProvided?: boolean;
|
||||
},
|
||||
): Pick<TScene, 'tokens' | 'npcTokens' | 'traps'> {
|
||||
const steps = previewRotationStepsCw(scene.previewRotationDeg, nextRotationDeg);
|
||||
return {
|
||||
tokens: opts?.tokensProvided
|
||||
? (scene.tokens as TScene['tokens'])
|
||||
: (rotateSceneTokensByCwSteps(scene.tokens, steps) as TScene['tokens']),
|
||||
npcTokens: opts?.npcTokensProvided
|
||||
? (scene.npcTokens as TScene['npcTokens'])
|
||||
: (rotateMapMarkersByCwSteps(scene.npcTokens, steps) as TScene['npcTokens']),
|
||||
traps: opts?.trapsProvided
|
||||
? (scene.traps as TScene['traps'])
|
||||
: (rotateMapMarkersByCwSteps(scene.traps, steps) as TScene['traps']),
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"build:obfuscate": "node scripts/build.mjs --production --obfuscate",
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"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/safeConsole.test.ts app/main/windows/bootWindow.test.ts app/main/effects/effectsStore.test.ts app/main/effects/sceneDarknessStore.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/sceneGrid.test.ts app/shared/types/sceneGridSnap.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/shared/ipc/contracts.players.test.ts app/shared/types/containMediaRect.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/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/safeConsole.test.ts app/main/windows/bootWindow.test.ts app/main/effects/effectsStore.test.ts app/main/effects/sceneDarknessStore.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/sceneGrid.test.ts app/shared/types/sceneGridSnap.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/shared/ipc/contracts.players.test.ts app/shared/types/containMediaRect.test.ts app/shared/types/scenePreviewRotation.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:write": "prettier . --write",
|
||||
"postinstall": "patch-package",
|
||||
|
||||
Reference in New Issue
Block a user