wechat_ai/src/App.jsx

115 lines
3.7 KiB
JavaScript

import { Component, useEffect, useState } from "react";
import MainShell from "./components/MainShell";
import AnnotationPage from "./pages/AnnotationPage";
import SecondaryWindow from "./windows/SecondaryWindow";
function AppErrorFallback({ error }) {
return (
<div className="grid min-h-dvh place-items-center bg-red-950 p-6 text-white">
<div className="w-full max-w-3xl rounded-[18px] border border-red-300 bg-white p-6 text-red-950 shadow-2xl">
<div className="text-[20px] font-black">应用渲染失败</div>
<div className="mt-4 space-y-2 font-mono text-[12px] leading-5">
<div>href: {window.location.href}</div>
<div>hash: {window.location.hash}</div>
<div>error: {String(error?.message || error)}</div>
</div>
</div>
</div>
);
}
class AppErrorBoundary extends Component {
state = { error: null };
componentDidCatch(error) {
this.setState({ error });
}
render() {
if (this.state.error) {
return <AppErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
function App() {
const [theme, setTheme] = useState(() => localStorage.getItem("theme") || "light");
const [activeTab, setActiveTab] = useState("clone");
const [activeKnowledge, setActiveKnowledge] = useState("全部");
const [activeSkill, setActiveSkill] = useState("全部");
const [settingSection, setSettingSection] = useState("基础配置");
const [route, setRoute] = useState(() => window.location.hash.replace("#", "") || "/");
useEffect(() => {
document.documentElement.dataset.theme = theme;
localStorage.setItem("theme", theme);
}, [theme]);
useEffect(() => {
const onStorage = (event) => {
if (event.key === "theme" && event.newValue) {
setTheme(event.newValue);
}
};
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}, []);
useEffect(() => {
const timers = new WeakMap();
const revealScrollbar = (event) => {
const target = event.target instanceof Element ? event.target : document.scrollingElement;
if (!target) return;
target.classList.add("scrollbar-active");
window.clearTimeout(timers.get(target));
timers.set(target, window.setTimeout(() => target.classList.remove("scrollbar-active"), 700));
};
document.addEventListener("scroll", revealScrollbar, true);
return () => document.removeEventListener("scroll", revealScrollbar, true);
}, []);
useEffect(() => {
const onHashChange = () => setRoute(window.location.hash.replace("#", "") || "/");
window.addEventListener("hashchange", onHashChange);
return () => window.removeEventListener("hashchange", onHashChange);
}, []);
const isWindowRoute = route.startsWith("/window/");
const isAnnotationRoute = route === "/annotate";
return (
<AppErrorBoundary>
<div className="min-h-dvh overflow-hidden rounded-[8px] bg-page text-text">
{isAnnotationRoute ? (
<AnnotationPage />
) : isWindowRoute ? (
<SecondaryWindow
route={route}
theme={theme}
setTheme={setTheme}
settingSection={settingSection}
setSettingSection={setSettingSection}
/>
) : (
<MainShell
activeTab={activeTab}
setActiveTab={setActiveTab}
theme={theme}
setTheme={setTheme}
activeKnowledge={activeKnowledge}
setActiveKnowledge={setActiveKnowledge}
activeSkill={activeSkill}
setActiveSkill={setActiveSkill}
/>
)}
</div>
</AppErrorBoundary>
);
}
export default App;