diff --git a/packages/frontend/core/src/layouts/styles.css.ts b/packages/frontend/core/src/layouts/styles.css.ts index 519f292b9d09e..64a6e16cdc67d 100644 --- a/packages/frontend/core/src/layouts/styles.css.ts +++ b/packages/frontend/core/src/layouts/styles.css.ts @@ -31,15 +31,3 @@ export const desktopTabsHeader = style({ width: '100%', overflow: 'hidden', }); - -export const desktopTabsHeaderTopLeft = style({ - display: 'flex', - flexFlow: 'row', - alignItems: 'center', - transition: 'width 0.3s, padding 0.3s', - justifyContent: 'space-between', - marginRight: -8, // make room for tab's padding - padding: '0 16px', - flexShrink: 0, - ['WebkitAppRegion' as string]: 'drag', -}); diff --git a/packages/frontend/core/src/layouts/workspace-layout.tsx b/packages/frontend/core/src/layouts/workspace-layout.tsx index 86f097292d762..085a251110302 100644 --- a/packages/frontend/core/src/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/layouts/workspace-layout.tsx @@ -35,12 +35,9 @@ import { WorkspaceAIOnboarding } from '../components/affine/ai-onboarding'; import { AppContainer } from '../components/affine/app-container'; import { SyncAwareness } from '../components/affine/awareness'; import { - appSidebarFloatingAtom, - appSidebarOpenAtom, appSidebarResizingAtom, SidebarSwitch, } from '../components/app-sidebar'; -import { appSidebarWidthAtom } from '../components/app-sidebar/index.jotai'; import { AIIsland } from '../components/pure/ai-island'; import { RootAppSidebar } from '../components/root-app-sidebar'; import { MainContainer } from '../components/workspace'; @@ -181,29 +178,17 @@ const WorkspaceLayoutProviders = ({ children }: PropsWithChildren) => { }; const DesktopLayout = ({ children }: PropsWithChildren) => { - const resizing = useAtomValue(appSidebarResizingAtom); - const sidebarWidth = useAtomValue(appSidebarWidthAtom); - const sidebarOpen = useAtomValue(appSidebarOpenAtom); - const sidebarFloating = useAtomValue(appSidebarFloatingAtom); - const sidebarResizing = useAtomValue(appSidebarResizingAtom); - const isMacosDesktop = environment.isDesktop && environment.isMacOs; - return (
-
- - -
- + + + + + } + />
diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx index fda509f0451a4..983d1a4a73a3e 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx +++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx @@ -1,9 +1,15 @@ -import { IconButton, Loading, observeResize } from '@affine/component'; +import { IconButton, Loading } from '@affine/component'; +import { + appSidebarFloatingAtom, + appSidebarOpenAtom, + appSidebarResizingAtom, +} from '@affine/core/components/app-sidebar'; +import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.jotai'; import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { DesktopStateSynchronizer } from '@affine/core/modules/workbench/services/desktop-state-synchronizer'; import type { WorkbenchMeta } from '@affine/electron-api'; -import { apis } from '@affine/electron-api'; +import { apis, events } from '@affine/electron-api'; import { CloseIcon, DeleteIcon, @@ -21,13 +27,15 @@ import { useService, useServiceOptional, } from '@toeverything/infra'; -import { debounce, partition } from 'lodash-es'; +import clsx from 'clsx'; +import { useAtomValue } from 'jotai'; +import { partition } from 'lodash-es'; import { Fragment, type MouseEventHandler, type ReactNode, useEffect, - useRef, + useState, } from 'react'; import { @@ -140,13 +148,39 @@ const WorkbenchTab = ({ ); }; +const useIsFullScreen = () => { + const [fullScreen, setFullScreen] = useState(false); + + useEffect(() => { + apis?.ui + .isFullScreen() + .then(setFullScreen) + .then(() => { + events?.ui.onFullScreen(setFullScreen); + }) + .catch(console.error); + }, []); + return fullScreen; +}; + export const AppTabsHeader = ({ style, - reportBoundingUpdate, + mode = 'app', + className, + left, }: { style?: React.CSSProperties; - reportBoundingUpdate?: boolean; + mode?: 'app' | 'shell'; + className?: string; + left?: ReactNode; }) => { + const sidebarWidth = useAtomValue(appSidebarWidthAtom); + const sidebarOpen = useAtomValue(appSidebarOpenAtom); + const sidebarFloating = useAtomValue(appSidebarFloatingAtom); + const sidebarResizing = useAtomValue(appSidebarResizingAtom); + const isMacosDesktop = environment.isDesktop && environment.isMacOs; + const fullScreen = useIsFullScreen(); + const tabsHeaderService = useService(AppTabsHeaderService); const tabs = useLiveData(tabsHeaderService.tabsStatus$); @@ -160,42 +194,36 @@ export const AppTabsHeader = ({ await tabsHeaderService.onToggleRightSidebar(); }, [tabsHeaderService]); - const ref = useRef(null); - useServiceOptional(DesktopStateSynchronizer); useEffect(() => { - if (ref.current && reportBoundingUpdate) { - return observeResize( - ref.current, - debounce(() => { - if (document.visibilityState === 'visible') { - const rect = ref.current?.getBoundingClientRect(); - if (!rect) { - return; - } - const toInt = (value: number) => Math.round(value); - const boundRect = { - height: toInt(rect.height), - width: toInt(rect.width), - x: toInt(rect.x), - y: toInt(rect.y), - }; - apis?.ui.updateTabsBoundingRect(boundRect).catch(console.error); - } - }, 50) - ); + if (mode === 'app') { + apis?.ui.pingAppLayoutReady().catch(console.error); } - return; - }, [reportBoundingUpdate]); + }, [mode]); return (
+
+ {left} +
{pinned.map(tab => { return ( diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts b/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts index f359f00a257eb..eb9f0a1bed070 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts +++ b/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts @@ -18,6 +18,20 @@ export const root = style({ }, }); +export const headerLeft = style({ + display: 'flex', + flexFlow: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: '0 16px', + flexShrink: 0, + selectors: { + [`${root}[data-mode="app"] &`]: { + transition: 'width 0.3s, padding 0.3s', + }, + }, +}); + export const tabs = style({ display: 'flex', flexDirection: 'row', diff --git a/packages/frontend/core/src/pages/404.tsx b/packages/frontend/core/src/pages/404.tsx index 9cdb546137c88..28bb0a1b2101f 100644 --- a/packages/frontend/core/src/pages/404.tsx +++ b/packages/frontend/core/src/pages/404.tsx @@ -44,7 +44,6 @@ export const PageNotFound = ({ style={{ paddingLeft: environment.isMacOs ? 80 : 0, }} - reportBoundingUpdate /> ) : null} {noPermission ? ( diff --git a/packages/frontend/core/src/pages/index.tsx b/packages/frontend/core/src/pages/index.tsx index 7c239db482552..3f9c1b5d0c99a 100644 --- a/packages/frontend/core/src/pages/index.tsx +++ b/packages/frontend/core/src/pages/index.tsx @@ -153,7 +153,6 @@ export const Component = () => { style={{ paddingLeft: environment.isMacOs ? 80 : 0, }} - reportBoundingUpdate /> ) : null}
console.error('failed to load app config')); + const handleMaximized = (maximized: boolean | undefined) => { + document.documentElement.dataset.maximized = String(maximized); + }; + const handleFullscreen = (fullscreen: boolean | undefined) => { + document.documentElement.dataset.fullscreen = String(fullscreen); + }; + + apis?.ui.isMaximized().then(handleMaximized).catch(console.error); + apis?.ui.isFullScreen().then(handleFullscreen).catch(console.error); + events?.ui.onMaximized(handleMaximized); + events?.ui.onFullScreen(handleFullscreen); } function mountApp() { diff --git a/packages/frontend/electron/renderer/shell/shell.css.ts b/packages/frontend/electron/renderer/shell/shell.css.ts index 3e92d4b8f149a..6e1286f5657a8 100644 --- a/packages/frontend/electron/renderer/shell/shell.css.ts +++ b/packages/frontend/electron/renderer/shell/shell.css.ts @@ -1,10 +1,13 @@ import { cssVar } from '@toeverything/theme'; -import { globalStyle, style } from '@vanilla-extract/css'; +import { createVar, globalStyle, style } from '@vanilla-extract/css'; + +export const sidebarOffsetVar = createVar(); export const root = style({ width: '100vw', height: '100vh', opacity: 1, + display: 'flex', transition: 'opacity 0.1s', background: cssVar('backgroundPrimaryColor'), selectors: { diff --git a/packages/frontend/electron/renderer/shell/shell.tsx b/packages/frontend/electron/renderer/shell/shell.tsx index 27273cbbab93a..324294d949923 100644 --- a/packages/frontend/electron/renderer/shell/shell.tsx +++ b/packages/frontend/electron/renderer/shell/shell.tsx @@ -1,12 +1,12 @@ import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; import { AppTabsHeader } from '@affine/core/modules/app-tabs-header'; -import { apis, events } from '@affine/electron-api'; +import { events } from '@affine/electron-api'; import { useEffect, useState } from 'react'; import * as styles from './shell.css'; const useIsShellActive = () => { - const [active, setActive] = useState(true); + const [active, setActive] = useState(false); useEffect(() => { const unsub = events?.ui.onTabShellViewActiveChange(active => { @@ -20,48 +20,9 @@ const useIsShellActive = () => { return active; }; -const useTabsBoundingRect = () => { - const [rect, setRect] = useState<{ - x: number; - y: number; - width: number; - height: number; - }>({ - x: environment.isDesktop && environment.isMacOs ? 80 : 0, - y: 0, - width: window.innerWidth, - height: 52, - }); - - useEffect(() => { - let unsub: (() => void) | undefined; - apis?.ui - .getTabsBoundingRect() - .then(rect => { - if (rect) { - setRect(rect); - } - unsub = events?.ui.onTabsBoundingRectChanged(rect => { - if (rect) { - setRect(rect); - } - }); - }) - .catch(err => { - console.error(err); - }); - return () => { - unsub?.(); - }; - }, []); - - return rect; -}; - export function ShellRoot() { const active = useIsShellActive(); const { appSettings } = useAppSettingHelper(); - const rect = useTabsBoundingRect(); const translucent = environment.isDesktop && environment.isMacOs && @@ -72,15 +33,7 @@ export function ShellRoot() { data-translucent={translucent} data-active={active} > - +
); } diff --git a/packages/frontend/electron/src/main/ui/events.ts b/packages/frontend/electron/src/main/ui/events.ts index 712de3fd1656f..b8e213ca81dc3 100644 --- a/packages/frontend/electron/src/main/ui/events.ts +++ b/packages/frontend/electron/src/main/ui/events.ts @@ -2,7 +2,6 @@ import type { MainEventRegister } from '../type'; import { onActiveTabChanged, onTabAction, - onTabsBoundingRectChanged, onTabShellViewActiveChange, onTabsStatusChange, onTabViewsMetaChanged, @@ -36,5 +35,4 @@ export const uiEvents = { onTabsStatusChange, onActiveTabChanged, onTabShellViewActiveChange, - onTabsBoundingRectChanged, } satisfies Record; diff --git a/packages/frontend/electron/src/main/ui/handlers.ts b/packages/frontend/electron/src/main/ui/handlers.ts index 0d7ba8281ebc2..996ce0939fbb2 100644 --- a/packages/frontend/electron/src/main/ui/handlers.ts +++ b/packages/frontend/electron/src/main/ui/handlers.ts @@ -11,7 +11,6 @@ import { closeTab, getMainWindow, getOnboardingWindow, - getTabsBoundingRect, getTabsStatus, getTabViewsMeta, getWorkbenchMeta, @@ -19,10 +18,10 @@ import { initAndShowMainWindow, isActiveTab, launchStage, + pingAppLayoutReady, showDevTools, showTab, showTabContextMenu, - updateTabsBoundingRect, updateWorkbenchMeta, } from '../windows-manager'; import { getChallengeResponse } from './challenge'; @@ -193,14 +192,8 @@ export const uiHandlers = { uiSubjects.onToggleRightSidebar$.next(tabId); } }, - getTabsBoundingRect: async () => { - return getTabsBoundingRect(); - }, - updateTabsBoundingRect: async ( - e, - rect: { x: number; y: number; width: number; height: number } - ) => { - return updateTabsBoundingRect(e.sender, rect); + pingAppLayoutReady: async e => { + pingAppLayoutReady(e.sender); }, showDevTools: async (_, ...args: Parameters) => { return showDevTools(...args); diff --git a/packages/frontend/electron/src/main/windows-manager/tab-views.ts b/packages/frontend/electron/src/main/windows-manager/tab-views.ts index 8ebcfdb555398..01711e415b377 100644 --- a/packages/frontend/electron/src/main/windows-manager/tab-views.ts +++ b/packages/frontend/electron/src/main/windows-manager/tab-views.ts @@ -5,7 +5,6 @@ import { type CookiesSetDetails, globalShortcut, Menu, - type Rectangle, type View, type WebContents, WebContentsView, @@ -130,7 +129,6 @@ export class WebContentViewsManager { } readonly tabViewsMeta$ = TabViewsMetaState.$; - readonly tabsBoundingRect$ = new BehaviorSubject(null); readonly appTabsUIReady$ = new BehaviorSubject(new Set()); // all web views @@ -202,14 +200,6 @@ export class WebContentViewsManager { TabViewsMetaState.patch(patch); }; - get tabsBoundingRect() { - return this.tabsBoundingRect$.value; - } - - set tabsBoundingRect(rect: Rectangle | null) { - this.tabsBoundingRect$.next(rect); - } - get shellView() { return this.webViewsMap$.value.get('shell'); } @@ -584,14 +574,6 @@ export class WebContentViewsManager { }) ); - disposables.push( - this.tabsBoundingRect$.subscribe(rect => { - if (rect) { - this.reorderViews(); - } - }) - ); - app.on('ready', () => { // bind CMD/CTRL+1~8 to switch tabs // bind CMD/CTRL+9 to switch to the last tab @@ -745,6 +727,11 @@ export class WebContentViewsManager { }); this.resizeView(view); + + view.webContents.on('did-finish-load', () => { + this.resizeView(view); + }); + // reorder will add to main window when loaded this.reorderViews(); @@ -887,32 +874,12 @@ export const showDevTools = (id?: string) => { } }; -export const onTabsBoundingRectChanged = ( - fn: (rect: Rectangle | null) => void -) => { - const sub = WebContentViewsManager.instance.tabsBoundingRect$.subscribe(fn); - return () => { - sub.unsubscribe(); - }; -}; - -export const getTabsBoundingRect = () => { - return WebContentViewsManager.instance.tabsBoundingRect; -}; - -export const updateTabsBoundingRect = (wc: WebContents, rect: Rectangle) => { - try { - if (isActiveTab(wc)) { - WebContentViewsManager.instance.tabsBoundingRect = rect; - } - const viewId = WebContentViewsManager.instance.getViewIdFromWebContentsId( - wc.id - ); - if (viewId) { - WebContentViewsManager.instance.setTabUIReady(viewId); - } - } catch (err) { - logger.error(err); +export const pingAppLayoutReady = (wc: WebContents) => { + const viewId = WebContentViewsManager.instance.getViewIdFromWebContentsId( + wc.id + ); + if (viewId) { + WebContentViewsManager.instance.setTabUIReady(viewId); } };