Skip to content

Commit

Permalink
Toggle dark mode (#11)
Browse files Browse the repository at this point in the history
* feat: toggle theme provider

* chore: clean up

* chore: database connection item hover dark mode

* chore: error box dark mode

* remove use client directive

* refactor toggle theme

* slightly refactor

---------

Co-authored-by: Visal In <[email protected]>
  • Loading branch information
roth-dev and invisal authored Dec 17, 2024
1 parent c5e75bf commit fa9f8e9
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 85 deletions.
4 changes: 4 additions & 0 deletions electron/file-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ export function getUserDataPath(relativePath: string) {

return userRelativePath;
}

export function getUserDataFile(filename: string) {
return path.join(app.getPath("userData"), filename)
}
19 changes: 18 additions & 1 deletion electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import electronUpdater, { type AppUpdater } from "electron-updater";
import log from "electron-log";
import { type ConnectionStoreItem } from "@/lib/conn-manager-store";
import { bindDockerIpc } from "./ipc/docker";
import { Setting } from "./setting";
import { ThemeType } from "@/context/theme-provider";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -50,6 +52,8 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
? path.join(process.env.APP_ROOT, "public")
: RENDERER_DIST;

const settings = new Setting();
settings.load();
let win: BrowserWindow | null;

const STUDIO_ENDPOINT = "https://studio.outerbase.com/embed";
Expand All @@ -72,9 +76,14 @@ function createDatabaseWindow(
},
});

const theme = settings.get<ThemeType>("theme") || "light";

ConnectionPool.create(conn);

const queryString = new URLSearchParams({ name: conn.name }).toString();
const queryString = new URLSearchParams({
name: conn.name,
theme,
}).toString();

dbWindow.on("closed", () => {
win?.show();
Expand Down Expand Up @@ -226,3 +235,11 @@ ipcMain.handle("restart", () => {
ipcMain.handle("open-file-dialog", async (_, options: OpenDialogOptions) => {
return await dialog.showOpenDialog(options);
});

ipcMain.handle("get-setting", (_, key) => {
return settings.get(key);
});

ipcMain.handle("set-setting", (_, key, value) => {
settings.set(key, value);
});
8 changes: 6 additions & 2 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ const outerbaseIpc = {
},
},

// You can expose other APTs you need here.
// ...
setting: {
get: <T = string>(key: string): Promise<T> => ipcRenderer.invoke("get-setting", key),
set: <T = string>(key: string, value: T): Promise<void> =>
ipcRenderer.invoke("set-setting", key, value)
}

};

export type OuterbaseIpc = typeof outerbaseIpc;
Expand Down
63 changes: 63 additions & 0 deletions electron/setting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from "fs";
import { getUserDataFile } from "./file-helper";

interface Settings {
[key: string]: string;
}

export class Setting {
private filePath: string;
private settings: Settings;

constructor() {
this.filePath = getUserDataFile("settings.json");
this.settings = {};
}

/**
* load window file system
*/
load() {
try {
if (fs.existsSync(this.filePath)) {
const data = fs.readFileSync(this.filePath, "utf-8");
this.settings = JSON.parse(data) as Settings;
} else {
this.save(); // Create the file if it doesn't exist
}
} catch (error) {
console.error("Error loading settings:", error);
}
}

save() {
try {
fs.writeFileSync(
this.filePath,
JSON.stringify(this.settings, null, 2),
"utf-8",
);
} catch (error) {
console.error("Error saving settings:", error);
}
}

/**
*
* @param key string
* @returns any value
*/
get<T>(key: string): T | undefined {
return this.settings[key] as T;
}

/**
*
* @param key string
* @param value any
*/
set<T extends string>(key: string, value: T) {
this.settings[key] = value;
this.save();
}
}
22 changes: 14 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useMemo, useState } from "react";
import { Tab } from "./components/tabs";
import pkg from "./../package.json";
import InstanceTab from "./instance";
import DatabaseTab from "./database";
import { Toaster } from "./components/ui/toaster";
import { Tab } from "./components/tabs";
import { useMemo, useState } from "react";
import UpdateBar from "./components/update-bar";
import { Toaster } from "./components/ui/toaster";
import ThemeProvider from "./context/theme-provider";

import pkg from "./../package.json";

function App() {
function Main() {
const [selected, setSelected] = useState("connection");

const tabs = useMemo(() => {
Expand Down Expand Up @@ -42,7 +42,7 @@ function App() {
<div className="flex-1 overflow-hidden">
<Tab selected={selected} onChange={setSelected} tabs={tabs} />
</div>
<div className="flex items-center bg-gray-600 px-2 py-1 font-mono text-sm text-white">
<div className="flex items-center bg-gray-600 px-2 py-1 font-mono text-sm text-white dark:bg-neutral-900">
<div>Outerbase Studio v{pkg.version}</div>
<div className="flex-1"></div>
<UpdateBar />
Expand All @@ -53,4 +53,10 @@ function App() {
);
}

export default App;
export default function App() {
return (
<ThemeProvider>
<Main />
</ThemeProvider>
);
}
14 changes: 11 additions & 3 deletions src/components/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { cn } from "@/lib/utils";
import { useState } from "react";
import { useMemo, useState } from "react";
import { Button } from "./ui/button";
import { Sun, MoonStar } from "lucide-react";
import { useTheme } from "@/context/theme-provider";

interface TabItemProps {
name: string;
Expand All @@ -14,7 +17,9 @@ interface TabProps {
}

export function Tab({ tabs, selected, onChange }: TabProps) {
const { theme, toggleTheme } = useTheme();
const [mountedList] = useState<Set<string>>(() => new Set([selected]));
const logo = useMemo(() => (theme === "dark" ? "light" : "dark"), [theme]);

const normalClassName =
"p-2 border border-secondary border-b border-b-border px-4 flex items-center cursor-pointer";
Expand All @@ -39,9 +44,12 @@ export function Tab({ tabs, selected, onChange }: TabProps) {
{tab.name}
</div>
))}
<div className="flex flex-1 items-center justify-end border-b pr-4">
<div className="flex flex-1 items-center justify-end gap-2 border-b pr-4">
<Button onClick={() => toggleTheme()} variant="ghost">
{theme === "dark" ? <Sun /> : <MoonStar />}
</Button>
<img
src="https://www.outerbase.com/downloads/brand/outerbase_dark.svg"
src={`https://www.outerbase.com/downloads/brand/outerbase_${logo}.svg`}
className="h-4"
/>
</div>
Expand Down
59 changes: 59 additions & 0 deletions src/context/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
PropsWithChildren,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";

const KEY = "theme";

export type ThemeType = "dark" | "light";

type ContextType = {
theme: ThemeType;
toggleTheme: (theme?: ThemeType) => void;
};

const ThemeContext = createContext<ContextType>({
theme: "light",
toggleTheme: () => {},
});

export const useTheme = () => useContext(ThemeContext);

export default function ThemeProvider({
children,
}: PropsWithChildren<unknown>) {
const [theme, setTheme] = useState<ThemeType>("light");

const toggleTheme = useCallback(async () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
document.body.className = newTheme;
await window.outerbaseIpc.setting.set(KEY, newTheme);
}, [setTheme, theme]);

useEffect(() => {
(async () => {
const savedTheme = await window.outerbaseIpc.setting.get<ThemeType>(KEY);
if (savedTheme) {
setTheme(savedTheme);
document.body.className = savedTheme;
}
})();
}, []);

const value = useMemo(() => {
return {
theme,
toggleTheme,
};
}, [toggleTheme, theme]);

return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}
6 changes: 4 additions & 2 deletions src/database/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ function ConnectionItem({
exit={{ transform: "translateX(100%)" }}
key={item.id}
className={cn(
"flex cursor-pointer items-center gap-4 border-b p-4 hover:bg-gray-100",
selectedConnection === item.id ? "bg-gray-100" : "bg-background",
"flex cursor-pointer items-center gap-4 border-b p-4 hover:bg-gray-100 dark:hover:bg-neutral-800",
selectedConnection === item.id
? "bg-gray-100 dark:bg-neutral-900"
: "bg-background",
)}
>
<IconComponent className="h-8 w-8" />
Expand Down
Loading

0 comments on commit fa9f8e9

Please sign in to comment.