feat: update electron main and React components

This commit is contained in:
2026-03-19 23:06:26 +08:00
parent 4a8220652e
commit 6858acfafa
6 changed files with 82 additions and 15 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
]
}
}

View File

@@ -30,6 +30,9 @@ if (argsGlobal.headless) {
} }
app.commandLine.appendSwitch('disable-gpu-shader-disk-cache'); app.commandLine.appendSwitch('disable-gpu-shader-disk-cache');
app.commandLine.appendSwitch('enable-webgl');
app.commandLine.appendSwitch('ignore-gpu-blacklist');
app.commandLine.appendSwitch('enable-accelerated-2d-canvas');
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);

View File

@@ -19,6 +19,21 @@ console.log = (...args) => { window.electronAPI?.log('log', ...args); _origLog.a
console.error = (...args) => { window.electronAPI?.log('error', ...args); _origError.apply(console, args); }; console.error = (...args) => { window.electronAPI?.log('error', ...args); _origError.apply(console, args); };
console.warn = (...args) => { window.electronAPI?.log('warn', ...args); _origWarn.apply(console, args); }; console.warn = (...args) => { window.electronAPI?.log('warn', ...args); _origWarn.apply(console, args); };
const STORAGE_KEY = 'xc-docs-config';
function loadStoredDocsPath(): string {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const config = JSON.parse(stored);
return config.docsPath || '';
}
} catch {
// Ignore parse errors
}
return '';
}
type Page = 'docs' | 'blueprint'; type Page = 'docs' | 'blueprint';
declare global { declare global {
@@ -33,7 +48,7 @@ declare global {
function App() { function App() {
const [currentPage, setCurrentPage] = useState<Page>('docs'); const [currentPage, setCurrentPage] = useState<Page>('docs');
const [docsPath, setDocsPath] = useState<string>(''); const [docsPath, setDocsPath] = useState<string>(loadStoredDocsPath());
const [showAddModal, setShowAddModal] = useState(false); const [showAddModal, setShowAddModal] = useState(false);
// Docs state lifted from ApiDocViewer to persist across view changes // Docs state lifted from ApiDocViewer to persist across view changes
@@ -89,7 +104,8 @@ function App() {
<main className="flex-1 overflow-hidden"> <main className="flex-1 overflow-hidden">
{currentPage === 'docs' ? ( {currentPage === 'docs' ? (
<ApiDocViewer <ApiDocViewer
onDocsPathChange={setDocsPath} docsPath={docsPath}
setDocsPath={setDocsPath}
showAddModal={showAddModal} showAddModal={showAddModal}
onCloseAddModal={() => setShowAddModal(false)} onCloseAddModal={() => setShowAddModal(false)}
externalDocs={externalDocs} externalDocs={externalDocs}

View File

@@ -13,7 +13,8 @@ interface ExternalDoc {
} }
interface ApiDocViewerProps { interface ApiDocViewerProps {
onDocsPathChange?: (path: string) => void; docsPath?: string;
setDocsPath?: (path: string) => void;
showAddModal?: boolean; showAddModal?: boolean;
onCloseAddModal?: () => void; onCloseAddModal?: () => void;
externalDocs: ExternalDoc[]; externalDocs: ExternalDoc[];
@@ -45,7 +46,8 @@ function saveStoredConfig(docsPath: string, externalDocs: ExternalDoc[], selecte
} }
export const ApiDocViewer = ({ export const ApiDocViewer = ({
onDocsPathChange, docsPath: docsPathProp,
setDocsPath: setDocsPathProp,
showAddModal, showAddModal,
onCloseAddModal, onCloseAddModal,
externalDocs, externalDocs,
@@ -59,10 +61,17 @@ export const ApiDocViewer = ({
}: ApiDocViewerProps) => { }: ApiDocViewerProps) => {
const stored = loadStoredConfig(); const stored = loadStoredConfig();
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [docsPath, setDocsPath] = useState(stored.docsPath) const [docsPath, setDocsPath] = useState(docsPathProp ?? stored.docsPath)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [errorMsg, setErrorMsg] = useState<string | null>(null) const [errorMsg, setErrorMsg] = useState<string | null>(null)
// Sync with prop when it changes (e.g., when switching views back to docs)
useEffect(() => {
if (docsPathProp !== undefined) {
setDocsPath(docsPathProp)
}
}, [docsPathProp])
useEffect(() => { useEffect(() => {
if (showAddModal) { if (showAddModal) {
setShowModal(true) setShowModal(true)
@@ -94,8 +103,9 @@ export const ApiDocViewer = ({
} }
}, [externalDocs, fileTree, selectedPath, stored.selectedPath, setSelectedPath, setCurrentContent]) }, [externalDocs, fileTree, selectedPath, stored.selectedPath, setSelectedPath, setCurrentContent])
// Save to localStorage when docsPath changes
useEffect(() => { useEffect(() => {
if (docsPath && externalDocs.length > 0) { if (docsPath) {
saveStoredConfig(docsPath, externalDocs, selectedPath) saveStoredConfig(docsPath, externalDocs, selectedPath)
} }
}, [docsPath, externalDocs, selectedPath]) }, [docsPath, externalDocs, selectedPath])
@@ -212,7 +222,7 @@ export const ApiDocViewer = ({
if (success) { if (success) {
setShowModal(false) setShowModal(false)
onCloseAddModal?.() onCloseAddModal?.()
onDocsPathChange?.(docsPath.trim()) setDocsPathProp?.(docsPath.trim())
} }
} }

View File

@@ -1,8 +1,20 @@
import Scene3D from './Scene3D'; import Scene3D from './Scene3D';
import DetailPanel from './DetailPanel'; import DetailPanel from './DetailPanel';
import { useBlueprintStore } from '../../store/blueprintStore'; import { useBlueprintStore } from '../../store/blueprintStore';
import { useEffect, useCallback } from 'react'; import { useEffect, useCallback, Component, ReactNode } from 'react';
import { parseBlueprintFromMd } from '../../data/blueprintParser'; import { parseBlueprintFromMd, blueprintData } from '../../data/blueprintParser';
class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(e: Error, info: React.ErrorInfo) {
console.error('[BlueprintPage] Error:', e, info);
}
render() {
if (this.state.hasError) return <div className="text-white p-4">3D渲染出错</div>;
return this.props.children;
}
}
interface BlueprintPageProps { interface BlueprintPageProps {
docsPath: string; docsPath: string;
@@ -12,7 +24,11 @@ export default function BlueprintPage({ docsPath }: BlueprintPageProps) {
const setBlueprintData = useBlueprintStore(state => state.setBlueprintData); const setBlueprintData = useBlueprintStore(state => state.setBlueprintData);
const loadBlueprint = useCallback(async () => { const loadBlueprint = useCallback(async () => {
if (!docsPath) return; // Use default blueprint data if no docsPath is set
if (!docsPath) {
setBlueprintData(blueprintData);
return;
}
const blueprintPath = docsPath.replace(/\\/g, '/') + '/blueprint.md'; const blueprintPath = docsPath.replace(/\\/g, '/') + '/blueprint.md';
let content: string | null = null; let content: string | null = null;
@@ -31,6 +47,9 @@ export default function BlueprintPage({ docsPath }: BlueprintPageProps) {
if (content) { if (content) {
const data = parseBlueprintFromMd(content); const data = parseBlueprintFromMd(content);
setBlueprintData(data); setBlueprintData(data);
} else {
// Fallback to default if file not found
setBlueprintData(blueprintData);
} }
}, [docsPath, setBlueprintData]); }, [docsPath, setBlueprintData]);
@@ -41,7 +60,9 @@ export default function BlueprintPage({ docsPath }: BlueprintPageProps) {
return ( return (
<div className="flex h-full"> <div className="flex h-full">
<div className="flex-1"> <div className="flex-1">
<ErrorBoundary>
<Scene3D /> <Scene3D />
</ErrorBoundary>
</div> </div>
<DetailPanel /> <DetailPanel />
</div> </div>

View File

@@ -6,7 +6,15 @@ import SystemStructure from './SystemStructure';
export default function Scene3D() { export default function Scene3D() {
return ( return (
<div className="w-full h-full bg-black"> <div className="w-full h-full bg-black">
<Canvas> <Canvas
gl={{
antialias: true,
alpha: false,
powerPreference: 'high-performance',
failIfMajorPerformanceCaveat: false,
}}
dpr={[1, 2]}
>
<PerspectiveCamera makeDefault position={[12, 10, 12]} fov={50} /> <PerspectiveCamera makeDefault position={[12, 10, 12]} fov={50} />
<OrbitControls enablePan enableZoom enableRotate /> <OrbitControls enablePan enableZoom enableRotate />