* [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)
134 lines
5.7 KiB
TypeScript
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>
|
|
);
|
|
}
|