优化策划工作区后台刷新体验
合并 Agent 工具事件并避免刷新时清空文件树 仅首次打开和手动刷新显示加载状态
This commit is contained in:
+87
-55
@@ -7,7 +7,7 @@ import {
|
||||
Folder,
|
||||
RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { resolveTauriInvoke } from '../../app/tauri';
|
||||
import type { DesignView, DesignWorkspaceEntry } from '../../app/types';
|
||||
@@ -181,61 +181,78 @@ export function DesignWorkspacePanel({
|
||||
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const refreshTimerRef = useRef<number | null>(null);
|
||||
|
||||
const loadWorkspace = useCallback(async () => {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke || !projectPath.trim()) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const [nextView, nextFiles] = await Promise.all([
|
||||
invoke<DesignView | null>('hydrate_design_agent_session', {
|
||||
projectPath,
|
||||
}),
|
||||
invoke<DesignWorkspaceEntry[]>('list_design_workspace', {
|
||||
projectPath,
|
||||
}),
|
||||
]);
|
||||
setView(nextView);
|
||||
setFiles(nextFiles);
|
||||
setExpandedPaths((current) => {
|
||||
const next = new Set(current);
|
||||
if (current.size === 0) {
|
||||
nextFiles
|
||||
.filter((entry) => entry.kind === 'directory')
|
||||
.forEach((entry) => next.add(entry.path));
|
||||
} else {
|
||||
nextFiles
|
||||
.filter(
|
||||
(entry) => entry.kind === 'directory' && next.has(entry.path),
|
||||
)
|
||||
.forEach((entry) => next.add(entry.path));
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setSelectedPath((current) =>
|
||||
current &&
|
||||
nextFiles.some(
|
||||
(entry) => entry.kind !== 'directory' && entry.path === current,
|
||||
)
|
||||
? current
|
||||
: null,
|
||||
);
|
||||
} catch (nextError) {
|
||||
setError(
|
||||
nextError instanceof Error ? nextError.message : String(nextError),
|
||||
);
|
||||
setFiles([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [projectPath]);
|
||||
const loadWorkspace = useCallback(
|
||||
async ({
|
||||
showLoading,
|
||||
hydrateSession,
|
||||
}: {
|
||||
showLoading: boolean;
|
||||
hydrateSession: boolean;
|
||||
}) => {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke || !projectPath.trim()) {
|
||||
if (showLoading) setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (showLoading) {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
}
|
||||
try {
|
||||
const nextFilesPromise = invoke<DesignWorkspaceEntry[]>(
|
||||
'list_design_workspace',
|
||||
{ projectPath },
|
||||
);
|
||||
const nextViewPromise = hydrateSession
|
||||
? invoke<DesignView | null>('hydrate_design_agent_session', {
|
||||
projectPath,
|
||||
})
|
||||
: Promise.resolve(null);
|
||||
const [nextFiles, nextView] = await Promise.all([
|
||||
nextFilesPromise,
|
||||
nextViewPromise,
|
||||
]);
|
||||
if (hydrateSession) setView(nextView);
|
||||
setFiles(nextFiles);
|
||||
setExpandedPaths((current) => {
|
||||
const next = new Set(current);
|
||||
if (current.size === 0) {
|
||||
nextFiles
|
||||
.filter((entry) => entry.kind === 'directory')
|
||||
.forEach((entry) => next.add(entry.path));
|
||||
} else {
|
||||
nextFiles
|
||||
.filter(
|
||||
(entry) => entry.kind === 'directory' && next.has(entry.path),
|
||||
)
|
||||
.forEach((entry) => next.add(entry.path));
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setSelectedPath((current) =>
|
||||
current &&
|
||||
nextFiles.some(
|
||||
(entry) => entry.kind !== 'directory' && entry.path === current,
|
||||
)
|
||||
? current
|
||||
: null,
|
||||
);
|
||||
} catch (nextError) {
|
||||
setError(
|
||||
nextError instanceof Error ? nextError.message : String(nextError),
|
||||
);
|
||||
if (showLoading) setFiles([]);
|
||||
} finally {
|
||||
if (showLoading) setLoading(false);
|
||||
}
|
||||
},
|
||||
[projectPath],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void loadWorkspace();
|
||||
void loadWorkspace({ showLoading: true, hydrateSession: true });
|
||||
}, [loadWorkspace]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -245,11 +262,20 @@ export function DesignWorkspacePanel({
|
||||
}
|
||||
let disposed = false;
|
||||
let cleanup: (() => void) | null = null;
|
||||
const scheduleRefresh = () => {
|
||||
if (refreshTimerRef.current !== null) {
|
||||
window.clearTimeout(refreshTimerRef.current);
|
||||
}
|
||||
refreshTimerRef.current = window.setTimeout(() => {
|
||||
refreshTimerRef.current = null;
|
||||
void loadWorkspace({ showLoading: false, hydrateSession: false });
|
||||
}, 250);
|
||||
};
|
||||
void listen<{ projectPath: string; kind: string }>(
|
||||
'design-agent-update',
|
||||
(event) => {
|
||||
if (!disposed && event.payload.projectPath === projectPath) {
|
||||
void loadWorkspace();
|
||||
scheduleRefresh();
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -263,6 +289,10 @@ export function DesignWorkspacePanel({
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
disposed = true;
|
||||
if (refreshTimerRef.current !== null) {
|
||||
window.clearTimeout(refreshTimerRef.current);
|
||||
refreshTimerRef.current = null;
|
||||
}
|
||||
cleanup?.();
|
||||
};
|
||||
}, [loadWorkspace, projectPath]);
|
||||
@@ -316,7 +346,9 @@ export function DesignWorkspacePanel({
|
||||
<button
|
||||
type="button"
|
||||
className="design-workspace-panel__refresh"
|
||||
onClick={() => void loadWorkspace()}
|
||||
onClick={() =>
|
||||
void loadWorkspace({ showLoading: true, hydrateSession: true })
|
||||
}
|
||||
disabled={loading}
|
||||
aria-label="刷新策划工作区"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user