Files
gridline/src/App.tsx
T
adrianbonpin 32a7b852ec v0.7.8: MySQL/SQLite backup-sync, Excel export, query cancel, settings import/export, Windows title-bar fix, SQLite table editor (#16)
* [P1-T1] feat(backup): MySQL/SQLite backup models + db_type on SyncOptions (Task 1.1)

* [P1-T2] feat: enable tools(mysql,sqlite) + tableManagement(sqlite) + add fflate (Task 1.2)

* [P1-T3] feat(cancel): CancelHandle enum + CancelRegistry (Task 1.3)

* [P2-T1] feat(export): hand-rolled XLSX writer with inline-string cells (Task 2.1)

* [P2-T2] feat(backup): SQLite .dump/restore/sync core, fail-closed virtual tables (Task 2.2)

* [P2-T3] feat(backup): MySQL dump/restore/sync arg builders + tool resolution (Task 2.3)

* [P2-T4] feat(settings): pure import validator + SettingsExport envelope + Store.apply_settings (Task 2.4)

* [P2-T5] feat(table-editor): SQLite create/diff/rebuild SQL generation, fail-closed AUTOINCREMENT (Task 2.5)

* [P3-T1] feat(commands): MySQL/SQLite backup + settings export/import commands + wrappers (Task 3.1)

* [P3-T2] feat(cancel): capture cancel primitives at connect; cancel_query command; SQLite interrupt test (Task 3.2)

* [P3-T3] feat(table-editor): SQLite object-change dispatch + execute_change Ddl/RebuildTable (Task 3.3)

* [P4-T1] feat(tools): DB-aware backup/restore/sync pages (Task 4.1)

* [P4-T2] feat(export): xlsx export in grid toolbar + overflow menu (Task 4.2)

* [P4-T3] feat(query): cancel button wired to cancelQuery (Task 4.3)

* [P4-T4] feat(settings): export/import buttons + validation gate (Task 4.4)

* [P4-T5] fix(ui): gate macOS overlay drag strip to macOS only (Task 4.5)

* [P4-T6] feat(table-editor): SQLite Create/Edit Table mode (Task 4.6)

* [P5-T1] chore: bump 0.7.7 -> 0.7.8 + README/AGENTS/ROADMAP status (Task 5.1)

* [P5-T2] build(release): bundle mariadb-dump + mariadb client (system-first fallback) (Task 5.2)

* fix(cancel): propagate cancellations past wrapped->raw fallback (SQLite/PG/MySQL) + MySQL CONNECTION_ID cast

* fix(export): Excel export from overflow menu did nothing + add export success/error toasts

* fix(export): tree kebab export fetches table data when rows not loaded

* docs(readme): surface v0.7.8 features (MySQL/SQLite backup-sync, Excel export, query cancel, SQLite table editor, settings import/export)
2026-08-08 00:09:36 +08:00

134 lines
5.7 KiB
TypeScript

import { useEffect } from "react";
import { useConnectionStore } from "./stores/connectionStore";
import { useSettingsStore } from "./stores/settingsStore";
import { useUiStore } from "./stores/uiStore";
import { useBackupStore } from "./stores/backupStore";
import { HomeScreen } from "./components/layout/HomeScreen";
import { SettingsPage } from "./components/settings/SettingsPage";
import { NewConnectionScreen } from "./components/connections/NewConnectionScreen";
import { ErrorBanner } from "./components/ui/ErrorBanner";
import { ToastContainer } from "./components/ui/Toast";
import { DbViewerScreen } from "./components/db-viewer/DbViewerScreen";
import { useAppearance } from "./hooks/useAppearance";
import { isMacOS } from "./lib/platform";
import { getCurrentWindow } from "@tauri-apps/api/window";
const VIEW_TITLES: Record<string, string> = {
home: "Gridline",
settings: "Settings",
"new-connection": "New Connection",
"db-viewer": "",
};
export default function App() {
const activeView = useUiStore((s) => s.activeView);
const setActiveView = useUiStore((s) => s.setActiveView);
const settingsReturnView = useUiStore((s) => s.settingsReturnView);
const openSettings = useUiStore((s) => s.openSettings);
const loadConnections = useConnectionStore((s) => s.loadAll);
const loadSettings = useSettingsStore((s) => s.load);
const settings = useSettingsStore((s) => s.settings);
const connectionError = useConnectionStore((s) => s.error);
const activeFolderId = useUiStore((s) => s.activeFolderId);
const folders = useConnectionStore((s) => s.folders);
const tags = useConnectionStore((s) => s.tags);
const prefilledConnectionString = useUiStore((s) => s.prefilledConnectionString);
const clearPrefilledConnectionString = useUiStore((s) => s.clearPrefilledConnectionString);
useAppearance(settings?.theme ?? "system", settings?.font_size ?? "medium", settings?.accent_color ?? "#2563EB");
useEffect(() => {
loadConnections();
loadSettings();
// Init backup event listener (noop outside Tauri)
useBackupStore.getState().initListener().catch(() => {});
}, [loadConnections, loadSettings]);
// If the user hasn't navigated anywhere yet, start in the configured default folder
const setActiveFolderId = useUiStore((s) => s.setActiveFolderId);
const defaultFolderId = settings?.default_folder_id;
useEffect(() => {
if (defaultFolderId && useUiStore.getState().activeFolderId === null) {
setActiveFolderId(defaultFolderId);
}
}, [defaultFolderId, setActiveFolderId]);
useEffect(() => {
let title = VIEW_TITLES[activeView] ?? "Gridline";
if (activeView === "db-viewer") {
const conn = useConnectionStore.getState().connections.find(
(c) => c.id === useUiStore.getState().activeConnectionId
);
if (conn) title = conn.name;
}
document.title = title;
try {
getCurrentWindow().setTitle(title).catch(() => {
// Ignore environments where the Tauri API is unavailable (tests, browser)
});
} catch {
// getCurrentWindow can throw outside of a Tauri runtime
}
}, [activeView]);
const keepDbViewerMounted =
activeView === "db-viewer" ||
(activeView === "settings" && settingsReturnView === "db-viewer");
const dbViewerVisible = activeView === "db-viewer";
return (
<div className="h-svh bg-canvas select-none flex flex-col overflow-hidden">
{typeof window !== "undefined" &&
"__TAURI_INTERNALS__" in window &&
isMacOS() && (
// macOS "Overlay" title bar: in-flow strip the window can be
// dragged by; traffic lights float over it. Only in Tauri.
<div
data-tauri-drag-region
aria-hidden
className="h-7 shrink-0 bg-canvas select-none"
/>
)}
<div className="flex-1 min-h-0">
{connectionError && (
<div className="px-6 pt-4">
<ErrorBanner
error={connectionError}
onRetry={loadConnections}
/>
</div>
)}
{activeView === "settings" && <SettingsPage />}
{activeView === "new-connection" && (
<NewConnectionScreen
defaultFolderId={activeFolderId}
prefilledConnectionString={prefilledConnectionString ?? ""}
folders={folders}
tags={tags}
onSaved={() => {
clearPrefilledConnectionString();
setActiveView("home");
}}
onCancel={() => {
clearPrefilledConnectionString();
setActiveView("home");
}}
/>
)}
{activeView === "home" && <HomeScreen />}
{keepDbViewerMounted && (
<div className={dbViewerVisible ? "contents" : "hidden"}>
<DbViewerScreen
connectionId={useUiStore.getState().activeConnectionId ?? ""}
onHome={() => setActiveView("home")}
onSettings={openSettings}
/>
</div>
)}
</div>
<ToastContainer />
</div>
);
}