feat(editor): drag-and-drop для аудио и превью сцены

Добавлена загрузка файлов перетаскиванием в блоки «Аудио игры», «Аудио сцены»
и «Превью сцены»: подсветка зоны, подсказки, фильтрация по типу, импорт без диалога.

IPC и main принимают пути файлов напрямую; для Electron 41 пути получаются через
webUtils.getPathForFile в preload (File.path в renderer больше недоступен).

Улучшен dev-запуск: Vite слушает 127.0.0.1, ожидание готовности по localhost.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-09 19:22:51 +08:00
parent c8ab9dd567
commit b7e6ff6915
10 changed files with 353 additions and 52 deletions
+41
View File
@@ -34,6 +34,7 @@ type Actions = {
createScene: () => Promise<void>;
selectScene: (id: SceneId) => Promise<void>;
importCampaignAudio: () => Promise<void>;
importCampaignAudioFromPaths: (filePaths: string[]) => Promise<void>;
updateCampaignAudios: (next: Project['campaignAudios']) => Promise<void>;
updateScene: (
sceneId: SceneId,
@@ -53,7 +54,12 @@ type Actions = {
) => Promise<void>;
updateConnections: (sceneId: SceneId, connections: SceneId[]) => Promise<void>;
importMediaToScene: (sceneId: SceneId) => Promise<void>;
importMediaToSceneFromPaths: (sceneId: SceneId, filePaths: string[]) => Promise<void>;
importScenePreview: (sceneId: SceneId) => Promise<{ assetId: AssetId | null; background: boolean }>;
importScenePreviewFromPath: (
sceneId: SceneId,
filePath: string,
) => Promise<{ assetId: AssetId | null; background: boolean }>;
clearScenePreview: (sceneId: SceneId) => Promise<void>;
updateSceneGraphNodePosition: (nodeId: GraphNodeId, x: number, y: number) => Promise<void>;
addSceneGraphNode: (sceneId: SceneId, x: number, y: number) => Promise<void>;
@@ -348,6 +354,14 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
await refreshProjects();
};
const importCampaignAudioFromPaths = async (filePaths: string[]) => {
if (filePaths.length === 0) return;
const res = await api.invoke(ipcChannels.project.importCampaignAudio, { filePaths });
if (res.imported.length === 0) return;
setState((s) => ({ ...s, project: res.project }));
await refreshProjects();
};
const updateCampaignAudios = async (next: Project['campaignAudios']) => {
const res = await api.invoke(ipcChannels.project.updateCampaignAudios, { audios: next });
setState((s) => ({ ...s, project: res.project }));
@@ -422,6 +436,14 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
await refreshProjects();
};
const importMediaToSceneFromPaths = async (sceneId: SceneId, filePaths: string[]) => {
if (filePaths.length === 0) return;
const res = await api.invoke(ipcChannels.project.importMedia, { sceneId, filePaths });
if (res.imported.length === 0) return;
setState((s) => ({ ...s, project: res.project }));
await refreshProjects();
};
const importScenePreview = async (sceneId: SceneId) => {
const res = await api.invoke(ipcChannels.project.importScenePreview, { sceneId });
setState((s) => ({ ...s, project: res.project }));
@@ -438,6 +460,22 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
return { assetId: res.assetId, background: res.background };
};
const importScenePreviewFromPath = async (sceneId: SceneId, filePath: string) => {
const res = await api.invoke(ipcChannels.project.importScenePreview, { sceneId, filePath });
setState((s) => ({ ...s, project: res.project }));
if (res.assetId !== null && res.background) {
setState((s) => ({
...s,
scenePreviewImports: {
...s.scenePreviewImports,
[sceneId]: { assetId: res.assetId, phase: 'queued' },
},
}));
}
await refreshProjects();
return { assetId: res.assetId, background: res.background };
};
const clearScenePreview = async (sceneId: SceneId) => {
const res = await api.invoke(ipcChannels.project.clearScenePreview, { sceneId });
setState((s) => ({ ...s, project: res.project }));
@@ -650,11 +688,14 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
createScene,
selectScene,
importCampaignAudio,
importCampaignAudioFromPaths,
updateCampaignAudios,
updateScene,
updateConnections,
importMediaToScene,
importMediaToSceneFromPaths,
importScenePreview,
importScenePreviewFromPath,
clearScenePreview,
updateSceneGraphNodePosition,
addSceneGraphNode,