From aed5fc8ef0e082e075dfcb63461c333d8e881de5 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 23 Jun 2024 21:52:52 +0800 Subject: [PATCH] feat: setting appearance - font (#85) * feat: setting appearance Signed-off-by: Innei * feat: font family and text size Signed-off-by: Innei --------- Signed-off-by: Innei --- electron-builder.yml | 1 + icons/mgc/palette_cute_re.svg | 1 + package.json | 1 + pnpm-lock.yaml | 8 ++ src/main/tipc.ts | 8 ++ src/main/window.ts | 19 ++- src/renderer/src/App.tsx | 6 + .../src/components/ui/background/vibrancy.tsx | 7 +- src/renderer/src/components/ui/select.tsx | 9 +- src/renderer/src/components/ui/switch.tsx | 4 +- src/renderer/src/components/user-button.tsx | 13 +- .../src/modules/entry-column/index.tsx | 2 +- .../src/modules/entry-content/index.tsx | 16 ++- .../src/modules/settings/constants.ts | 34 +++++ src/renderer/src/modules/settings/control.tsx | 41 ++++++ .../src/modules/settings/modal/content.tsx | 35 +++-- src/renderer/src/modules/settings/section.tsx | 6 + .../src/modules/settings/tabs/apperance.tsx | 124 ++++++++++++++++++ src/renderer/src/modules/settings/title.tsx | 44 +++++-- src/renderer/src/modules/settings/utils.ts | 9 ++ .../(main)/(layer)/feeds/[feedId]/layout.tsx | 2 +- src/renderer/src/pages/settings/actions.tsx | 13 +- .../src/pages/settings/appearance.tsx | 16 +++ src/renderer/src/pages/settings/general.tsx | 46 +++++++ src/renderer/src/pages/settings/index.tsx | 54 +------- src/renderer/src/pages/settings/layout.tsx | 30 ++--- src/renderer/src/pages/settings/profile.tsx | 12 +- src/renderer/src/pages/settings/shortcuts.tsx | 32 +++-- src/renderer/src/providers/root-providers.tsx | 2 + .../src/providers/ui-setting-Initialize.tsx | 12 ++ src/renderer/src/store/ui.ts | 15 ++- src/renderer/src/styles/tailwind-extend.css | 2 +- src/shared/src/bridge.ts | 14 ++ 33 files changed, 512 insertions(+), 126 deletions(-) create mode 100644 icons/mgc/palette_cute_re.svg create mode 100644 src/renderer/src/modules/settings/constants.ts create mode 100644 src/renderer/src/modules/settings/control.tsx create mode 100644 src/renderer/src/modules/settings/section.tsx create mode 100644 src/renderer/src/modules/settings/tabs/apperance.tsx create mode 100644 src/renderer/src/modules/settings/utils.ts create mode 100644 src/renderer/src/pages/settings/appearance.tsx create mode 100644 src/renderer/src/pages/settings/general.tsx create mode 100644 src/renderer/src/providers/ui-setting-Initialize.tsx create mode 100644 src/shared/src/bridge.ts diff --git a/electron-builder.yml b/electron-builder.yml index 95ed4ae96..ce3b4f48a 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -24,6 +24,7 @@ files: - "out" - "resources" - "!node_modules" + - "node_modules/font-list/**/*" asarUnpack: - resources/** win: diff --git a/icons/mgc/palette_cute_re.svg b/icons/mgc/palette_cute_re.svg new file mode 100644 index 000000000..98e79cc1b --- /dev/null +++ b/icons/mgc/palette_cute_re.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index 98706615b..a69a7cc68 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "dayjs": "1.11.11", "dotenv": "16.4.5", "electron-updater": "^6.2.1", + "font-list": "1.5.1", "foxact": "0.2.35", "framer-motion": "11.2.11", "franc-min": "6.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c580fc414..c15d3b705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: electron-updater: specifier: ^6.2.1 version: 6.2.1 + font-list: + specifier: 1.5.1 + version: 1.5.1 foxact: specifier: 0.2.35 version: 0.2.35(react@18.3.1) @@ -3480,6 +3483,9 @@ packages: debug: optional: true + font-list@1.5.1: + resolution: {integrity: sha512-Hr5V0dsSv91wH3FgirXd7qh1PydqA/vMQyWjFFWn+lUPJtC+3i2tzgVqbLRcvQh87TGdbTGbAR3mEo4VlwC1jw==} + foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -9515,6 +9521,8 @@ snapshots: follow-redirects@1.15.6: {} + font-list@1.5.1: {} + foreground-child@3.1.1: dependencies: cross-spawn: 7.0.3 diff --git a/src/main/tipc.ts b/src/main/tipc.ts index 70a55f56b..94abeea4c 100644 --- a/src/main/tipc.ts +++ b/src/main/tipc.ts @@ -155,6 +155,14 @@ export const router = { }), openSettingWindow: t.procedure.action(async () => createSettingWindow()), + + getSystemFonts: t.procedure.action(async (): Promise => new Promise((resolve) => { + // NOTE: should external font-list deps + // use `require` to avoid bundling, vite behavior + require("font-list").getFonts().then((fonts) => { + resolve(fonts.map((font) => font.replaceAll("\"", ""))) + }) + })), } export type Router = typeof router diff --git a/src/main/window.ts b/src/main/window.ts index 04801b957..88624eba2 100644 --- a/src/main/window.ts +++ b/src/main/window.ts @@ -1,12 +1,18 @@ import path from "node:path" import { is } from "@electron-toolkit/utils" +import { callGlobalContextMethod } from "@shared/bridge" import type { BrowserWindowConstructorOptions } from "electron" import { BrowserWindow, shell } from "electron" import icon from "../../resources/icon.png?asset" import { store } from "./store" +const windows = { + settingWindow: null as BrowserWindow | null, + mainWindow: null as BrowserWindow | null, +} + export function createWindow( options: { extraPath?: string @@ -123,14 +129,21 @@ export const createMainWindow = () => { x: bounds.x, y: bounds.y, }) + windows.mainWindow = null }) + windows.mainWindow = window return window } -const windows = { - settingWindow: null as BrowserWindow | null, -} + export const createSettingWindow = () => { + // We need to open the setting modal in the main window when the main window exists, + // if we open a new window then the state between the two windows will be out of sync. + if (windows.mainWindow) { + windows.mainWindow.show() + callGlobalContextMethod(windows.mainWindow, "showSetting") + return + } if (windows.settingWindow) { windows.settingWindow.show() return diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 50c559571..2cbe45a22 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,13 +1,16 @@ import { queryClient } from "@renderer/lib/query-client" +import { registerGlobalContext } from "@shared/bridge" import { useEffect } from "react" import { Outlet } from "react-router-dom" import { useDark } from "./hooks/common/useDark" +import { useSettingModal } from "./modules/settings/modal/hooks" import { RootProviders } from "./providers/root-providers" import { handlers } from "./tipc" function App() { useDark() + const showSetting = useSettingModal() useEffect(() => { const cleanup = handlers?.invalidateQuery.listen((queryKey) => { queryClient.invalidateQueries({ @@ -15,6 +18,9 @@ function App() { }) }) + registerGlobalContext({ + showSetting, + }) return cleanup }, []) diff --git a/src/renderer/src/components/ui/background/vibrancy.tsx b/src/renderer/src/components/ui/background/vibrancy.tsx index 7a907d74b..0c7101fe9 100644 --- a/src/renderer/src/components/ui/background/vibrancy.tsx +++ b/src/renderer/src/components/ui/background/vibrancy.tsx @@ -1,13 +1,16 @@ import { useDark } from "@renderer/hooks" import { cn } from "@renderer/lib/utils" +import { useUIStore } from "@renderer/store" import { useMediaQuery } from "usehooks-ts" export const Vibrancy: Component< React.DetailedHTMLProps, HTMLDivElement> > = ({ className, children, ...rest }) => { + const opaqueSidebar = useUIStore((s) => s.opaqueSidebar) const canVibrancy = - window.electron && window.electron.process.platform === "darwin" - + window.electron && + window.electron.process.platform === "darwin" && + !opaqueSidebar const systemDark = useMediaQuery("(prefers-color-scheme: dark)") const { isDark } = useDark() diff --git a/src/renderer/src/components/ui/select.tsx b/src/renderer/src/components/ui/select.tsx index edf6c1ba6..35bf2375d 100644 --- a/src/renderer/src/components/ui/select.tsx +++ b/src/renderer/src/components/ui/select.tsx @@ -1,6 +1,5 @@ import * as SelectPrimitive from "@radix-ui/react-select" import { cn } from "@renderer/lib/utils" -import { Check, ChevronDown, ChevronUp } from "lucide-react" import * as React from "react" const Select = SelectPrimitive.Root @@ -25,7 +24,7 @@ const SelectTrigger = React.forwardRef< > {children} - + )) @@ -43,7 +42,7 @@ const SelectScrollUpButton = React.forwardRef< )} {...props} > - + )) SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName @@ -60,7 +59,7 @@ const SelectScrollDownButton = React.forwardRef< )} {...props} > - + )) SelectScrollDownButton.displayName = @@ -124,7 +123,7 @@ const SelectItem = React.forwardRef< > - + {children} diff --git a/src/renderer/src/components/ui/switch.tsx b/src/renderer/src/components/ui/switch.tsx index f4499b82d..f817e0ebf 100644 --- a/src/renderer/src/components/ui/switch.tsx +++ b/src/renderer/src/components/ui/switch.tsx @@ -8,7 +8,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( diff --git a/src/renderer/src/components/user-button.tsx b/src/renderer/src/components/user-button.tsx index a78886a85..f1a461263 100644 --- a/src/renderer/src/components/user-button.tsx +++ b/src/renderer/src/components/user-button.tsx @@ -5,7 +5,6 @@ import { } from "@renderer/components/ui/avatar" import { useSignOut } from "@renderer/hooks" import { loginHandler } from "@renderer/lib/auth" -import { tipcClient } from "@renderer/lib/client" import { APP_NAME } from "@renderer/lib/constants" import { nextFrame, stopPropagation } from "@renderer/lib/dom" import { cn } from "@renderer/lib/utils" @@ -134,14 +133,10 @@ export const ProfileButton: FC = memo((props) => { { - if (window.electron) { - tipcClient?.openSettingWindow() - } else { - // Here we need to delay one frame, so it's two raf, - // in order to have `point-event: none` recorded by RadixOverlay after modal is invoked in a certain scenario, - // and the page freezes after modal is turned off. - nextFrame(settingModalPresent) - } + // Here we need to delay one frame, so it's two raf, + // in order to have `point-event: none` recorded by RadixOverlay after modal is invoked in a certain scenario, + // and the page freezes after modal is turned off. + nextFrame(settingModalPresent) }} > Preferences diff --git a/src/renderer/src/modules/entry-column/index.tsx b/src/renderer/src/modules/entry-column/index.tsx index 794f5c52a..929a419e5 100644 --- a/src/renderer/src/modules/entry-column/index.tsx +++ b/src/renderer/src/modules/entry-column/index.tsx @@ -324,7 +324,7 @@ const ListHeader: FC<{ - +
Mark all as read?
diff --git a/src/renderer/src/modules/entry-content/index.tsx b/src/renderer/src/modules/entry-content/index.tsx index ad6260762..900bac195 100644 --- a/src/renderer/src/modules/entry-content/index.tsx +++ b/src/renderer/src/modules/entry-content/index.tsx @@ -9,7 +9,7 @@ import { WrappedElementProvider, } from "@renderer/providers/wrapped-element-provider" import { Queries } from "@renderer/queries" -import { useEntry, useFeedHeaderTitle } from "@renderer/store" +import { useEntry, useFeedHeaderTitle, useUIStore } from "@renderer/store" import { m } from "framer-motion" import { useEffect, useState } from "react" @@ -76,6 +76,8 @@ function EntryContentRender({ entryId }: { entryId: string }) { }, ) + const readerFontFamily = useUIStore((state) => state.readerFontFamily) + if (!entry) return null return ( @@ -83,13 +85,19 @@ function EntryContentRender({ entryId }: { entryId: string }) {
-
+
-
+
{(summary.isLoading || summary.data) && (
-
+
AI summary
diff --git a/src/renderer/src/modules/settings/constants.ts b/src/renderer/src/modules/settings/constants.ts new file mode 100644 index 000000000..796f16445 --- /dev/null +++ b/src/renderer/src/modules/settings/constants.ts @@ -0,0 +1,34 @@ +import type { SettingPageConfig } from "./utils" + +function getSettings() { + const map = import.meta.glob("../../pages/settings/*.tsx", { eager: true }) + + const settings = [] as { + name: string + iconName: string + path: string + Component: () => JSX.Element + priority: number + }[] + for (const path in map) { + const p = path.split("/").pop()?.replace(".tsx", "")! + + if (p === "index" || p === "layout") continue + + const Module = map[path] as { + Component: () => JSX.Element + loader: () => SettingPageConfig + } + + if (!Module.loader) continue + settings.push({ + ...Module.loader(), + Component: Module.Component, + path: p, + }) + } + + return settings.sort((a, b) => a.priority - b.priority) +} + +export const settings = /* #__PURE__ */ getSettings() diff --git a/src/renderer/src/modules/settings/control.tsx b/src/renderer/src/modules/settings/control.tsx new file mode 100644 index 000000000..66e03e576 --- /dev/null +++ b/src/renderer/src/modules/settings/control.tsx @@ -0,0 +1,41 @@ +import { Checkbox } from "@renderer/components/ui/checkbox" +import { Label } from "@renderer/components/ui/label" +import { Switch } from "@renderer/components/ui/switch" +import { useId } from "react" + +export const SettingCheckbox: Component<{ + label: string + checked: boolean + onCheckedChange: (checked: boolean) => void +}> = ({ checked, label, onCheckedChange }) => { + const id = useId() + return ( +
+ + +
+ ) +} + +export const SettingSwitch: Component<{ + label: string + checked: boolean + onCheckedChange: (checked: boolean) => void +}> = ({ checked, label, onCheckedChange }) => { + const id = useId() + return ( +
+ + +
+ ) +} diff --git a/src/renderer/src/modules/settings/modal/content.tsx b/src/renderer/src/modules/settings/modal/content.tsx index 0f0664ec9..f98bf5e26 100644 --- a/src/renderer/src/modules/settings/modal/content.tsx +++ b/src/renderer/src/modules/settings/modal/content.tsx @@ -1,14 +1,18 @@ import { MotionButtonBase } from "@renderer/components/ui/button" import { LoadingCircle } from "@renderer/components/ui/loading" import { useCurrentModal } from "@renderer/components/ui/modal" -import { settingTabs } from "@renderer/lib/constants" import { preventDefault } from "@renderer/lib/dom" -import { SettingsTitle } from "@renderer/modules/settings/title" +import { + SettingsSidebarTitle, + SettingsTitle, +} from "@renderer/modules/settings/title" import { createContextState } from "foxact/context-state" import { m } from "framer-motion" -import type { FC, PropsWithChildren } from "react" +import type { FC, PropsWithChildren, ReactNode } from "react" import { createElement, useEffect, useState } from "react" +import { settings } from "../constants" + const [SettingTabProvider, useSettingTab, useSetSettingTab] = createContextState("") @@ -28,6 +32,10 @@ function Layout(props: PropsWithChildren) { const { children } = props const setTab = useSetSettingTab() const tab = useSettingTab() + + useEffect(() => { + if (!tab) setTab(settings[0].path) + }, []) return (
- {settingTabs.map((t) => ( + {settings.map((t) => ( ))}
-
+
{children}
@@ -87,7 +95,7 @@ const Close = () => { } const Content = () => { - const key = useSettingTab() || "index" + const key = useSettingTab() const Component = pages[key] if (!Component) return null @@ -100,7 +108,7 @@ const LoadRemixAsyncComponent: FC<{ }> = ({ loader }) => { const [loading, setLoading] = useState(true) - const [Component, setComponent] = useState({ + const [Component, setComponent] = useState<{ c: () => ReactNode }>({ c: () => null, }) @@ -111,8 +119,15 @@ const LoadRemixAsyncComponent: FC<{ if (!module.Component) { return } + + const { loader } = module setComponent({ - c: module.Component, + c: () => ( + <> + + + + ), }) }) .finally(() => { diff --git a/src/renderer/src/modules/settings/section.tsx b/src/renderer/src/modules/settings/section.tsx new file mode 100644 index 000000000..d0ee514c1 --- /dev/null +++ b/src/renderer/src/modules/settings/section.tsx @@ -0,0 +1,6 @@ +import type { FC } from "react" + +export const SettingSectionTitle: FC<{ + title: string +}> = ({ title }) => +
{title}
diff --git a/src/renderer/src/modules/settings/tabs/apperance.tsx b/src/renderer/src/modules/settings/tabs/apperance.tsx new file mode 100644 index 000000000..61dc899a9 --- /dev/null +++ b/src/renderer/src/modules/settings/tabs/apperance.tsx @@ -0,0 +1,124 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@renderer/components/ui/select" +import { useDark } from "@renderer/hooks" +import { tipcClient } from "@renderer/lib/client" +import { getOS } from "@renderer/lib/utils" +import { uiActions, useUIStore } from "@renderer/store" +import { useQuery } from "@tanstack/react-query" +import { useCallback } from "react" + +import { SettingSwitch } from "../control" +import { SettingSectionTitle } from "../section" +import { SettingsTitle } from "../title" + +export const SettingAppearance = () => { + const { isDark, toggleDark } = useDark() + const saveDarkSetting = useCallback(() => { + toggleDark() + }, []) + + const state = useUIStore() + + return ( +
+ + + + {window.electron && getOS() === "macOS" && ( + { + uiActions.set("opaqueSidebar", checked) + }} + /> + )} + + + {window.electron && } + +
+ ) +} + +const Fonts = () => { + const { data } = useQuery({ + queryFn: () => tipcClient?.getSystemFonts(), + queryKey: ["systemFonts"], + }) + const readerFontFamily = useUIStore( + (state) => state.readerFontFamily || "SN Pro", + ) + return ( +
+ Font Family + +
+ ) +} + +const textSizeMap = { + tiny: 12, + smaller: 14, + default: 16, + medium: 18, + large: 20, +} + +const TextSize = () => { + const uiTextSize = useUIStore((state) => state.uiTextSize) + + return ( +
+ Text Size + +
+ ) +} diff --git a/src/renderer/src/modules/settings/title.tsx b/src/renderer/src/modules/settings/title.tsx index c78a5ffbd..8f826c4a5 100644 --- a/src/renderer/src/modules/settings/title.tsx +++ b/src/renderer/src/modules/settings/title.tsx @@ -1,16 +1,17 @@ -import { settingTabs } from "@renderer/lib/constants" import { cn } from "@renderer/lib/utils" +import { useLoaderData } from "react-router-dom" -export const SettingsTitle = ({ +import { settings } from "./constants" +import type { SettingPageConfig } from "./utils" + +export const SettingsSidebarTitle = ({ path, className, - sticky, }: { path: string className?: string - sticky?: boolean }) => { - const tab = settingTabs.find((t) => t.path === path) + const tab = settings.find((t) => t.path === path) if (!tab) { return null @@ -19,13 +20,40 @@ export const SettingsTitle = ({ return (
- + {tab.name}
) } + +export const SettingsTitle = ({ + className, + loader, +}: { + className?: string + loader?: () => SettingPageConfig +}) => { + const { iconName, name: title } = (useLoaderData() || + loader?.() || + {}) as SettingPageConfig + + if (!title) { + return null + } + return ( +
+ + {title} +
+ ) +} diff --git a/src/renderer/src/modules/settings/utils.ts b/src/renderer/src/modules/settings/utils.ts new file mode 100644 index 000000000..a347c6332 --- /dev/null +++ b/src/renderer/src/modules/settings/utils.ts @@ -0,0 +1,9 @@ +export interface SettingPageConfig { + iconName: string + name: string + priority: number + +} +export const defineSettingPage = (config: SettingPageConfig) => () => ({ + ...config, +}) diff --git a/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx index 762b5d617..2332520d6 100644 --- a/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx +++ b/src/renderer/src/pages/(main)/(layer)/feeds/[feedId]/layout.tsx @@ -23,7 +23,7 @@ export function Component() { initial: entryColWidth, containerRef, onResizeEnd({ position }) { - uiActions.setEntryColWidth(position) + uiActions.set("entryColWidth", position) }, }) diff --git a/src/renderer/src/pages/settings/actions.tsx b/src/renderer/src/pages/settings/actions.tsx index 6069adbcb..f490ce18d 100644 --- a/src/renderer/src/pages/settings/actions.tsx +++ b/src/renderer/src/pages/settings/actions.tsx @@ -4,11 +4,22 @@ import { apiClient } from "@renderer/lib/api-fetch" import type { ActionsResponse } from "@renderer/models" import { ActionCard } from "@renderer/modules/settings/action-card" import { SettingsTitle } from "@renderer/modules/settings/title" +import { defineSettingPage } from "@renderer/modules/settings/utils" import { Queries } from "@renderer/queries" import { useMutation } from "@tanstack/react-query" import { useEffect, useState } from "react" import { toast } from "sonner" +const iconName = "i-mgc-magic-2-cute-re" +const name = "Actions" +const priority = 1020 + +export const loader = defineSettingPage({ + iconName, + name, + priority, +}) + type Operation = | "contains" | "not_contains" @@ -79,7 +90,7 @@ export function Component() { return ( <> - +
{actionsData.map((action, actionIdx) => ( +} diff --git a/src/renderer/src/pages/settings/general.tsx b/src/renderer/src/pages/settings/general.tsx new file mode 100644 index 000000000..4bb5d7d75 --- /dev/null +++ b/src/renderer/src/pages/settings/general.tsx @@ -0,0 +1,46 @@ +import { tipcClient } from "@renderer/lib/client" +import { SettingSwitch } from "@renderer/modules/settings/control" +import { + SettingsTitle, +} from "@renderer/modules/settings/title" +import { defineSettingPage } from "@renderer/modules/settings/utils" +import { useCallback, useEffect, useState } from "react" + +const iconName = "i-mgc-settings-7-cute-re" +const name = "General" +const priority = 1000 + +export const loader = defineSettingPage({ + iconName, + name, + priority, +}) + +export function Component() { + const [loginSetting, setLoginSetting] = useState(false) + useEffect(() => { + tipcClient?.getLoginItemSettings().then((settings) => { + setLoginSetting(settings.openAtLogin) + }) + }, []) + + const saveLoginSetting = useCallback((checked: boolean) => { + tipcClient?.setLoginItemSettings(checked) + setLoginSetting(checked) + }, []) + + return ( + <> + +
+ {window.electron && ( + + )} +
+ + ) +} diff --git a/src/renderer/src/pages/settings/index.tsx b/src/renderer/src/pages/settings/index.tsx index 9c8cd087e..be13cf66b 100644 --- a/src/renderer/src/pages/settings/index.tsx +++ b/src/renderer/src/pages/settings/index.tsx @@ -1,52 +1,8 @@ -import { Checkbox } from "@renderer/components/ui/checkbox" -import { Label } from "@renderer/components/ui/label" -import { useDark } from "@renderer/hooks" -import { tipcClient } from "@renderer/lib/client" -import { SettingsTitle } from "@renderer/modules/settings/title" -import { useCallback, useEffect, useState } from "react" +import { redirect } from "react-router-dom" -export function Component() { - const { isDark, toggleDark } = useDark() +export const Component = () => null - const [loginSetting, setLoginSetting] = useState(false) - useEffect(() => { - tipcClient?.getLoginItemSettings().then((settings) => { - setLoginSetting(settings.openAtLogin) - }) - }, []) - - const saveLoginSetting = useCallback((checked: boolean) => { - tipcClient?.setLoginItemSettings(checked) - setLoginSetting(checked) - }, []) - - const saveDarkSetting = useCallback(() => { - toggleDark() - }, []) - - return ( - <> - -
- {window.electron && ( -
- - -
- )} -
- - -
-
- - ) +export const loader = () => { + redirect("/settings/general") + return null } diff --git a/src/renderer/src/pages/settings/layout.tsx b/src/renderer/src/pages/settings/layout.tsx index 0c75625ea..e8c3dcb24 100644 --- a/src/renderer/src/pages/settings/layout.tsx +++ b/src/renderer/src/pages/settings/layout.tsx @@ -1,8 +1,11 @@ import { Logo } from "@renderer/components/icons/logo" import { Vibrancy } from "@renderer/components/ui/background" -import { APP_NAME, settingTabs } from "@renderer/lib/constants" +import { APP_NAME } from "@renderer/lib/constants" import { preventDefault } from "@renderer/lib/dom" -import { SettingsTitle } from "@renderer/modules/settings/title" +import { settings } from "@renderer/modules/settings/constants" +import { + SettingsSidebarTitle, +} from "@renderer/modules/settings/title" import { Link, Outlet, useLocation } from "react-router-dom" // TODO Web UI @@ -15,32 +18,27 @@ export function Component() {
- {settingTabs.map((t) => ( + {settings.map((t) => ( - + ))}
- - {APP_NAME} - + {APP_NAME}
-
+
diff --git a/src/renderer/src/pages/settings/profile.tsx b/src/renderer/src/pages/settings/profile.tsx index 17947151e..e9b4fa603 100644 --- a/src/renderer/src/pages/settings/profile.tsx +++ b/src/renderer/src/pages/settings/profile.tsx @@ -13,6 +13,7 @@ import { import { Input } from "@renderer/components/ui/input" import { apiClient } from "@renderer/lib/api-fetch" import { SettingsTitle } from "@renderer/modules/settings/title" +import { defineSettingPage } from "@renderer/modules/settings/utils" import { useMutation } from "@tanstack/react-query" import { useForm } from "react-hook-form" import { toast } from "sonner" @@ -24,6 +25,15 @@ const formSchema = z.object({ avatar: z.string().url(), }) +const iconName = "i-mgc-user-setting-cute-re" +const name = "Profile" +const priority = 1030 +export const loader = defineSettingPage({ + iconName, + name, + priority, +}) + export function Component() { const user = useUser() @@ -54,7 +64,7 @@ export function Component() { return ( <> - +
- +
{Object.keys(shortcuts).map((type) => (
-
{type}
-
+
{type}
+
{Object.keys(shortcuts[type]).map((action, index) => ( -
+
{shortcuts[type][action].name}
- {shortcuts[type][action].key.split( - ",", - ).map((keys) => ( - {keys} + {shortcuts[type][action].key.split(",").map((keys) => ( + + {keys} + ))}
diff --git a/src/renderer/src/providers/root-providers.tsx b/src/renderer/src/providers/root-providers.tsx index 4721571e7..35df1690c 100644 --- a/src/renderer/src/providers/root-providers.tsx +++ b/src/renderer/src/providers/root-providers.tsx @@ -11,6 +11,7 @@ import { HelmetProvider } from "react-helmet-async" import { BizRouterProvider } from "./biz-router-provider" import { ContextMenuProvider } from "./context-menu-provider" +import { UISettingInitialize } from "./ui-setting-Initialize" import { UserProvider } from "./user-provider" const loadFeatures = () => @@ -31,6 +32,7 @@ export const RootProviders: FC = ({ children }) => ( + {children} diff --git a/src/renderer/src/providers/ui-setting-Initialize.tsx b/src/renderer/src/providers/ui-setting-Initialize.tsx new file mode 100644 index 000000000..711aa0922 --- /dev/null +++ b/src/renderer/src/providers/ui-setting-Initialize.tsx @@ -0,0 +1,12 @@ +import { useUIStore } from "@renderer/store" +import { useInsertionEffect } from "react" + +export const UISettingInitialize = () => { + const state = useUIStore() + + useInsertionEffect(() => { + const root = document.documentElement + root.style.fontSize = `${state.uiTextSize}px` + }, [state.uiTextSize]) + return null +} diff --git a/src/renderer/src/store/ui.ts b/src/renderer/src/store/ui.ts index 926df750d..dce929ce3 100644 --- a/src/renderer/src/store/ui.ts +++ b/src/renderer/src/store/ui.ts @@ -2,15 +2,21 @@ import { createZustandStore, getStoreActions } from "./utils/helper" interface UIState { entryColWidth: number + + opaqueSidebar: boolean + readerFontFamily: string + uiTextSize: number } const createDefaultUIState = (): UIState => ({ entryColWidth: 340, + opaqueSidebar: false, + readerFontFamily: "SN Pro", + uiTextSize: 16, }) interface UIActions { - setEntryColWidth: (width: number) => void - clear: () => void + set: (key: T, value: UIState[T]) => void } export const useUIStore = createZustandStore("ui", { version: 1, @@ -20,9 +26,8 @@ export const useUIStore = createZustandStore("ui", { clear() { set(createDefaultUIState()) }, - - setEntryColWidth(width) { - set({ entryColWidth: width }) + set(key, value) { + set({ [key]: value }) }, })) diff --git a/src/renderer/src/styles/tailwind-extend.css b/src/renderer/src/styles/tailwind-extend.css index b7de8aa99..0d130486b 100644 --- a/src/renderer/src/styles/tailwind-extend.css +++ b/src/renderer/src/styles/tailwind-extend.css @@ -159,7 +159,7 @@ appearance: none; border-radius: 5px; border-width: 1px; - border-color: theme(colors.zinc.500); + border-color: theme(colors.theme.inactive); --tw-border-opacity: 0.2; } diff --git a/src/shared/src/bridge.ts b/src/shared/src/bridge.ts new file mode 100644 index 000000000..3fdfaf07b --- /dev/null +++ b/src/shared/src/bridge.ts @@ -0,0 +1,14 @@ +import type { BrowserWindow } from "electron" + +const PREFIX = "__follow" +interface RenderGlobalContext { + showSetting: () => void +} + +export const registerGlobalContext = (context: RenderGlobalContext) => { + globalThis[PREFIX] = context +} +export const callGlobalContextMethod = ( + window: BrowserWindow, + method: keyof RenderGlobalContext, +) => window.webContents.executeJavaScript(`globalThis.${PREFIX}.${method}()`)