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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user