diff --git a/apps/main/src/tipc/app.ts b/apps/main/src/tipc/app.ts index c67f3a32b6..991f5cf586 100644 --- a/apps/main/src/tipc/app.ts +++ b/apps/main/src/tipc/app.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url" import { getRendererHandlers } from "@egoist/tipc/main" import { callWindowExpose } from "@follow/shared/bridge" -import { app, BrowserWindow, clipboard, dialog, screen, shell } from "electron" +import { app, BrowserWindow, clipboard, dialog, shell } from "electron" import { registerMenuAndContextMenu } from "~/init" import { clearAllData, getCacheSize } from "~/lib/cleaner" @@ -15,7 +15,7 @@ import { registerAppTray } from "~/lib/tray" import { logger, revealLogFile } from "~/logger" import { cleanupOldRender, loadDynamicRenderEntry } from "~/updater/hot-updater" -import { isDev, isWindows11 } from "../env" +import { isDev } from "../env" import { downloadFile } from "../lib/download" import { i18n } from "../lib/i18n" import { cleanBetterAuthSessionCookie, cleanUser } from "../lib/user" @@ -144,25 +144,7 @@ export const appRoute = { } } }), - getWindowIsMaximized: t.procedure.input().action(async ({ context }) => { - const window: BrowserWindow | null = (context.sender as Sender).getOwnerBrowserWindow() - if (isWindows11 && window) { - const size = screen.getDisplayMatching(window.getBounds()).workAreaSize - - const windowSize = window.getSize() - const windowPosition = window.getPosition() - const isMaximized = - windowSize[0] === size.width && - windowSize[1] === size.height && - windowPosition[0] === 0 && - windowPosition[1] === 0 - - return !!isMaximized - } - - return window?.isMaximized() - }), quitAndInstall: t.procedure.action(async () => { quitAndInstall() }), diff --git a/apps/main/src/window.ts b/apps/main/src/window.ts index 573b96aba0..aa6b15eb73 100644 --- a/apps/main/src/window.ts +++ b/apps/main/src/window.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url" import { is } from "@electron-toolkit/utils" import { APP_PROTOCOL } from "@follow/shared" -import { callWindowExpose } from "@follow/shared/bridge" +import { callWindowExpose, WindowState } from "@follow/shared/bridge" import type { BrowserWindowConstructorOptions } from "electron" import { app, BrowserWindow, screen, shell } from "electron" import type { Event } from "electron/main" @@ -197,6 +197,27 @@ export function createWindow( }) } + // async render and main state + window.on("maximize", async () => { + const caller = callWindowExpose(window) + await caller.setWindowState(WindowState.MAXIMIZED) + }) + + window.on("unmaximize", async () => { + const caller = callWindowExpose(window) + await caller.setWindowState(WindowState.NORMAL) + }) + + window.on("minimize", async () => { + const caller = callWindowExpose(window) + await caller.setWindowState(WindowState.MINIMIZED) + }) + + window.on("restore", async () => { + const caller = callWindowExpose(window) + await caller.setWindowState(WindowState.NORMAL) + }) + return window } export const windowStateStoreKey = "windowState" diff --git a/apps/renderer/src/atoms/app.ts b/apps/renderer/src/atoms/app.ts index 21ac12efb7..4489a6c2e5 100644 --- a/apps/renderer/src/atoms/app.ts +++ b/apps/renderer/src/atoms/app.ts @@ -1,3 +1,4 @@ +import { WindowState } from "@follow/shared/bridge" import { atom } from "jotai" import { createAtomHooks } from "~/lib/jotai" @@ -5,3 +6,8 @@ import { createAtomHooks } from "~/lib/jotai" export const [, , useAppIsReady, , appIsReady, setAppIsReady] = createAtomHooks(atom(false)) export const [, , useAppSearchOpen, , , setAppSearchOpen] = createAtomHooks(atom(false)) + +// For electron +export const [, , useWindowState, , windowState, setWindowState] = createAtomHooks( + atom(WindowState.NORMAL), +) diff --git a/apps/renderer/src/modules/app/Titlebar.tsx b/apps/renderer/src/modules/app/Titlebar.tsx index 97b810d640..4515883a1c 100644 --- a/apps/renderer/src/modules/app/Titlebar.tsx +++ b/apps/renderer/src/modules/app/Titlebar.tsx @@ -1,14 +1,12 @@ -import { useQuery } from "@tanstack/react-query" +import { WindowState } from "@follow/shared/bridge" +import { useWindowState } from "~/atoms/app" import { useUISettingKey } from "~/atoms/settings/ui" import { ElECTRON_CUSTOM_TITLEBAR_HEIGHT } from "~/constants" import { tipcClient } from "~/lib/client" export const Titlebar = () => { - const { data: isMaximized, refetch } = useQuery({ - queryFn: () => tipcClient?.getWindowIsMaximized(), - queryKey: ["windowIsMaximized"], - }) + const isMaximized = useWindowState() === WindowState.MAXIMIZED const feedColWidth = useUISettingKey("feedColWidth") @@ -35,7 +33,6 @@ export const Titlebar = () => { className="no-drag-region pointer-events-auto flex h-full w-[50px] items-center justify-center duration-200 hover:bg-theme-item-active" onClick={async () => { await tipcClient?.windowAction({ action: "maximum" }) - refetch() }} > {isMaximized ? ( diff --git a/apps/renderer/src/providers/extension-expose-provider.tsx b/apps/renderer/src/providers/extension-expose-provider.tsx index 732299456a..83355fa395 100644 --- a/apps/renderer/src/providers/extension-expose-provider.tsx +++ b/apps/renderer/src/providers/extension-expose-provider.tsx @@ -4,6 +4,7 @@ import { useEffect, useLayoutEffect } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" +import { setWindowState } from "~/atoms/app" import { getGeneralSettings } from "~/atoms/settings/general" import { getUISettings, useToggleZenMode } from "~/atoms/settings/ui" import { setUpdaterStatus } from "~/atoms/updater" @@ -85,5 +86,15 @@ export const ExtensionExposeProvider = () => { dialog, }) }, [dialog]) + + useBindElectronBridge() return null } + +const useBindElectronBridge = () => { + useEffect(() => { + registerGlobalContext({ + setWindowState, + }) + }, []) +} diff --git a/apps/renderer/src/providers/inject-styles-provider.tsx b/apps/renderer/src/providers/inject-styles-provider.tsx new file mode 100644 index 0000000000..9fe524490f --- /dev/null +++ b/apps/renderer/src/providers/inject-styles-provider.tsx @@ -0,0 +1,38 @@ +import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.jsx" +import { RootPortal } from "@follow/components/ui/portal/index.js" +import type { FC, PropsWithChildren } from "react" +import { createContext, useCallback, useContext, useState } from "react" + +const Provider = createContext<(id: string, styles: string) => () => void>(() => () => {}) +export const PortalInjectStylesProvider: FC = ({ children }) => { + const [styles, setStyles] = useState({} as Record) + + const injectStyles = useCallback((id: string, styles: string) => { + const dispose = () => { + setStyles((prev) => { + const { [id]: _, ...rest } = prev + return rest + }) + } + if (styles[id]) return dispose + setStyles((prev) => ({ ...prev, [id]: styles })) + + return dispose + }, []) + return ( + + + {Object.entries(styles).map(([id, style]) => ( + + {style} + + ))} + + {children} + + ) +} + +export const useInjectStyles = () => { + return useContext(Provider) +} diff --git a/apps/renderer/src/providers/root-providers.tsx b/apps/renderer/src/providers/root-providers.tsx index eb4c60d672..e7bbf5c1bd 100644 --- a/apps/renderer/src/providers/root-providers.tsx +++ b/apps/renderer/src/providers/root-providers.tsx @@ -47,6 +47,7 @@ export const RootProviders: FC = ({ children }) => ( {import.meta.env.DEV && } + {children} diff --git a/apps/server/client/providers/root-providers.tsx b/apps/server/client/providers/root-providers.tsx index 0269d0d13d..0abd939925 100644 --- a/apps/server/client/providers/root-providers.tsx +++ b/apps/server/client/providers/root-providers.tsx @@ -20,7 +20,7 @@ export const RootProviders: FC = ({ children }) => ( - {/* */} + {children} diff --git a/packages/shared/src/bridge.ts b/packages/shared/src/bridge.ts index 66968bf40c..8a02b48762 100644 --- a/packages/shared/src/bridge.ts +++ b/packages/shared/src/bridge.ts @@ -17,6 +17,12 @@ declare const dialog: { cancelText?: string }) => Promise } + +export enum WindowState { + MINIMIZED = "minimized", + MAXIMIZED = "maximized", + NORMAL = "normal", +} interface RenderGlobalContext { /// Access Settings showSetting: (path?: string) => void @@ -46,6 +52,9 @@ interface RenderGlobalContext { /// Utils toast: typeof toast + /// Electron State + setWindowState: (state: WindowState) => void + readyToUpdate: () => void dialog: typeof dialog // URL