From 3547b6981028b30ba565efbd0fe78b0eea63f998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E7=86=B1?= Date: Sat, 17 May 2025 14:43:10 +0800 Subject: [PATCH] perf: (design): enhance menu change handling and improve fake toolbar rendering (#5219) --- examples/src/sheets/lazy.ts | 2 + examples/src/sheets/main.ts | 2 - .../src/views/uni-toolbar/UniToolbar.tsx | 8 +- .../src/services/menu/menu-manager.service.ts | 8 +- .../ui/src/views/components/ribbon/Ribbon.tsx | 166 ++++++++++-------- 5 files changed, 103 insertions(+), 83 deletions(-) diff --git a/examples/src/sheets/lazy.ts b/examples/src/sheets/lazy.ts index cb19aa0fc4..2c843fb642 100644 --- a/examples/src/sheets/lazy.ts +++ b/examples/src/sheets/lazy.ts @@ -22,6 +22,7 @@ import { UniverSheetsDataValidationUIPlugin } from '@univerjs/sheets-data-valida import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui'; import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui'; import { UniverSheetsFormulaUIPlugin } from '@univerjs/sheets-formula-ui'; +import { UniverSheetsNoteUIPlugin } from '@univerjs/sheets-note-ui'; import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui'; import { UniverSheetsTableUIPlugin } from '@univerjs/sheets-table-ui'; import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui'; @@ -34,6 +35,7 @@ export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCt [UniverSheetsNumfmtUIPlugin], [UniverThreadCommentUIPlugin], [UniverSheetsThreadCommentUIPlugin], + [UniverSheetsNoteUIPlugin], [UniverSheetsTableUIPlugin], [UniverSheetsFormulaUIPlugin], [UniverSheetsDataValidationUIPlugin], diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index da69052e6e..065e9f26d4 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -31,7 +31,6 @@ import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'; import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'; -import { UniverSheetsNoteUIPlugin } from '@univerjs/sheets-note-ui'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'; import { UniverSheetsTablePlugin } from '@univerjs/sheets-table'; @@ -132,7 +131,6 @@ function createNewInstance() { univer.registerPlugin(UniverSheetsTablePlugin); univer.registerPlugin(UniverNetworkPlugin); univer.registerPlugin(UniverSheetsNotePlugin); - univer.registerPlugin(UniverSheetsNoteUIPlugin); univer.registerPlugin(ImportCSVButtonPlugin); // If we are running in e2e platform, we should immediately register the debugger plugin. diff --git a/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx b/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx index 3d6ac56684..439de97969 100644 --- a/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx +++ b/packages-experimental/uniui/src/views/uni-toolbar/UniToolbar.tsx @@ -72,13 +72,13 @@ export function UniToolbar() { return (
{hasWorkbooks && } -
+
{uniVisibleItems.map((subItem) => subItem && )}
diff --git a/packages/ui/src/services/menu/menu-manager.service.ts b/packages/ui/src/services/menu/menu-manager.service.ts index ac4f9b44a7..a579916014 100644 --- a/packages/ui/src/services/menu/menu-manager.service.ts +++ b/packages/ui/src/services/menu/menu-manager.service.ts @@ -34,7 +34,7 @@ export interface IMenuSchema { } export interface IMenuManagerService { - readonly menuChanged$: Observable; + readonly menuChanged$: Observable; mergeMenu(source: MenuSchemaType, target?: MenuSchemaType): void; @@ -54,7 +54,7 @@ export type MenuSchemaType = { }; export class MenuManagerService extends Disposable implements IMenuManagerService { - readonly menuChanged$ = new Subject(); + readonly menuChanged$ = new Subject(); private _menu: MenuSchemaType = { [MenuManagerPosition.RIBBON]: { @@ -243,7 +243,7 @@ export class MenuManagerService extends Disposable implements IMenuManagerServic const _key = key as keyof MenuSchemaType; _target[_key] = merge({}, _target[_key], source[_key]); - this.menuChanged$.next(Date.now()); + this.menuChanged$.next(); } else if (typeof value === 'object') { this.mergeMenu(source, value); } @@ -252,7 +252,7 @@ export class MenuManagerService extends Disposable implements IMenuManagerServic appendRootMenu(source: MenuSchemaType): void { this._menu = merge({}, this._menu, source); - this.menuChanged$.next(Date.now()); + this.menuChanged$.next(); } private _buildMenuSchema(data: MenuSchemaType): IMenuSchema[] { diff --git a/packages/ui/src/views/components/ribbon/Ribbon.tsx b/packages/ui/src/views/components/ribbon/Ribbon.tsx index eb82d10829..e0879500c7 100644 --- a/packages/ui/src/views/components/ribbon/Ribbon.tsx +++ b/packages/ui/src/views/components/ribbon/Ribbon.tsx @@ -17,14 +17,14 @@ import type { ComponentType } from 'react'; import type { Observable } from 'rxjs'; import type { IMenuSchema } from '../../../services/menu/menu-manager.service'; -import { LocaleService } from '@univerjs/core'; +import { LocaleService, throttle } from '@univerjs/core'; import { borderBottomClassName, borderClassName, clsx, divideXClassName, Dropdown, HoverCard } from '@univerjs/design'; import { DatabaseSingle, EyeSingle, FunctionSingle, HomeSingle, InsertSingle, MoreDownSingle, MoreFunctionSingle } from '@univerjs/icons'; import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { combineLatest } from 'rxjs'; import { IMenuManagerService } from '../../../services/menu/menu-manager.service'; import { MenuManagerPosition, RibbonPosition } from '../../../services/menu/types'; -import { useDependency, useObservable } from '../../../utils/di'; +import { useDependency } from '../../../utils/di'; import { ComponentContainer } from '../ComponentContainer'; import { ToolbarButton } from './ToolbarButton'; import { ToolbarItem } from './ToolbarItem'; @@ -48,11 +48,19 @@ export function Ribbon(props: IRibbonProps) { const menuManagerService = useDependency(IMenuManagerService); const localeService = useDependency(LocaleService); + const [menuChangedTimes, setMenuChangedTimes] = useState(0); - const menuChanged = useObservable(menuManagerService.menuChanged$); + useEffect(() => { + const subscription = menuManagerService.menuChanged$.subscribe(() => { + setMenuChangedTimes((prev) => prev + 1); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); const containerRef = useRef(null!); - const fakeToolbarRef = useRef(null!); const toolbarItemRefs = useRef(RibbonPosition.START); const [groupSelectorVisible, setGroupSelectorVisible] = useState(false); const [collapsedIds, setCollapsedIds] = useState([]); + const [fakeToolbarVisible, setFakeToolbarVisible] = useState(false); // const [changingActiveTab, setChangingActiveTab] = useState(false); const handleSelectTab = useCallback((group: IMenuSchema) => { @@ -80,10 +89,11 @@ export function Ribbon(props: IRibbonProps) { }; }, []); - // subscribe to menu changes + // process menu changes useEffect(() => { const ribbon = menuManagerService.getMenuByPositionKey(MenuManagerPosition.RIBBON); + // Collect all hidden$ Observables and their corresponding paths const hiddenObservaleMap: Observable[] = []; const hiddenKeyMap: string[] = []; for (const group of ribbon) { @@ -101,8 +111,8 @@ export function Ribbon(props: IRibbonProps) { } } - const subscription = combineLatest(hiddenObservaleMap) - // .pipe(debounceTime(300)) + // Only get the current value once, not continuously subscribe + combineLatest(hiddenObservaleMap) .subscribe((hiddenMap) => { const newRibbon: IMenuSchema[] = []; @@ -147,12 +157,9 @@ export function Ribbon(props: IRibbonProps) { } setRibbon(newRibbon); - }); - - return () => { - subscription.unsubscribe(); - }; - }, [menuChanged]); + }) + .unsubscribe(); + }, [menuChangedTimes]); const activeGroup = useMemo(() => { const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? []; @@ -186,37 +193,43 @@ export function Ribbon(props: IRibbonProps) { }, [collapsedIds, ribbon, activatedTab]); useEffect(() => { - const observer = new ResizeObserver((entries) => { + const observer = new ResizeObserver(throttle((entries) => { for (const entry of entries) { - const { width: avaliableWidth } = entry.contentRect; - const toolbarItems = Object.values(toolbarItemRefs.current); - const sortedToolbarItems = toolbarItems.sort((a, b) => { - if (a.groupOrder === b.groupOrder) { - return a.order - b.order; + setFakeToolbarVisible(true); + + requestAnimationFrame(() => { + const { width: avaliableWidth } = entry.contentRect; + const toolbarItems = Object.values(toolbarItemRefs.current); + const sortedToolbarItems = toolbarItems.sort((a, b) => { + if (a.groupOrder === b.groupOrder) { + return a.order - b.order; + } + return a.groupOrder - b.groupOrder; + }); + + const newCollapsedIds: string[] = []; + let totalWidth = 32; + const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? []; + + const gapWidth = (allGroups.length - 1) * 8; + totalWidth += gapWidth; + + for (const { el, key } of sortedToolbarItems) { + if (!el) continue; + + totalWidth += el?.getBoundingClientRect().width + 8; + + if (totalWidth > avaliableWidth - gapWidth) { + newCollapsedIds.push(key); + } } - return a.groupOrder - b.groupOrder; + + setCollapsedIds(newCollapsedIds); + + setFakeToolbarVisible(false); }); - - const newCollapsedIds: string[] = []; - let totalWidth = 32; - const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? []; - - const gapWidth = (allGroups.length - 1) * 8; - totalWidth += gapWidth; - - for (const { el, key } of sortedToolbarItems) { - if (!el) continue; - - totalWidth += el?.getBoundingClientRect().width + 8; - - if (totalWidth > avaliableWidth - gapWidth) { - newCollapsedIds.push(key); - } - } - - setCollapsedIds(newCollapsedIds); } - }); + }, 300)); observer.observe(containerRef.current); @@ -225,6 +238,45 @@ export function Ribbon(props: IRibbonProps) { }; }, [ribbon, activatedTab]); + const fakeToolbar = useMemo(() => { + if (!fakeToolbarVisible) return null; + + return ( + + ); + }, [activeGroup.allGroups, fakeToolbarVisible]); + return ( <> {headerMenu && (headerMenuComponents && headerMenuComponents.size > 0) && ( @@ -377,39 +429,7 @@ export function Ribbon(props: IRibbonProps) {
{/* fake toolbar */} -
- {activeGroup.allGroups.map((groupItem) => (groupItem.children?.length || groupItem.item) && ( - -
- {groupItem.children && groupItem.children?.map((child) => ( - child.item && ( - { - if (ref?.el) { - toolbarItemRefs.current[child.key] = { - el: ref.el, - key: child.key, - groupOrder: groupItem.order, - order: child.order, - }; - } - }} - /> - ) - ))} -
-
- ))} -
+ {fakeToolbar} ); }