feat(traps): activation VFX/SFX, explosion effect, and help section
Wire mimic/pit/arrow/laser media on activate, poison/explosion via effects, and document the scene editor and traps in Instructions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,3 +34,11 @@ void test('ipc router: project.list does not require license', () => {
|
||||
const routerSrc = fs.readFileSync(path.join(here, '..', '..', '..', 'main', 'ipc', 'router.ts'), 'utf8');
|
||||
assert.match(routerSrc, /if \(channel === ipcChannels\.project\.list\) return false/);
|
||||
});
|
||||
|
||||
void test('projectState: openProject не выбирает сцену (selectedSceneId: null)', () => {
|
||||
const src = fs.readFileSync(path.join(here, 'projectState.ts'), 'utf8');
|
||||
assert.match(
|
||||
src,
|
||||
/const openProject = async[\s\S]+?openingProjectId: id, selectedSceneId: null[\s\S]+?selectedSceneId: null,\s*openingProjectId: null/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ type Actions = {
|
||||
setMaterialRotation: (materialId: MaterialId, rotationDeg: 0 | 90 | 180 | 270) => Promise<void>;
|
||||
setMaterialLegend: (
|
||||
materialId: MaterialId,
|
||||
legend: import('../../shared/types').MaterialLegend | null,
|
||||
legend: import('../../../shared/types').MaterialLegend | null,
|
||||
) => Promise<void>;
|
||||
pickMaterialImage: () => Promise<{ filePath: string; previewDataUrl: string } | null>;
|
||||
updateScene: (
|
||||
@@ -286,7 +286,8 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
invalidateAssetUrlCache();
|
||||
|
||||
const job = (async () => {
|
||||
setState((s) => ({ ...s, openingProjectId: id }));
|
||||
// При открытии не выбираем сцену: список/граф/инспектор без выделения.
|
||||
setState((s) => ({ ...s, openingProjectId: id, selectedSceneId: null }));
|
||||
try {
|
||||
const res = await api.invoke(ipcChannels.project.open, { projectId: id });
|
||||
if (projectDataEpochRef.current !== epoch) {
|
||||
@@ -296,7 +297,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
setState((s) => ({
|
||||
...s,
|
||||
project: res.project,
|
||||
selectedSceneId: res.project.currentSceneId,
|
||||
selectedSceneId: null,
|
||||
openingProjectId: null,
|
||||
}));
|
||||
} catch {
|
||||
@@ -538,7 +539,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
|
||||
const setMaterialLegend = async (
|
||||
materialId: MaterialId,
|
||||
legend: import('../../shared/types').MaterialLegend | null,
|
||||
legend: import('../../../shared/types').MaterialLegend | null,
|
||||
) => {
|
||||
const res = await api.invoke(ipcChannels.project.setMaterialLegend, { materialId, legend });
|
||||
// Список проектов не меняется — не дергаем refreshProjects на каждое обновление легенды.
|
||||
@@ -562,7 +563,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
previewVideoAutostart?: boolean;
|
||||
previewRotationDeg?: 0 | 90 | 180 | 270;
|
||||
darkenScene?: boolean;
|
||||
traps?: import('../../shared/types').SceneTrap[];
|
||||
traps?: import('../../../shared/types').SceneTrap[];
|
||||
settings?: Partial<Scene['settings']>;
|
||||
media?: Partial<Scene['media']>;
|
||||
layout?: { x: number; y: number };
|
||||
@@ -969,7 +970,8 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
|
||||
setState((s) => ({
|
||||
...s,
|
||||
project: res.project,
|
||||
selectedSceneId: res.project?.currentSceneId ?? null,
|
||||
// Восстановление открытого проекта — без автовыбора сцены в редакторе.
|
||||
selectedSceneId: null,
|
||||
}));
|
||||
} catch {
|
||||
if (projectDataEpochRef.current !== epoch) return;
|
||||
|
||||
Reference in New Issue
Block a user