fix: 去除路由改为 Store 控制当前激活 Tab

This commit is contained in:
奇趣保罗 2026-01-22 11:27:02 +08:00
parent cc3a8984c1
commit ff35f5b105
5 changed files with 23 additions and 55 deletions

View File

@ -1,5 +1,3 @@
import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useProjectTabsStore } from "@/store/project-tabs-store"; import { useProjectTabsStore } from "@/store/project-tabs-store";
@ -10,16 +8,6 @@ export function ProjectTabsBar() {
const activateTab = useProjectTabsStore((state) => state.activateTab); const activateTab = useProjectTabsStore((state) => state.activateTab);
const activateHome = useProjectTabsStore((state) => state.activateHome); const activateHome = useProjectTabsStore((state) => state.activateHome);
const closeTab = useProjectTabsStore((state) => state.closeTab); const closeTab = useProjectTabsStore((state) => state.closeTab);
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const active = tabs.find((tab) => tab.id === activeTabId);
if (!active) return;
const targetPath = active.kind === "home" ? "/" : `/editor/${active.projectId}`;
if (location.pathname === targetPath) return;
navigate(targetPath);
}, [activeTabId, tabs, location.pathname, navigate]);
function handleSelect(tabId: string, kind: "home" | "project") { function handleSelect(tabId: string, kind: "home" | "project") {
if (kind === "home") activateHome(); if (kind === "home") activateHome();

View File

@ -1,26 +1,26 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { createHashRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import "./index.css"; import "./index.css";
import Home from "./pages/home.tsx"; import Home from "./pages/home.tsx";
import Editor from "./pages/editor.tsx"; import Editor from "./pages/editor.tsx";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { useProjectTabsStore } from "@/store/project-tabs-store";
const router = createHashRouter([ function App() {
{ const tabs = useProjectTabsStore((state) => state.tabs);
path: "/", const activeTabId = useProjectTabsStore((state) => state.activeTabId);
element: <Home />, const active = tabs.find((tab) => tab.id === activeTabId);
},
{ if (active?.kind === "project") {
path: "/editor/:id", return <Editor projectId={active.projectId} />;
element: <Editor />, }
},
]); return <Home />;
}
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<RouterProvider router={router} /> <App />
<Toaster position="top-center" /> <Toaster position="top-center" />
</StrictMode> </StrictMode>
); );

View File

@ -1,7 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type React from "react"; import type React from "react";
import { useParams, useNavigate } from "react-router";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@ -18,7 +17,7 @@ import {
updateProject, updateProject,
deleteProjectDeep, deleteProjectDeep,
} from "@/lib/db"; } from "@/lib/db";
import { ArrowBigDownDash, ArrowBigUpDash, Brackets, CaseSensitive, Filter, FolderOpen, Languages, LocateFixed, MoreVertical, PencilLine, Reply, Save, Settings, Trash2 } from "lucide-react"; import { ArrowBigDownDash, ArrowBigUpDash, Brackets, CaseSensitive, Filter, FolderOpen, Languages, LocateFixed, MoreVertical, PencilLine, Save, Settings, Trash2 } from "lucide-react";
import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit"; import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit";
import { buildStructureFromObject, flattenEntries, flattenValues, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath, moveEntryByOffset } from "@/lib/i18n-structure"; import { buildStructureFromObject, flattenEntries, flattenValues, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath, moveEntryByOffset } from "@/lib/i18n-structure";
import { ImportLanguageModal } from "@/components/biz/import-language-modal"; import { ImportLanguageModal } from "@/components/biz/import-language-modal";
@ -51,9 +50,11 @@ import { loadNativeProjectSources, saveNativeLanguageFile, type LoadedProjectSou
import { useProjectSourcesStore } from "@/store/sources-store"; import { useProjectSourcesStore } from "@/store/sources-store";
import CommonMenubar, { type CommonMenubarItem } from "@/components/biz/editor/common-menubar"; import CommonMenubar, { type CommonMenubarItem } from "@/components/biz/editor/common-menubar";
export default function Editor() { type EditorProps = {
const { id: projectId } = useParams(); projectId?: string;
const navigate = useNavigate(); };
export default function Editor({ projectId }: EditorProps) {
const [project, setProject] = useState<Project | null>(null); const [project, setProject] = useState<Project | null>(null);
const [structure, setStructure] = useState<ProjectStructure | null>(null); const [structure, setStructure] = useState<ProjectStructure | null>(null);
const [languages, setLanguages] = useState<string[]>([]); const [languages, setLanguages] = useState<string[]>([]);
@ -605,23 +606,6 @@ export default function Editor() {
<CommonMenubar disabledItems={disabledItems} onClickItem={onClickItem} /> <CommonMenubar disabledItems={disabledItems} onClickItem={onClickItem} />
<div className="flex items-center justify-end gap-2 text-foreground"> <div className="flex items-center justify-end gap-2 text-foreground">
<HeaderConnectionIndicator projectId={projectId ?? ""} /> <HeaderConnectionIndicator projectId={projectId ?? ""} />
{!structure ? (
<Button onClick={() => { setSourcesWizardOpen(true); }}>
<FolderOpen />
</Button>
) : (
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => { setSourcesWizardOpen(true); }}>
<FolderOpen />
</Button>
<Button variant="outline" onClick={() => { setImportOpen(true); }}>
<Reply />
</Button>
</div>
)}
{projectId && ( {projectId && (
<SyncFromFilesButton <SyncFromFilesButton
projectId={projectId ?? ""} projectId={projectId ?? ""}
@ -640,9 +624,9 @@ export default function Editor() {
</Button> </Button>
)} )}
{project && ( {project && (
<Button variant="outline" onClick={() => setSettingsOpen(true)}> <Button variant="ghost" onClick={() => setSettingsOpen(true)}>
<Settings /> <Settings />
<span className="sr-only"></span>
</Button> </Button>
)} )}
</div> </div>
@ -832,7 +816,6 @@ export default function Editor() {
await deleteProjectDeep(projectId); await deleteProjectDeep(projectId);
forgetProjectInTabs(projectId); forgetProjectInTabs(projectId);
activateHomeTab(); activateHomeTab();
navigate("/");
}} }}
/> />

View File

@ -1,5 +1,4 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { createProject, deleteProjectDeep, listProjects, type Project, updateProject } from "@/lib/db"; import { createProject, deleteProjectDeep, listProjects, type Project, updateProject } from "@/lib/db";
@ -26,7 +25,6 @@ function App() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
const [currentProject, setCurrentProject] = useState<Project | null>(null); const [currentProject, setCurrentProject] = useState<Project | null>(null);
const navigate = useNavigate();
const flags = useConnectionFlags(); const flags = useConnectionFlags();
const activateHomeTab = useProjectTabsStore((state) => state.activateHome); const activateHomeTab = useProjectTabsStore((state) => state.activateHome);
const registerProjectsInTabs = useProjectTabsStore((state) => state.registerProjects); const registerProjectsInTabs = useProjectTabsStore((state) => state.registerProjects);
@ -119,14 +117,12 @@ function App() {
className="relative cursor-pointer rounded-md border p-4 transition hover:shadow-sm" className="relative cursor-pointer rounded-md border p-4 transition hover:shadow-sm"
onClick={() => { onClick={() => {
openProjectTab(p); openProjectTab(p);
navigate(`/editor/${p.id}`);
}} }}
role="button" role="button"
tabIndex={0} tabIndex={0}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") { if (e.key === "Enter" || e.key === " ") {
openProjectTab(p); openProjectTab(p);
navigate(`/editor/${p.id}`);
} }
}} }}
> >

View File

@ -227,7 +227,8 @@ export const useProjectTabsStore = create<ProjectTabsStore>((set, get) => ({
get().closeProjectTab(projectId); get().closeProjectTab(projectId);
set((state) => { set((state) => {
if (!state.projectIndex[projectId]) return state; if (!state.projectIndex[projectId]) return state;
const { [projectId]: _removed, ...rest } = state.projectIndex;
const { [projectId]: _removed, ...rest } = state.projectIndex; // eslint-disable-line @typescript-eslint/no-unused-vars
return { return {
projectIndex: rest, projectIndex: rest,
lastActiveProjectId: state.lastActiveProjectId === projectId ? null : state.lastActiveProjectId, lastActiveProjectId: state.lastActiveProjectId === projectId ? null : state.lastActiveProjectId,