diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts index d37de943483..58e2eb19d21 100644 --- a/apps/desktop/electron.vite.config.ts +++ b/apps/desktop/electron.vite.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ plugins: [ tsconfigPaths, externalizeDepsPlugin({ - exclude: ["@superset/*"], + exclude: ["@superset/*", "electron-store"], }), ], diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 889c4e1346d..58b665ec379 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -44,6 +44,7 @@ "clsx": "^2.1.1", "dotenv": "^17.2.3", "electron-router-dom": "^2.1.0", + "electron-store": "^11.0.2", "fast-glob": "^3.3.3", "framer-motion": "^12.23.24", "http-proxy": "^1.18.1", diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index a0041bcc922..bf0ccab8a89 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -1,6 +1,7 @@ import path from "node:path"; import { app } from "electron"; import { makeAppSetup } from "lib/electron-app/factories/app/setup"; +import { registerStorageHandlers } from "./lib/storage-ipcs"; import { MainWindow } from "./windows/main"; // Protocol scheme for deep linking @@ -23,6 +24,9 @@ app.on("open-url", (event, url) => { event.preventDefault(); }); +// Register storage IPC handlers +registerStorageHandlers(); + // Allow multiple instances - removed single instance lock (async () => { await app.whenReady(); diff --git a/apps/desktop/src/main/lib/storage-ipcs.ts b/apps/desktop/src/main/lib/storage-ipcs.ts new file mode 100644 index 00000000000..a3582656464 --- /dev/null +++ b/apps/desktop/src/main/lib/storage-ipcs.ts @@ -0,0 +1,23 @@ +import { ipcMain } from "electron"; +import { store } from "./storage-manager"; + +/** + * Register storage IPC handlers + * These handlers provide access to electron-store from the renderer process + */ +export function registerStorageHandlers() { + ipcMain.handle("storage:get", async (_event, input: { key: string }) => { + return store.get(input.key); + }); + + ipcMain.handle( + "storage:set", + async (_event, input: { key: string; value: any }) => { + store.set(input.key, input.value); + }, + ); + + ipcMain.handle("storage:delete", async (_event, input: { key: string }) => { + store.delete(input.key); + }); +} diff --git a/apps/desktop/src/main/lib/storage-manager.ts b/apps/desktop/src/main/lib/storage-manager.ts new file mode 100644 index 00000000000..b3afb422489 --- /dev/null +++ b/apps/desktop/src/main/lib/storage-manager.ts @@ -0,0 +1,12 @@ +import Store from "electron-store"; +import { homedir } from "node:os"; +import { join } from "node:path"; + +/** + * Electron store instance for persisting application state + * Stores data in ~/.superset/app-state.json + */ +export const store = new Store({ + cwd: join(homedir(), ".superset"), + name: "app-state", +}); diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts index a914a2032e5..dd499f9ce42 100644 --- a/apps/desktop/src/preload/index.ts +++ b/apps/desktop/src/preload/index.ts @@ -10,6 +10,11 @@ declare global { interface Window { App: typeof API; ipcRenderer: typeof ipcRendererAPI; + electronStore: { + get: (key: string) => any; + set: (key: string, value: any) => void; + delete: (key: string) => void; + }; } } @@ -66,5 +71,13 @@ const ipcRendererAPI = { // Expose electron-trpc IPC channel FIRST (must be before contextBridge calls) exposeElectronTRPC(); +// Expose electron-store API via IPC +const electronStoreAPI = { + get: (key: string) => ipcRenderer.invoke("storage:get", { key }), + set: (key: string, value: any) => ipcRenderer.invoke("storage:set", { key, value }), + delete: (key: string) => ipcRenderer.invoke("storage:delete", { key }), +}; + contextBridge.exposeInMainWorld("App", API); contextBridge.exposeInMainWorld("ipcRenderer", ipcRendererAPI); +contextBridge.exposeInMainWorld("electronStore", electronStoreAPI); diff --git a/apps/desktop/src/renderer/lib/electron-storage.ts b/apps/desktop/src/renderer/lib/electron-storage.ts new file mode 100644 index 00000000000..455c7e7c5b9 --- /dev/null +++ b/apps/desktop/src/renderer/lib/electron-storage.ts @@ -0,0 +1,20 @@ +import { createJSONStorage } from "zustand/middleware"; + +/** + * Custom Zustand storage adapter that uses electron-store for persistence via IPC + * Stores state in ~/.superset/app-state.json + */ +const electronStorageAdapter = { + getItem: async (name: string): Promise => { + const value = await window.electronStore.get(name); + return value as string | null; + }, + setItem: async (name: string, value: string): Promise => { + await window.electronStore.set(name, value); + }, + removeItem: async (name: string): Promise => { + await window.electronStore.delete(name); + }, +}; + +export const electronStorage = createJSONStorage(() => electronStorageAdapter); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx index 2a952d7979d..535ed6f84f8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/NewWorkspaceView.tsx @@ -1,4 +1,16 @@ +import { useWorkspacesStore } from "renderer/stores/workspaces"; +import { useAddTab } from "renderer/stores"; + export function NewWorkspaceView() { + const activeWorkspaceId = useWorkspacesStore( + (state) => state.activeWorkspaceId, + ); + const addWorkspace = useWorkspacesStore((state) => state.addWorkspace); + const markWorkspaceAsUsed = useWorkspacesStore( + (state) => state.markWorkspaceAsUsed, + ); + const addTab = useAddTab(); + return (
@@ -29,11 +41,27 @@ export function NewWorkspaceView() {