Лицензия, редактор, пульт и сборка

- Main: license service, IPC, router; закрытие окон; yauzl закрытие zip (EMFILE), zipRead тест
- Editor: стабильный projectState без мигания, логотип и меню, строки UI, LayoutShell overlay
- Control: ластик для всех типов эффектов, затухание/нарастание музыки при смене сцены
- Сборка: vite, build/dev scripts, obfuscate-main и build-env скрипты с тестами; package.json

Made-with: Cursor
This commit is contained in:
Ivan Fontosh
2026-04-19 20:11:24 +08:00
parent 5e7dc5ea19
commit 2fa20da94d
40 changed files with 2629 additions and 211 deletions
+18 -8
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { ipcChannels } from '../../../shared/ipc/contracts';
import type { AssetId, GraphNodeId, Project, ProjectId, Scene, SceneId } from '../../../shared/types';
@@ -54,9 +54,13 @@ function randomId(prefix: string): string {
return `${prefix}_${Math.random().toString(16).slice(2)}_${Date.now().toString(16)}`;
}
export function useProjectState(): readonly [State, Actions] {
export function useProjectState(licenseActive: boolean): readonly [State, Actions] {
const api = getDndApi();
const [state, setState] = useState<State>({ projects: [], project: null, selectedSceneId: null });
const projectRef = useRef<Project | null>(null);
useEffect(() => {
projectRef.current = state.project;
}, [state.project]);
const actions = useMemo<Actions>(() => {
const refreshProjects = async () => {
@@ -77,11 +81,11 @@ export function useProjectState(): readonly [State, Actions] {
const closeProject = async () => {
setState((s) => ({ ...s, project: null, selectedSceneId: null }));
await refreshProjects();
if (licenseActive) await refreshProjects();
};
const createScene = async () => {
const p = state.project;
const p = projectRef.current;
if (!p) return;
const sceneId = randomId('scene') as SceneId;
const scene: Scene = {
@@ -307,16 +311,22 @@ export function useProjectState(): readonly [State, Actions] {
exportProject,
deleteProject,
};
}, [api, state.project]);
}, [api, licenseActive]);
useEffect(() => {
if (!licenseActive) {
queueMicrotask(() => {
setState({ projects: [], project: null, selectedSceneId: null });
});
return;
}
void (async () => {
await actions.refreshProjects();
const listRes = await api.invoke(ipcChannels.project.list, {});
setState((s) => ({ ...s, projects: listRes.projects }));
const res = await api.invoke(ipcChannels.project.get, {});
setState((s) => ({ ...s, project: res.project, selectedSceneId: res.project?.currentSceneId ?? null }));
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [licenseActive, api]);
return [state, actions] as const;
}