mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
feat(ui): add DesktopRibbonService and integrate with Ribbon componenta (#6428)
This commit is contained in:
@@ -113,6 +113,7 @@ export { INotificationService } from './services/notification/notification.servi
|
||||
export { BuiltInUIPart, IUIPartsService, UIPartsService } from './services/parts/parts.service';
|
||||
export { IPlatformService, PlatformService } from './services/platform/platform.service';
|
||||
export { CanvasPopupService, ICanvasPopupService, type IPopup } from './services/popup/canvas-popup.service';
|
||||
export { DesktopRibbonService, IRibbonService } from './services/ribbon/ribbon.service';
|
||||
export { KeyCode, MetaKeys } from './services/shortcut/keycode';
|
||||
export { ShortcutPanelService } from './services/shortcut/shortcut-panel.service';
|
||||
export { type IShortcutItem, IShortcutService, ShortcutService } from './services/shortcut/shortcut.service';
|
||||
|
||||
@@ -49,6 +49,7 @@ import { INotificationService } from './services/notification/notification.servi
|
||||
import { IUIPartsService, UIPartsService } from './services/parts/parts.service';
|
||||
import { IPlatformService, PlatformService } from './services/platform/platform.service';
|
||||
import { CanvasPopupService, ICanvasPopupService } from './services/popup/canvas-popup.service';
|
||||
import { DesktopRibbonService, IRibbonService } from './services/ribbon/ribbon.service';
|
||||
import { ShortcutPanelService } from './services/shortcut/shortcut-panel.service';
|
||||
import { IShortcutService, ShortcutService } from './services/shortcut/shortcut.service';
|
||||
import { DesktopSidebarService } from './services/sidebar/desktop-sidebar.service';
|
||||
@@ -102,6 +103,7 @@ export class UniverUIPlugin extends Plugin {
|
||||
[ShortcutPanelService],
|
||||
[IUIPartsService, { useClass: UIPartsService }],
|
||||
[ILayoutService, { useClass: DesktopLayoutService }],
|
||||
[IRibbonService, { useClass: DesktopRibbonService }],
|
||||
[IShortcutService, { useClass: ShortcutService }],
|
||||
[IPlatformService, { useClass: PlatformService }],
|
||||
[IMenuManagerService, { useClass: MenuManagerService }],
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { IMenuSchema } from '../menu/menu-manager.service';
|
||||
import { createIdentifier, Disposable, IUniverInstanceService } from '@univerjs/core';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import { IMenuManagerService } from '../menu/menu-manager.service';
|
||||
import { MenuManagerPosition, RibbonPosition } from '../menu/types';
|
||||
|
||||
export const IRibbonService = createIdentifier<IRibbonService>('univer.ribbon-service');
|
||||
|
||||
export interface IRibbonService {
|
||||
ribbon$: Observable<IMenuSchema[]>;
|
||||
activatedTab$: Observable<string>;
|
||||
collapsedIds$: Observable<string[]>;
|
||||
fakeToolbarVisible$: Observable<boolean>;
|
||||
|
||||
setActivatedTab(tab: string): void;
|
||||
setCollapsedIds(ids: string[]): void;
|
||||
setFakeToolbarVisible(visible: boolean): void;
|
||||
}
|
||||
|
||||
export class DesktopRibbonService extends Disposable implements IRibbonService {
|
||||
private readonly _ribbon$ = new BehaviorSubject<IMenuSchema[]>([]);
|
||||
readonly ribbon$ = this._ribbon$.asObservable();
|
||||
|
||||
private readonly _activatedTab$ = new BehaviorSubject<string>(RibbonPosition.START);
|
||||
readonly activatedTab$ = this._activatedTab$.asObservable();
|
||||
|
||||
private readonly _collapsedIds$ = new BehaviorSubject<string[]>([]);
|
||||
readonly collapsedIds$ = this._collapsedIds$.asObservable();
|
||||
|
||||
private readonly _fakeToolbarVisible$ = new BehaviorSubject<boolean>(false);
|
||||
readonly fakeToolbarVisible$ = this._fakeToolbarVisible$.asObservable();
|
||||
|
||||
constructor(
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
) {
|
||||
super();
|
||||
this._initRibbonSubscription();
|
||||
}
|
||||
|
||||
setActivatedTab(tab: string): void {
|
||||
this._activatedTab$.next(tab);
|
||||
}
|
||||
|
||||
setCollapsedIds(ids: string[]): void {
|
||||
this._collapsedIds$.next(ids);
|
||||
}
|
||||
|
||||
setFakeToolbarVisible(visible: boolean): void {
|
||||
this._fakeToolbarVisible$.next(visible);
|
||||
}
|
||||
|
||||
private _initRibbonSubscription(): void {
|
||||
this.disposeWithMe(
|
||||
combineLatest([
|
||||
this._menuManagerService.menuChanged$.pipe(startWith(undefined)),
|
||||
this._univerInstanceService.focused$.pipe(startWith(undefined)),
|
||||
]).subscribe(() => {
|
||||
this._updateRibbon();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _updateRibbon() {
|
||||
const ribbon = this._menuManagerService.getMenuByPositionKey(MenuManagerPosition.RIBBON);
|
||||
|
||||
// Collect all hidden$ Observables and their corresponding paths
|
||||
const hiddenObservableMap: Observable<boolean>[] = [];
|
||||
const hiddenKeyMap: string[] = [];
|
||||
for (const group of ribbon) {
|
||||
if (group.children) {
|
||||
for (const item of group.children) {
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.item?.hidden$) {
|
||||
hiddenObservableMap.push(child.item.hidden$);
|
||||
hiddenKeyMap.push(child.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenObservableMap.length === 0) {
|
||||
this._ribbon$.next(ribbon);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only get the current value once, not continuously subscribe
|
||||
combineLatest(hiddenObservableMap)
|
||||
.pipe(startWith(new Array(hiddenObservableMap.length).fill(false)))
|
||||
.subscribe((hiddenMap) => {
|
||||
const newRibbon: IMenuSchema[] = [];
|
||||
|
||||
const hiddenPathMap = hiddenMap.map((hidden, index) => {
|
||||
if (hidden) {
|
||||
return hiddenKeyMap[index];
|
||||
}
|
||||
return null;
|
||||
}).filter((item) => !!item) as string[];
|
||||
|
||||
for (const group of ribbon) {
|
||||
const newGroup: IMenuSchema = { ...group, children: [] };
|
||||
|
||||
if (group.children?.length) {
|
||||
for (const item of group.children) {
|
||||
const newItem: IMenuSchema = { ...item, children: [] };
|
||||
let shouldAddItem = true;
|
||||
|
||||
if (item.children?.length) {
|
||||
for (const child of item.children) {
|
||||
if (!hiddenPathMap.includes(child.key)) {
|
||||
newItem.children?.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (newItem.children?.every((child) => child.children?.length === 0)) {
|
||||
shouldAddItem = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAddItem) {
|
||||
newGroup.children?.push(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newGroup.children?.length && newGroup.children.every((item) => item.children?.length)) {
|
||||
newRibbon.push(newGroup);
|
||||
}
|
||||
}
|
||||
|
||||
this._ribbon$.next(newRibbon);
|
||||
})
|
||||
.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,14 @@
|
||||
*/
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { RibbonType } from '../../../controllers/ui/ui.controller';
|
||||
import type { IMenuSchema } from '../../../services/menu/menu-manager.service';
|
||||
import { IUniverInstanceService, LocaleService, throttle } from '@univerjs/core';
|
||||
import { LocaleService, throttle } from '@univerjs/core';
|
||||
import { borderBottomClassName, clsx, divideXClassName, Dropdown } from '@univerjs/design';
|
||||
import { MoreFunctionIcon } 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 { Fragment, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { RibbonPosition } from '../../../services/menu/types';
|
||||
import { IRibbonService } from '../../../services/ribbon/ribbon.service';
|
||||
import { useDependency, useObservable } from '../../../utils/di';
|
||||
import { ComponentContainer } from '../ComponentContainer';
|
||||
import { ClassicMenu } from './ribbon-menu/ClassicMenu';
|
||||
@@ -41,24 +39,9 @@ interface IRibbonProps {
|
||||
export function Ribbon(props: IRibbonProps) {
|
||||
const { ribbonType, headerMenuComponents, headerMenu = true } = props;
|
||||
|
||||
const menuManagerService = useDependency(IMenuManagerService);
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const ribbonService = useDependency(IRibbonService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
const [menuChangedTimes, setMenuChangedTimes] = useState(0);
|
||||
|
||||
const focusedUnit = useObservable(univerInstanceService.focused$);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = menuManagerService.menuChanged$.subscribe(() => {
|
||||
setMenuChangedTimes((prev) => prev + 1);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null!);
|
||||
const toolbarItemRefs = useRef<Record<string, {
|
||||
el: HTMLElement;
|
||||
@@ -68,99 +51,31 @@ export function Ribbon(props: IRibbonProps) {
|
||||
itemOrder: number;
|
||||
}>>({});
|
||||
|
||||
const [ribbon, setRibbon] = useState<IMenuSchema[]>([]);
|
||||
const [activatedTab, setActivatedTab] = useState<string>(RibbonPosition.START);
|
||||
const [collapsedIds, setCollapsedIds] = useState<string[]>([]);
|
||||
const [fakeToolbarVisible, setFakeToolbarVisible] = useState(false);
|
||||
const ribbonData = useObservable(ribbonService.ribbon$, []);
|
||||
const activatedTab = useObservable(ribbonService.activatedTab$, RibbonPosition.START);
|
||||
const collapsedIds = useObservable(ribbonService.collapsedIds$, []);
|
||||
const fakeToolbarVisible = useObservable(ribbonService.fakeToolbarVisible$, false);
|
||||
|
||||
const ribbon = useMemo(() => {
|
||||
if (ribbonType === 'simple') {
|
||||
const simpleRibbon: IMenuSchema[] = [{ key: RibbonPosition.START, children: [], order: 0 }];
|
||||
ribbonData.forEach((group) => {
|
||||
group.children?.forEach((item) => {
|
||||
simpleRibbon[0].children?.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
return simpleRibbon;
|
||||
}
|
||||
|
||||
return ribbonData;
|
||||
}, [ribbonType, ribbonData]);
|
||||
|
||||
const handleSelectTab = useCallback((group: IMenuSchema) => {
|
||||
toolbarItemRefs.current = {};
|
||||
setActivatedTab(group.key);
|
||||
ribbonService.setActivatedTab(group.key);
|
||||
}, []);
|
||||
|
||||
// process menu changes
|
||||
useEffect(() => {
|
||||
const ribbon = menuManagerService.getMenuByPositionKey(MenuManagerPosition.RIBBON);
|
||||
|
||||
// Collect all hidden$ Observables and their corresponding paths
|
||||
const hiddenObservableMap: Observable<boolean>[] = [];
|
||||
const hiddenKeyMap: string[] = [];
|
||||
for (const group of ribbon) {
|
||||
if (group.children) {
|
||||
for (const item of group.children) {
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.item?.hidden$) {
|
||||
hiddenObservableMap.push(child.item.hidden$);
|
||||
hiddenKeyMap.push(`${group.key}/${item.key}/${child.key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only get the current value once, not continuously subscribe
|
||||
combineLatest(hiddenObservableMap)
|
||||
.subscribe((hiddenMap) => {
|
||||
const newRibbon: IMenuSchema[] = [];
|
||||
|
||||
const hiddenPathMap = hiddenMap.map((hidden, index) => {
|
||||
if (hidden) {
|
||||
return hiddenKeyMap[index];
|
||||
}
|
||||
return null;
|
||||
}).filter((item) => !!item) as string[];
|
||||
|
||||
for (const group of ribbon) {
|
||||
const newGroup: IMenuSchema = { ...group, children: [] };
|
||||
|
||||
if (group.children?.length) {
|
||||
for (const item of group.children) {
|
||||
const newItem: IMenuSchema = { ...item, children: [] };
|
||||
let shouldAddItem = true;
|
||||
|
||||
if (item.children?.length) {
|
||||
for (const child of item.children) {
|
||||
const path = `${group.key}/${item.key}/${child.key}`;
|
||||
|
||||
if (!hiddenPathMap.includes(path)) {
|
||||
newItem.children?.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (newItem.children?.every((child) => child.children?.length === 0)) {
|
||||
shouldAddItem = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAddItem) {
|
||||
newGroup.children?.push(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newGroup.children?.length && newGroup.children.every((item) => item.children?.length)) {
|
||||
newRibbon.push(newGroup);
|
||||
}
|
||||
}
|
||||
|
||||
if (ribbonType === 'simple') {
|
||||
const simpleRibbon: IMenuSchema[] = [{ key: RibbonPosition.START, children: [], order: 0 }];
|
||||
newRibbon.forEach((group) => {
|
||||
group.children?.forEach((item) => {
|
||||
simpleRibbon[0].children?.push(item);
|
||||
});
|
||||
});
|
||||
|
||||
setRibbon(simpleRibbon);
|
||||
} else {
|
||||
setRibbon(newRibbon);
|
||||
}
|
||||
})
|
||||
.unsubscribe();
|
||||
}, [menuChangedTimes, focusedUnit]);
|
||||
|
||||
const activeGroup = useMemo(() => {
|
||||
const allGroups = ribbon.find((group) => group.key === activatedTab)?.children ?? [];
|
||||
const visibleGroups: IMenuSchema[] = [];
|
||||
@@ -196,7 +111,7 @@ export function Ribbon(props: IRibbonProps) {
|
||||
let timer: number | null = null;
|
||||
const observer = new ResizeObserver(throttle((entries) => {
|
||||
for (const entry of entries) {
|
||||
setFakeToolbarVisible(true);
|
||||
ribbonService.setFakeToolbarVisible(true);
|
||||
|
||||
timer = requestAnimationFrame(() => {
|
||||
const { width: avaliableWidth } = entry.contentRect;
|
||||
@@ -221,9 +136,9 @@ export function Ribbon(props: IRibbonProps) {
|
||||
}
|
||||
}
|
||||
|
||||
setCollapsedIds(newCollapsedIds);
|
||||
ribbonService.setCollapsedIds(newCollapsedIds);
|
||||
|
||||
setFakeToolbarVisible(false);
|
||||
ribbonService.setFakeToolbarVisible(false);
|
||||
});
|
||||
}
|
||||
}, 100));
|
||||
|
||||
Reference in New Issue
Block a user