feat: add simple mode for Ribbon (#5243)

This commit is contained in:
白熱
2025-05-21 18:07:10 +08:00
committed by GitHub
parent 4ae5482fed
commit 4b4c800cf7
20 changed files with 114 additions and 519 deletions
@@ -1,36 +0,0 @@
/**
* 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 { IAccessor, ICommand } from '@univerjs/core';
import { CommandType } from '@univerjs/core';
export const DropdownListFirstItemOperation: ICommand = {
id: 'custom-menu.operation.dropdown-list-first-item',
type: CommandType.OPERATION,
handler: async (_accessor: IAccessor) => {
// alert('Dropdown list first item operation')
return true;
},
};
export const DropdownListSecondItemOperation: ICommand = {
id: 'custom-menu.operation.dropdown-list-second-item',
type: CommandType.OPERATION,
handler: async (_accessor: IAccessor) => {
// alert('Dropdown list second item operation')
return true;
},
};
@@ -1,27 +0,0 @@
/**
* 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 { IAccessor, ICommand } from '@univerjs/core';
import { CommandType } from '@univerjs/core';
export const SingleButtonOperation: ICommand = {
id: 'custom-menu.operation.single-button',
type: CommandType.OPERATION,
handler: async (_accessor: IAccessor) => {
// alert('Single button operation')
return true;
},
};
@@ -1,118 +0,0 @@
/**
* 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 { Disposable, ICommandService, Inject, Injector } from '@univerjs/core';
import { AiSingle, BarChartSingle, CommentSingle } from '@univerjs/icons';
import { ComponentManager, ContextMenuGroup, ContextMenuPosition, IMenuManagerService, MenuItemType, RibbonOthersGroup } from '@univerjs/ui';
import { DropdownListFirstItemOperation, DropdownListSecondItemOperation } from '../commands/operations/dropdown-list.operation';
import { SingleButtonOperation } from '../commands/operations/single-button.operation';
import { CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID, CustomMenuItemDropdownListFirstItemFactory, CustomMenuItemDropdownListMainButtonFactory, CustomMenuItemDropdownListSecondItemFactory } from './menu/dropdown-list.menu';
import { CustomMenuItemSingleButtonFactory } from './menu/single-button.menu';
export class CustomMenuController extends Disposable {
constructor(
@Inject(Injector) private readonly _injector: Injector,
@ICommandService private readonly _commandService: ICommandService,
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
@Inject(ComponentManager) private readonly _componentManager: ComponentManager
) {
super();
this._initCommands();
this._registerComponents();
this._initMenus();
}
/**
* register commands
*/
private _initCommands(): void {
[
SingleButtonOperation,
DropdownListFirstItemOperation,
DropdownListSecondItemOperation,
].forEach((c) => {
this.disposeWithMe(this._commandService.registerCommand(c));
});
}
/**
* register icon components
*/
private _registerComponents(): void {
this.disposeWithMe(this._componentManager.register('ButtonIcon', AiSingle));
this.disposeWithMe(this._componentManager.register('ItemIcon', BarChartSingle));
this.disposeWithMe(this._componentManager.register('MainButtonIcon', CommentSingle));
}
/**
* register menu items
*/
private _initMenus(): void {
const largeDropdownList = Array.from({ length: 15 }, (_, i) => ({
order: 20 + i,
menuItemFactory: () => {
return {
id: `${DropdownListSecondItemOperation.id}-${i}`,
type: MenuItemType.BUTTON,
title: 'customMenu.itemTwo',
icon: 'ItemIcon',
};
},
}));
this._menuManagerService.mergeMenu({
[RibbonOthersGroup.OTHERS]: {
[SingleButtonOperation.id]: {
order: 9999,
menuItemFactory: CustomMenuItemSingleButtonFactory,
},
[CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID]: {
order: 9998.5,
menuItemFactory: CustomMenuItemDropdownListMainButtonFactory,
[DropdownListFirstItemOperation.id]: {
order: 0,
menuItemFactory: CustomMenuItemDropdownListFirstItemFactory,
},
[DropdownListSecondItemOperation.id]: {
order: 1,
menuItemFactory: CustomMenuItemDropdownListSecondItemFactory,
},
...largeDropdownList,
},
},
[ContextMenuPosition.MAIN_AREA]: {
[ContextMenuGroup.OTHERS]: {
[SingleButtonOperation.id]: {
order: 9999,
menuItemFactory: CustomMenuItemSingleButtonFactory,
},
[CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID]: {
order: 9998,
menuItemFactory: CustomMenuItemDropdownListMainButtonFactory,
[DropdownListFirstItemOperation.id]: {
order: 0,
menuItemFactory: CustomMenuItemDropdownListFirstItemFactory,
},
[DropdownListSecondItemOperation.id]: {
order: 1,
menuItemFactory: CustomMenuItemDropdownListSecondItemFactory,
},
},
},
},
});
}
}
@@ -1,51 +0,0 @@
/**
* 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 { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
import { MenuItemType } from '@univerjs/ui';
import { DropdownListFirstItemOperation, DropdownListSecondItemOperation } from '../../commands/operations/dropdown-list.operation';
export const CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID = 'custom-menu.operation.dropdown-list';
export function CustomMenuItemDropdownListMainButtonFactory(): IMenuSelectorItem<string> {
return {
// When type is MenuItemType.SUBITEMS, this factory serves as a container for the drop-down list, and you can set any unique id
id: CUSTOM_MENU_DROPDOWN_LIST_OPERATION_ID,
// The type of the menu item, in this case, it is a subitems
type: MenuItemType.SUBITEMS,
icon: 'MainButtonIcon',
tooltip: 'customMenu.dropdownList',
title: 'customMenu.dropdown',
};
}
export function CustomMenuItemDropdownListFirstItemFactory(): IMenuButtonItem<string> {
return {
id: DropdownListFirstItemOperation.id,
type: MenuItemType.BUTTON,
title: 'customMenu.itemOne',
icon: 'ItemIcon',
};
}
export function CustomMenuItemDropdownListSecondItemFactory(): IMenuButtonItem<string> {
return {
id: DropdownListSecondItemOperation.id,
type: MenuItemType.BUTTON,
title: 'customMenu.itemTwo',
icon: 'ItemIcon',
};
}
@@ -1,34 +0,0 @@
/**
* 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 { IMenuButtonItem } from '@univerjs/ui';
import { MenuItemType } from '@univerjs/ui';
import { SingleButtonOperation } from '../../commands/operations/single-button.operation';
export function CustomMenuItemSingleButtonFactory(): IMenuButtonItem<string> {
return {
// Bind the command id, clicking the button will trigger this command
id: SingleButtonOperation.id,
// The type of the menu item, in this case, it is a button
type: MenuItemType.BUTTON,
// The icon of the button, which needs to be registered in ComponentManager
icon: 'ButtonIcon',
// The tooltip of the button. Prioritize matching internationalization. If no match is found, the original string will be displayed
tooltip: 'customMenu.singleButton',
// The title of the button. Prioritize matching internationalization. If no match is found, the original string will be displayed
title: 'customMenu.button',
};
}
-17
View File
@@ -1,17 +0,0 @@
/**
* 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.
*/
export { UniverSheetsCustomMenuPlugin } from './plugin';
@@ -1,26 +0,0 @@
/**
* 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.
*/
export default {
customMenu: {
button: 'Button',
singleButton: 'Single button',
dropdown: 'Dropdown',
dropdownList: 'Dropdown list',
itemOne: 'Item 1',
itemTwo: 'Item 2',
},
};
@@ -1,26 +0,0 @@
/**
* 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.
*/
export default {
customMenu: {
button: '按钮',
singleButton: '单个按钮',
dropdown: '下拉',
dropdownList: '下拉列表',
itemOne: '项目一',
itemTwo: '项目二',
},
};
-53
View File
@@ -1,53 +0,0 @@
/**
* 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 { Dependency } from '@univerjs/core';
import { Inject, Injector, LocaleService, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core';
import { CustomMenuController } from './controllers/custom-menu.controller';
import enUS from './locale/en-US';
import zhCN from './locale/zh-CN';
const SHEET_CUSTOM_MENU_PLUGIN = 'SHEET_CUSTOM_MENU_PLUGIN';
export class UniverSheetsCustomMenuPlugin extends Plugin {
static override type = UniverInstanceType.UNIVER_SHEET;
static override pluginName = SHEET_CUSTOM_MENU_PLUGIN;
constructor(
_config = {},
@Inject(Injector) protected readonly _injector: Injector,
@Inject(LocaleService) private readonly _localeService: LocaleService
) {
super();
this._localeService.load({
enUS,
zhCN,
});
}
override onStarting(): void {
([
[CustomMenuController],
] as Dependency[]).forEach((d) => this._injector.add(d));
}
override onRendered(): void {
touchDependencies(this._injector, [
[CustomMenuController],
]);
}
}
+1 -2
View File
@@ -39,7 +39,6 @@ import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
import { UniverSheetsZenEditorPlugin } from '@univerjs/sheets-zen-editor';
import { UniverUIPlugin } from '@univerjs/ui';
import { enUS, faIR, frFR, ruRU, viVN, zhCN, zhTW } from '../locales';
import { UniverSheetsCustomMenuPlugin } from './custom-menu';
import ImportCSVButtonPlugin from './custom-plugin/import-csv-button';
import '@univerjs/sheets/facade';
@@ -105,6 +104,7 @@ function createNewInstance() {
univer.registerPlugin(UniverRenderEnginePlugin);
univer.registerPlugin(UniverUIPlugin, {
container: 'app',
// ribbonType: 'simple',
});
univer.registerPlugin(UniverDocsUIPlugin);
@@ -126,7 +126,6 @@ function createNewInstance() {
univer.registerPlugin(UniverSheetsSortPlugin);
univer.registerPlugin(UniverSheetsHyperLinkPlugin);
univer.registerPlugin(UniverSheetsThreadCommentPlugin);
univer.registerPlugin(UniverSheetsCustomMenuPlugin);
univer.registerPlugin(UniverSheetsTablePlugin);
univer.registerPlugin(UniverNetworkPlugin);
@@ -48,7 +48,9 @@ export class DebuggerController extends Disposable {
const configs = this._configService.getConfig<IUniverDebuggerConfig>(DEBUGGER_PLUGIN_CONFIG_KEY);
if (configs?.fab) {
this._uiPartsService.registerComponent(BuiltInUIPart.GLOBAL, () => connectInjector(Fab, this._injector));
this.disposeWithMe(
this._uiPartsService.registerComponent(BuiltInUIPart.GLOBAL, () => connectInjector(Fab, this._injector))
);
}
}
}
@@ -1,43 +0,0 @@
/**
* 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 { Disposable, ICommandService } from '@univerjs/core';
import { IMenuManagerService } from '@univerjs/ui';
import { menuSchema, SwitchUnitOperation } from './multi-units.menu';
/**
* This controller helps to debug multi units inside a single Univer
* instance.
*/
export class MultiUnitsController extends Disposable {
constructor(
@ICommandService private readonly _commandService: ICommandService,
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService
) {
super();
this._initializeCommand();
this._initializeMenu();
}
private _initializeCommand(): void {
this.disposeWithMe(this._commandService.registerCommand(SwitchUnitOperation));
}
private _initializeMenu(): void {
this._menuManagerService.mergeMenu(menuSchema);
}
}
@@ -1,62 +0,0 @@
/**
* 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 { IAccessor, IOperation, Workbook } from '@univerjs/core';
import type { IMenuSelectorItem, IValueOption, MenuSchemaType } from '@univerjs/ui';
import { CommandType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { MenuItemType, RibbonOthersGroup } from '@univerjs/ui';
import { merge, Observable, shareReplay } from 'rxjs';
export const SwitchUnitOperation: IOperation<{ value: string }> = {
id: 'debugger.operation.switch-unit',
type: CommandType.OPERATION,
handler: (accessor, params) => {
const univerInstanceService = accessor.get(IUniverInstanceService);
if (!univerInstanceService.getUnit(params.value)) return false;
univerInstanceService.setCurrentUnitForType(params.value);
return true;
},
};
function SwitchUnitMenuFactory(accessor: IAccessor): IMenuSelectorItem {
const instanceService = accessor.get(IUniverInstanceService);
const selections$ = new Observable<Array<IValueOption>>((observer) => {
function emit() {
const sheets = instanceService.getAllUnitsForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
const options = sheets.map((sheet) => ({ label: sheet.getName() || sheet.getUnitId(), value: sheet.getUnitId() }));
observer.next(options);
}
merge(instanceService.unitAdded$, instanceService.unitDisposed$).subscribe(() => emit());
emit();
}).pipe(shareReplay(1));
return {
id: SwitchUnitOperation.id,
title: 'Switch Unit', // TODO: i18n,
type: MenuItemType.SELECTOR,
selections: selections$,
};
}
export const menuSchema: MenuSchemaType = {
[RibbonOthersGroup.OTHERS]: {
[SwitchUnitOperation.id]: {
order: 999,
menuItemFactory: SwitchUnitMenuFactory,
},
},
};
@@ -20,7 +20,6 @@ import { IConfigService, Inject, Injector, merge, Plugin, registerDependencies,
import { DEBUGGER_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './controllers/config.schema';
import { DebuggerController } from './controllers/debugger.controller';
import { E2EController } from './controllers/e2e/e2e.controller';
import { MultiUnitsController } from './controllers/multi-units/multi-units.controller';
import { PerformanceMonitorController } from './controllers/performance-monitor.controller';
import { UniverWatermarkMenuController } from './controllers/watermark.menu.controller';
@@ -51,7 +50,6 @@ export class UniverDebuggerPlugin extends Plugin {
override onStarting(): void {
const dependencies: Dependency[] = [
[DebuggerController],
[MultiUnitsController],
[E2EController],
[UniverWatermarkMenuController],
];
@@ -70,7 +68,6 @@ export class UniverDebuggerPlugin extends Plugin {
override onReady(): void {
touchDependencies(this._injector, [
[DebuggerController],
[MultiUnitsController],
]);
}
@@ -32,6 +32,7 @@ import { useNotification } from './use-notification';
import { useSidebar } from './use-sidebar';
import { useSnapshot } from './use-snapshot';
import { useTheme } from './use-theme';
import { useUnits } from './use-units';
import { useUser } from './use-user';
import { useWatermark } from './use-watermark';
@@ -40,6 +41,8 @@ export function Fab() {
const univerInstanceService = useDependency(IUniverInstanceService);
const unitType = univerInstanceService.getFocusedUnit()?.type;
if (!unitType) return null;
const items: IDropdownMenuProps['items'] = [
useLocale(),
useDarkMode(),
@@ -53,6 +56,7 @@ export function Fab() {
{ type: 'separator' },
unitType === UniverInstanceType.UNIVER_SHEET && useFloatingDom(),
unitType === UniverInstanceType.UNIVER_SHEET && useCellContent(),
unitType === UniverInstanceType.UNIVER_SHEET && useUnits(),
useSnapshot(),
useEditable(),
useUser(),
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { IUniverInstanceService } from '@univerjs/core';
import { useDependency } from '@univerjs/ui';
const menu = [
@@ -26,10 +26,6 @@ const menu = [
label: 'Dispose current unit',
value: 'unit',
},
{
label: 'Create another sheet',
value: 'create',
},
];
export function useDispose() {
@@ -45,8 +41,6 @@ export function useDispose() {
if (!focused) return false;
return univerInstanceService.disposeUnit(focused.getUnitId());
} else if (value === 'create') {
univerInstanceService.createUnit(UniverInstanceType.UNIVER_SHEET, {});
}
};
@@ -0,0 +1,66 @@
/**
* 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 { Workbook } from '@univerjs/core';
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { useDependency, useObservable } from '@univerjs/ui';
import { useEffect, useState } from 'react';
const defaultMenu = [
{
label: 'Create another sheet',
value: 'create',
},
];
export function useUnits() {
const univerInstanceService = useDependency(IUniverInstanceService);
const [menu, setMenu] = useState<{ label: string; value: string }[]>([...defaultMenu]);
const unitAdded = useObservable(univerInstanceService.unitAdded$);
const unitDisposed = useObservable(univerInstanceService.unitDisposed$);
useEffect(() => {
const sheets = univerInstanceService.getAllUnitsForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
const options = sheets.map((sheet) => ({
label: sheet.getName() || sheet.getUnitId(),
value: sheet.getUnitId(),
}));
setMenu([
...defaultMenu,
...(options as any[]),
]);
}, [unitAdded, unitDisposed]);
const onSelect = (value: string) => {
if (value === 'create') {
univerInstanceService.createUnit(UniverInstanceType.UNIVER_SHEET, {});
} else {
if (!univerInstanceService.getUnit(value)) return false;
univerInstanceService.setCurrentUnitForType(value);
}
};
return {
type: 'subItem' as const,
children: '🪸 Units',
options: menu.map((item) => ({
type: 'item' as const,
children: item.label,
onSelect: () => onSelect(item.value),
})),
};
}
@@ -16,6 +16,8 @@
import { createIdentifier } from '@univerjs/core';
export type RibbonType = 'default' | 'simple';
export interface IWorkbenchOptions {
container?: string | HTMLElement;
@@ -29,6 +31,8 @@ export interface IWorkbenchOptions {
*/
toolbar?: boolean;
ribbonType?: RibbonType;
/**
* If Univer should make the footer bar visible.
*/
@@ -16,6 +16,7 @@
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 { LocaleService, throttle } from '@univerjs/core';
import { borderBottomClassName, borderClassName, clsx, divideXClassName, Dropdown, HoverCard } from '@univerjs/design';
@@ -30,6 +31,7 @@ import { ToolbarButton } from './ToolbarButton';
import { ToolbarItem } from './ToolbarItem';
interface IRibbonProps {
ribbonType: RibbonType;
headerMenuComponents?: Set<ComponentType>;
headerMenu?: boolean;
}
@@ -44,7 +46,7 @@ const iconMap = {
};
export function Ribbon(props: IRibbonProps) {
const { headerMenuComponents, headerMenu = true } = props;
const { ribbonType, headerMenuComponents, headerMenu = true } = props;
const menuManagerService = useDependency(IMenuManagerService);
const localeService = useDependency(LocaleService);
@@ -64,8 +66,9 @@ export function Ribbon(props: IRibbonProps) {
const toolbarItemRefs = useRef<Record<string, {
el: HTMLSpanElement;
key: string;
groupOrder: number;
order: number;
groupOrder: number;
itemOrder: number;
}>>({});
const [ribbon, setRibbon] = useState<IMenuSchema[]>([]);
@@ -156,7 +159,18 @@ export function Ribbon(props: IRibbonProps) {
}
}
setRibbon(newRibbon);
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]);
@@ -201,10 +215,7 @@ export function Ribbon(props: IRibbonProps) {
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;
return a.order - b.order || a.groupOrder - b.groupOrder || a.itemOrder - b.itemOrder;
});
const newCollapsedIds: string[] = [];
@@ -229,7 +240,7 @@ export function Ribbon(props: IRibbonProps) {
setFakeToolbarVisible(false);
});
}
}, 300));
}, 100));
observer.observe(containerRef.current);
@@ -249,7 +260,7 @@ export function Ribbon(props: IRibbonProps) {
univer-flex univer-h-10 univer-min-w-min univer-items-center univer-px-3 univer-opacity-0
`, borderBottomClassName)}
>
{activeGroup.allGroups.map((groupItem) => (groupItem.children?.length || groupItem.item) && (
{activeGroup.allGroups.map((groupItem, index) => (groupItem.children?.length || groupItem.item) && (
<Fragment key={groupItem.key}>
<div className="univer-grid univer-grid-flow-col univer-gap-2 univer-px-2">
{groupItem.children && groupItem.children?.map((child) => (
@@ -262,8 +273,9 @@ export function Ribbon(props: IRibbonProps) {
toolbarItemRefs.current[child.key] = {
el: ref.el,
key: child.key,
order: index,
groupOrder: groupItem.order,
order: child.order,
itemOrder: child.order,
};
}
}}
@@ -298,7 +310,10 @@ export function Ribbon(props: IRibbonProps) {
<div
className={clsx(`
univer-box-border univer-grid univer-h-10 univer-grid-flow-col univer-items-center univer-px-3
`, 'univer-grid-cols-[auto,1fr]', borderBottomClassName)}
`, {
'univer-grid-cols-[auto,1fr]': ribbon.length > 1,
'univer-grid-cols-none': ribbon.length === 1,
}, borderBottomClassName)}
>
{/* <search className="univer-mr-1 univer-w-20">
<Input />
@@ -317,6 +332,7 @@ export function Ribbon(props: IRibbonProps) {
return (
<a
key={group.key}
data-u-comp="ribbon-group-btn"
className={`
univer-box-border univer-flex univer-cursor-pointer univer-items-center
univer-gap-2.5 univer-rounded-lg univer-px-2 univer-py-1.5
@@ -377,7 +393,11 @@ export function Ribbon(props: IRibbonProps) {
</HoverCard>
)}
<div ref={containerRef} className={clsx('univer-flex univer-overflow-hidden', divideXClassName)}>
<div
data-u-comp="ribbon-toolbar"
ref={containerRef}
className={clsx('univer-flex univer-overflow-hidden', divideXClassName)}
>
{activeGroup.visibleGroups.map((groupItem) => (groupItem.children?.length || groupItem.item) && (
<Fragment key={groupItem.key}>
<div className="univer-grid univer-grid-flow-col univer-gap-2 univer-px-2">
@@ -390,7 +410,7 @@ export function Ribbon(props: IRibbonProps) {
{/* More functions dropdown */}
{collapsedIds.length > 0 && (
<div className="univer-pl-2">
<div data-u-comp="ribbon-toolbar-more" className="univer-pl-2">
<Dropdown
collisionPadding={{ right: 12 }}
overlay={(
@@ -50,6 +50,7 @@ export function DesktopWorkbenchContent(props: IUniverWorkbenchProps) {
footer = true,
headerMenu = true,
contextMenu = true,
ribbonType = 'default',
mountContainer,
onRendered,
} = props;
@@ -161,6 +162,7 @@ export function DesktopWorkbenchContent(props: IUniverWorkbenchProps) {
key="toolbar"
components={toolbarComponents}
sharedProps={{
ribbonType,
headerMenuComponents,
headerMenu,
}}