feat(ui): add new ribbon type (#6105)

This commit is contained in:
白熱
2025-11-10 10:59:06 +08:00
committed by GitHub
parent 708daa9c5c
commit ba2ed35c90
32 changed files with 1635 additions and 1167 deletions
+2 -2
View File
@@ -93,11 +93,11 @@
"@types/fs-extra": "^11.0.4",
"@univerjs-infra/shared": "workspace:*",
"detect-port": "^2.1.0",
"esbuild": "^0.25.11",
"esbuild": "^0.25.12",
"esbuild-plugin-alias": "^0.2.1",
"esbuild-plugin-clean": "^1.0.1",
"esbuild-plugin-copy": "^2.1.1",
"esbuild-plugin-vue3": "^0.4.2",
"esbuild-plugin-vue3": "^0.5.1",
"esbuild-style-plugin": "^1.6.3",
"fs-extra": "^11.3.2",
"minimist": "^1.2.8",
+1
View File
@@ -122,6 +122,7 @@ function createNewInstance() {
[UniverRenderEnginePlugin],
[UniverUIPlugin, {
container: 'app',
// ribbonType: 'classic',
}],
[UniverWebComponentAdapterPlugin],
[UniverVue3AdapterPlugin],
+7 -7
View File
@@ -3,7 +3,7 @@
"type": "module",
"version": "0.11.0",
"private": true,
"packageManager": "pnpm@10.18.3",
"packageManager": "pnpm@10.20.0",
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
"license": "Apache-2.0",
"funding": {
@@ -45,21 +45,21 @@
"release": "release-it"
},
"devDependencies": {
"@antfu/eslint-config": "^6.1.0",
"@antfu/eslint-config": "^6.2.0",
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
"@eslint-react/eslint-plugin": "^2.2.4",
"@eslint-react/eslint-plugin": "^2.3.1",
"@playwright/test": "^1.56.1",
"@release-it-plugins/workspaces": "^5.0.3",
"@release-it/conventional-changelog": "^10.0.1",
"@types/fs-extra": "^11.0.4",
"@types/node": "^24.9.2",
"@types/node": "^24.10.0",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@univerjs-infra/shared": "workspace:*",
"@univerjs/design": "workspace:*",
"@vitejs/plugin-react": "^5.1.0",
"eslint": "9.38.0",
"eslint": "9.39.1",
"eslint-plugin-format": "^1.0.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "7.0.1",
@@ -67,14 +67,14 @@
"fs-extra": "^11.3.2",
"husky": "^9.1.7",
"lint-staged": "^16.2.6",
"posthog-node": "^5.10.4",
"posthog-node": "^5.11.0",
"react": "19.2.0",
"react-dom": "19.2.0",
"release-it": "^19.0.5",
"serve": "^14.2.5",
"tailwindcss": "3.4.18",
"tsx": "^4.20.6",
"turbo": "^2.5.8",
"turbo": "^2.6.0",
"typescript": "^5.9.3",
"vitest": "^3.2.4"
},
@@ -16,7 +16,7 @@
import type { IAccessor } from '@univerjs/core';
import type { IMenuButtonItem, IMenuSelectorItem, MenuSchemaType } from '@univerjs/ui';
import { MenuItemType, RibbonStartGroup } from '@univerjs/ui';
import { MenuItemType, RibbonOthersGroup } from '@univerjs/ui';
import { ReplayLocalRecordCommand, ReplayLocalRecordOnActiveCommand, ReplayLocalRecordOnNamesakeCommand } from '../commands/commands/replay.command';
import { OpenRecordPanelOperation } from '../commands/operations/operation';
import { ActionRecorderService } from '../services/action-recorder.service';
@@ -67,9 +67,9 @@ export function ReplayLocalRecordOnActiveMenuItemFactory(): IMenuButtonItem {
}
export const menuSchema: MenuSchemaType = {
[RibbonStartGroup.OTHERS]: {
[RibbonOthersGroup.OTHERS]: {
[RECORD_MENU_ITEM_ID]: {
order: 2,
order: 1,
menuItemFactory: RecordMenuItemFactory,
[OpenRecordPanelOperation.id]: {
order: 1,
+1 -1
View File
@@ -77,7 +77,7 @@
"@univerjs/themes": "workspace:*",
"@wendellhu/redi": "1.0.1",
"async-lock": "^1.4.1",
"dayjs": "^1.11.18",
"dayjs": "^1.11.19",
"fast-diff": "1.3.0",
"kdbush": "^4.0.2",
"lodash-es": "^4.17.21",
+1 -1
View File
@@ -78,7 +78,7 @@
"@univerjs/themes": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.18",
"dayjs": "^1.11.19",
"rc-dropdown": "^4.2.1",
"rc-menu": "^9.16.0",
"rc-virtual-list": "^3.16.1",
@@ -16,31 +16,31 @@
import type { MenuSchemaType } from '@univerjs/ui';
import { AddDecimalCommand, SetCurrencyCommand, SetPercentCommand, SubtractDecimalCommand } from '@univerjs/sheets-numfmt';
import { RibbonDataGroup } from '@univerjs/ui';
import { RibbonStartGroup } from '@univerjs/ui';
import { OpenNumfmtPanelOperator } from '../commands/operations/open.numfmt.panel.operation';
import { AddDecimalMenuItem, CurrencySymbolIconMenuItem, FactoryOtherMenuItem, PercentMenuItem, SubtractDecimalMenuItem } from './menu';
export const menuSchema: MenuSchemaType = {
[RibbonDataGroup.FORMULAS]: {
[RibbonStartGroup.LAYOUT]: {
[OpenNumfmtPanelOperator.id]: {
order: 9,
menuItemFactory: FactoryOtherMenuItem,
},
[SetPercentCommand.id]: {
order: 9.1,
menuItemFactory: PercentMenuItem,
},
[SetCurrencyCommand.id]: {
order: 0,
order: 9.2,
menuItemFactory: CurrencySymbolIconMenuItem,
},
[AddDecimalCommand.id]: {
order: 1,
order: 9.3,
menuItemFactory: AddDecimalMenuItem,
},
[SubtractDecimalCommand.id]: {
order: 2,
order: 9.4,
menuItemFactory: SubtractDecimalMenuItem,
},
[SetPercentCommand.id]: {
order: 3,
menuItemFactory: PercentMenuItem,
},
[OpenNumfmtPanelOperator.id]: {
order: 4,
menuItemFactory: FactoryOtherMenuItem,
},
},
};
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import type { ICommand } from '@univerjs/core';
import { CommandType, EDITOR_ACTIVATED, ICommandService, IContextService, ThemeService } from '@univerjs/core';
import type { ICommand, Workbook } from '@univerjs/core';
import { CommandType, DEFAULT_STYLES, EDITOR_ACTIVATED, ICommandService, IContextService, IUniverInstanceService, ThemeService, UniverInstanceType } from '@univerjs/core';
import {
SetInlineFormatBoldCommand,
SetInlineFormatFontFamilyCommand,
@@ -35,7 +35,9 @@ import {
SetStrikeThroughCommand,
SetTextColorCommand,
SetUnderlineCommand,
SheetsSelectionsService,
} from '@univerjs/sheets';
import { getFontStyleAtCursor } from '../../controllers/menu/utils';
/**
* It is used to set the bold style of selections or one cell, need to distinguish between
@@ -155,6 +157,90 @@ export const SetRangeFontSizeCommand: ICommand = {
},
};
export const SetRangeFontIncreaseCommand: ICommand = {
type: CommandType.COMMAND,
id: 'sheet.command.set-range-font-increase',
handler: async (accessor) => {
const univerInstanceService = accessor.get(IUniverInstanceService);
const commandService = accessor.get(ICommandService);
const contextService = accessor.get(IContextService);
const isCellEditorFocus = contextService.getContextValue(EDITOR_ACTIVATED);
const selectionManagerService = accessor.get(SheetsSelectionsService);
const defaultValue = DEFAULT_STYLES.fs;
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
const worksheet = workbook.getActiveSheet();
const primary = selectionManagerService.getCurrentLastSelection()?.primary;
const params = {
value: defaultValue,
};
if (isCellEditorFocus) {
const textRun = getFontStyleAtCursor(accessor);
if (textRun != null) {
const value = textRun.ts?.fs ?? defaultValue;
params.value = value >= 400 ? 400 : value + 1;
}
return commandService.executeCommand(SetInlineFormatFontSizeCommand.id, params);
}
if (primary != null) {
const style = worksheet.getComposedCellStyle(primary.startRow, primary.startColumn);
const value = style.fs ?? defaultValue;
params.value = value >= 400 ? 400 : value + 1;
return commandService.executeCommand(SetFontSizeCommand.id, params);
}
return false;
},
};
export const SetRangeFontDecreaseCommand: ICommand = {
type: CommandType.COMMAND,
id: 'sheet.command.set-range-font-decrease',
handler: async (accessor) => {
const univerInstanceService = accessor.get(IUniverInstanceService);
const commandService = accessor.get(ICommandService);
const contextService = accessor.get(IContextService);
const isCellEditorFocus = contextService.getContextValue(EDITOR_ACTIVATED);
const selectionManagerService = accessor.get(SheetsSelectionsService);
const defaultValue = DEFAULT_STYLES.fs;
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
const worksheet = workbook.getActiveSheet();
const primary = selectionManagerService.getCurrentLastSelection()?.primary;
const params = {
value: defaultValue,
};
if (isCellEditorFocus) {
const textRun = getFontStyleAtCursor(accessor);
if (textRun != null) {
const value = textRun.ts?.fs ?? defaultValue;
params.value = value <= 6 ? 6 : value - 1;
}
return commandService.executeCommand(SetInlineFormatFontSizeCommand.id, params);
}
if (primary != null) {
const style = worksheet.getComposedCellStyle(primary.startRow, primary.startColumn);
const value = style.fs ?? defaultValue;
params.value = value <= 6 ? 6 : value - 1;
return commandService.executeCommand(SetFontSizeCommand.id, params);
}
return false;
},
};
export const SetRangeFontFamilyCommand: ICommand = {
type: CommandType.COMMAND,
id: 'sheet.command.set-range-font-family',
@@ -64,7 +64,9 @@ import { DeleteRangeMoveUpConfirmCommand } from '../commands/commands/delete-ran
import { HideColConfirmCommand, HideRowConfirmCommand } from '../commands/commands/hide-row-col-confirm.command';
import {
SetRangeBoldCommand,
SetRangeFontDecreaseCommand,
SetRangeFontFamilyCommand,
SetRangeFontIncreaseCommand,
SetRangeFontSizeCommand,
SetRangeItalicCommand,
SetRangeStrickThroughCommand,
@@ -96,6 +98,7 @@ import { RenameSheetOperation } from '../commands/operations/rename-sheet.operat
import { CellBorderSelectorMenuItemFactory } from './menu/border.menu';
import { CLEAR_SELECTION_MENU_ID, ClearSelectionAllMenuItemFactory, ClearSelectionContentMenuItemFactory, ClearSelectionFormatMenuItemFactory, ClearSelectionMenuItemFactory } from './menu/clear.menu';
import { DELETE_RANGE_MENU_ID, DeleteRangeMenuItemFactory, DeleteRangeMoveLeftMenuItemFactory, DeleteRangeMoveUpMenuItemFactory, RemoveColMenuItemFactory, RemoveRowMenuItemFactory } from './menu/delete.menu';
import { FontSizeDecreaseMenuItemFactory, FontSizeIncreaseMenuItemFactory, FontSizeSelectorMenuItemFactory } from './menu/font.menu';
import { ToggleGridlinesMenuFactory } from './menu/gridlines.menu';
import {
CELL_INSERT_MENU_ID,
@@ -118,7 +121,6 @@ import {
// CutMenuItemFactory,
FitContentMenuItemFactory,
FontFamilySelectorMenuItemFactory,
FontSizeSelectorMenuItemFactory,
FormatPainterMenuItemFactory,
FrozenColMenuItemFactory,
FrozenMenuItemFactory,
@@ -196,8 +198,14 @@ export const menuSchema: MenuSchemaType = {
order: 2,
menuItemFactory: FontSizeSelectorMenuItemFactory,
},
// TODO: fontsize +
// TODO: fontsize -
[SetRangeFontIncreaseCommand.id]: {
order: 2.1,
menuItemFactory: FontSizeIncreaseMenuItemFactory,
},
[SetRangeFontDecreaseCommand.id]: {
order: 2.2,
menuItemFactory: FontSizeDecreaseMenuItemFactory,
},
[SetRangeBoldCommand.id]: {
order: 5,
menuItemFactory: BoldMenuItemFactory,
@@ -0,0 +1,158 @@
/**
* 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 } from '@univerjs/core';
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
import {
DEFAULT_STYLES,
EDITOR_ACTIVATED,
FOCUSING_SHEET,
ICommandService,
IContextService,
IUniverInstanceService,
UniverInstanceType,
} from '@univerjs/core';
import { SetTextSelectionsOperation } from '@univerjs/docs';
import { SetInlineFormatCommand } from '@univerjs/docs-ui';
import {
RangeProtectionPermissionEditPoint,
SetRangeValuesMutation,
SetSelectionsOperation,
SetWorksheetActiveOperation,
SheetsSelectionsService,
WorkbookEditablePermission,
WorksheetEditPermission,
WorksheetSetCellStylePermission,
} from '@univerjs/sheets';
import { FONT_SIZE_COMPONENT, FONT_SIZE_LIST, getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
import { Observable } from 'rxjs';
import {
SetRangeFontDecreaseCommand,
SetRangeFontIncreaseCommand,
SetRangeFontSizeCommand,
} from '../../commands/commands/inline-format.command';
import { deriveStateFromActiveSheet$, getCurrentRangeDisable$ } from './menu-util';
import { getFontStyleAtCursor } from './utils';
function updateFontSizeValue(accessor: IAccessor, defaultValue: number) {
const selectionManagerService = accessor.get(SheetsSelectionsService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const commandService = accessor.get(ICommandService);
const contextService = accessor.get(IContextService);
return deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => {
const updateSheet = () => {
let fs = defaultValue;
const primary = selectionManagerService.getCurrentLastSelection()?.primary;
if (primary != null) {
const style = worksheet.getComposedCellStyle(primary.startRow, primary.startColumn);
if (style.fs) {
fs = style.fs;
}
}
subscriber.next(fs);
};
const updateSheetEditor = () => {
const textRun = getFontStyleAtCursor(accessor);
if (textRun != null) {
const fs = (textRun.ts?.fs ?? defaultValue);
subscriber.next(fs);
}
};
const disposable = commandService.onCommandExecuted((c) => {
const id = c.id;
if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) {
updateSheet();
}
if (
(id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) &&
contextService.getContextValue(EDITOR_ACTIVATED) &&
contextService.getContextValue(FOCUSING_SHEET)
) {
updateSheetEditor();
}
});
updateSheet();
return disposable.dispose;
}));
}
export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem<number> {
const defaultValue = DEFAULT_STYLES.fs;
const disabled$ = getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}, true);
return {
id: SetRangeFontSizeCommand.id,
type: MenuItemType.SELECTOR,
tooltip: 'toolbar.fontSize',
label: {
name: FONT_SIZE_COMPONENT,
props: {
min: 6,
max: 400,
disabled$,
},
},
selections: FONT_SIZE_LIST,
disabled$,
value$: updateFontSizeValue(accessor, defaultValue),
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
export function FontSizeIncreaseMenuItemFactory(accessor: IAccessor): IMenuButtonItem {
const disabled$ = getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}, true);
return {
id: SetRangeFontIncreaseCommand.id,
type: MenuItemType.BUTTON,
icon: 'FontSizeIncreaseIcon',
tooltip: 'toolbar.fontSizeIncrease',
disabled$,
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
export function FontSizeDecreaseMenuItemFactory(accessor: IAccessor) {
const disabled$ = getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}, true);
return {
id: SetRangeFontDecreaseCommand.id,
type: MenuItemType.BUTTON,
icon: 'FontSizeReduceIcon',
tooltip: 'toolbar.fontSizeDecrease',
disabled$,
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
@@ -14,13 +14,12 @@
* limitations under the License.
*/
import type { DocumentDataModel, IAccessor, IColorStyle, Nullable, Workbook } from '@univerjs/core';
import type { IAccessor, IColorStyle, Nullable, Workbook } from '@univerjs/core';
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
import {
BooleanNumber,
composeStyles,
DEFAULT_STYLES,
DOCS_NORMAL_EDITOR_UNIT_ID_KEY,
EDITOR_ACTIVATED,
FOCUSING_SHEET,
FontItalic,
@@ -35,7 +34,7 @@ import {
VerticalAlign,
WrapStrategy,
} from '@univerjs/core';
import { DocSelectionManagerService, SetTextSelectionsOperation } from '@univerjs/docs';
import { SetTextSelectionsOperation } from '@univerjs/docs';
import { SetInlineFormatCommand } from '@univerjs/docs-ui';
import {
CancelFrozenCommand,
@@ -77,8 +76,6 @@ import {
FONT_FAMILY_COMPONENT,
FONT_FAMILY_ITEM_COMPONENT,
FONT_FAMILY_LIST,
FONT_SIZE_COMPONENT,
FONT_SIZE_LIST,
getMenuHiddenObservable,
IClipboardInterfaceService,
MenuItemType,
@@ -98,7 +95,6 @@ import {
ResetRangeTextColorCommand,
SetRangeBoldCommand,
SetRangeFontFamilyCommand,
SetRangeFontSizeCommand,
SetRangeItalicCommand,
SetRangeStrickThroughCommand,
SetRangeTextColorCommand,
@@ -110,6 +106,7 @@ import { SetWorksheetColAutoWidthCommand } from '../../commands/commands/set-wor
import { MENU_ITEM_INPUT_COMPONENT } from '../../components/menu-item-input';
import { FormatPainterStatus, IFormatPainterService } from '../../services/format-painter/format-painter.service';
import { deriveStateFromActiveSheet$, getCurrentRangeDisable$, getObservableWithExclusiveRange$ } from './menu-util';
import { getFontStyleAtCursor } from './utils';
export enum SheetMenuPosition {
ROW_HEADER_CONTEXT_MENU = 'ROW_HEADER_CONTEXT_MENU',
@@ -448,76 +445,6 @@ export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSel
};
}
export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem<number> {
const commandService = accessor.get(ICommandService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const selectionManagerService = accessor.get(SheetsSelectionsService);
const contextService = accessor.get(IContextService);
const defaultValue = DEFAULT_STYLES.fs;
const disabled$ = getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}, true);
return {
id: SetRangeFontSizeCommand.id,
type: MenuItemType.SELECTOR,
tooltip: 'toolbar.fontSize',
label: {
name: FONT_SIZE_COMPONENT,
props: {
min: 1,
max: 400,
disabled$,
},
},
selections: FONT_SIZE_LIST,
disabled$,
value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => {
const updateSheet = () => {
let fs = defaultValue;
const primary = selectionManagerService.getCurrentLastSelection()?.primary;
if (primary != null) {
const style = worksheet.getComposedCellStyle(primary.startRow, primary.startColumn);
if (style.fs) {
fs = style.fs;
}
}
subscriber.next(fs);
};
const updateSheetEditor = () => {
const textRun = getFontStyleAtCursor(accessor);
if (textRun != null) {
const fs = textRun.ts?.fs ?? defaultValue;
subscriber.next(fs);
}
};
const disposable = commandService.onCommandExecuted((c) => {
const id = c.id;
if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) {
updateSheet();
}
if (
(id === SetTextSelectionsOperation.id || id === SetInlineFormatCommand.id) &&
contextService.getContextValue(EDITOR_ACTIVATED) &&
contextService.getContextValue(FOCUSING_SHEET)
) {
updateSheetEditor();
}
});
updateSheet();
return disposable.dispose;
})),
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
export function ResetTextColorMenuItemFactory(accessor: IAccessor): IMenuButtonItem {
return {
id: ResetRangeTextColorCommand.id,
@@ -1394,20 +1321,3 @@ export function SetColWidthMenuItemFactory(accessor: IAccessor): IMenuButtonItem
}),
};
}
function getFontStyleAtCursor(accessor: IAccessor) {
const univerInstanceService = accessor.get(IUniverInstanceService);
const textSelectionService = accessor.get(DocSelectionManagerService);
const editorDataModel = univerInstanceService.getUnit<DocumentDataModel>(DOCS_NORMAL_EDITOR_UNIT_ID_KEY);
const activeTextRange = textSelectionService.getActiveTextRange();
if (editorDataModel == null || activeTextRange == null) return null;
const textRuns = editorDataModel.getBody()?.textRuns;
if (textRuns == null) return;
const { startOffset } = activeTextRange;
const textRun = textRuns.find(({ st, ed }) => startOffset >= st && startOffset <= ed);
return textRun;
}
@@ -0,0 +1,36 @@
/**
* 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 { DocumentDataModel, IAccessor } from '@univerjs/core';
import { DOCS_NORMAL_EDITOR_UNIT_ID_KEY, IUniverInstanceService } from '@univerjs/core';
import { DocSelectionManagerService } from '@univerjs/docs';
export function getFontStyleAtCursor(accessor: IAccessor) {
const univerInstanceService = accessor.get(IUniverInstanceService);
const textSelectionService = accessor.get(DocSelectionManagerService);
const editorDataModel = univerInstanceService.getUnit<DocumentDataModel>(DOCS_NORMAL_EDITOR_UNIT_ID_KEY);
const activeTextRange = textSelectionService.getActiveTextRange();
if (editorDataModel == null || activeTextRange == null) return null;
const textRuns = editorDataModel.getBody()?.textRuns;
if (textRuns == null) return;
const { startOffset } = activeTextRange;
const textRun = textRuns.find(({ st, ed }) => startOffset >= st && startOffset <= ed);
return textRun;
}
@@ -41,7 +41,9 @@ import { HideColConfirmCommand, HideRowConfirmCommand } from '../commands/comman
import {
ResetRangeTextColorCommand,
SetRangeBoldCommand,
SetRangeFontDecreaseCommand,
SetRangeFontFamilyCommand,
SetRangeFontIncreaseCommand,
SetRangeFontSizeCommand,
SetRangeItalicCommand,
SetRangeStrickThroughCommand,
@@ -192,6 +194,7 @@ export class SheetUIController extends Disposable {
this.disposeWithMe(componentManager.register('HideGridlinesDoubleIcon', HideGridlinesDoubleIcon));
}
// eslint-disable-next-line max-lines-per-function
private _initCommands(): void {
[
AddWorksheetMergeAllCommand,
@@ -223,6 +226,8 @@ export class SheetUIController extends Disposable {
SetRangeSubscriptCommand,
SetRangeSuperscriptCommand,
SetRangeFontSizeCommand,
SetRangeFontIncreaseCommand,
SetRangeFontDecreaseCommand,
SetRangeFontFamilyCommand,
SetRangeTextColorCommand,
ResetRangeTextColorCommand,
+2
View File
@@ -37,7 +37,9 @@ export { HideColConfirmCommand, HideRowConfirmCommand } from './commands/command
export {
ResetRangeTextColorCommand,
SetRangeBoldCommand,
SetRangeFontDecreaseCommand,
SetRangeFontFamilyCommand,
SetRangeFontIncreaseCommand,
SetRangeFontSizeCommand,
SetRangeItalicCommand,
SetRangeStrickThroughCommand,
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'Copiar format',
font: 'Tipus de lletra',
fontSize: 'Mida de la lletra',
fontSizeIncrease: 'Augmentar la mida de la lletra',
fontSizeDecrease: 'Disminuir la mida de la lletra',
bold: 'Negreta',
italic: 'Cursiva',
strikethrough: 'Ratllat',
+2
View File
@@ -24,6 +24,8 @@ const locale = {
formatPainter: 'Paint format',
font: 'Font',
fontSize: 'Font size',
fontSizeIncrease: 'Increase font size',
fontSizeDecrease: 'Decrease font size',
bold: 'Bold',
italic: 'Italic',
strikethrough: 'Strikethrough',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'Copiar formato',
font: 'Fuente',
fontSize: 'Tamaño de fuente',
fontSizeIncrease: 'Aumentar tamaño de fuente',
fontSizeDecrease: 'Disminuir tamaño de fuente',
bold: 'Negrita',
italic: 'Cursiva',
strikethrough: 'Tachado',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'کپی قالب',
font: 'فونت',
fontSize: 'اندازه فونت',
fontSizeIncrease: 'افزایش اندازه فونت',
fontSizeDecrease: 'کاهش اندازه فونت',
bold: 'پررنگ',
italic: 'ایتالیک',
strikethrough: 'خط‌دار',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'Reproduire la mise en forme',
font: 'Police',
fontSize: 'Taille de police',
fontSizeIncrease: 'Augmenter la taille de la police',
fontSizeDecrease: 'Diminuer la taille de la police',
bold: 'Gras',
italic: 'Italique',
strikethrough: 'Barré',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: '서식 페인트',
font: '글꼴',
fontSize: '글꼴 크기',
fontSizeIncrease: '글꼴 크기 증가',
fontSizeDecrease: '글꼴 크기 감소',
bold: '굵게',
italic: '기울임',
strikethrough: '취소선',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'Копировать формат',
font: 'Шрифт',
fontSize: 'Размер шрифта',
fontSizeIncrease: 'Увеличить размер шрифта',
fontSizeDecrease: 'Уменьшить размер шрифта',
bold: 'Жирный',
italic: 'Курсив',
strikethrough: 'Зачеркнутый',
+2
View File
@@ -26,6 +26,8 @@ const locale: typeof enUS = {
formatPainter: 'Chổi định dạng',
font: 'Phông chữ',
fontSize: 'Cỡ chữ',
fontSizeIncrease: 'Tăng cỡ chữ',
fontSizeDecrease: 'Giảm cỡ chữ',
bold: 'In đậm',
italic: 'In nghiêng',
strikethrough: 'Gạch ngang',
+2
View File
@@ -27,6 +27,8 @@ const locale: typeof enUS = {
formatPainter: '格式刷',
font: '字体',
fontSize: '字号',
fontSizeIncrease: '增大字号',
fontSizeDecrease: '减小字号',
bold: '粗体',
italic: '斜体',
strikethrough: '删除线',
+2
View File
@@ -27,6 +27,8 @@ const locale: typeof enUS = {
formatPainter: '格式刷',
font: '字體',
fontSize: '字號',
fontSizeIncrease: '增大字號',
fontSizeDecrease: '減小字號',
bold: '粗體',
italic: '斜體',
strikethrough: '刪除線',
@@ -16,7 +16,7 @@
import { createIdentifier } from '@univerjs/core';
export type RibbonType = 'default' | 'simple';
export type RibbonType = 'default' | 'simple' | 'classic';
export interface IWorkbenchOptions {
container?: string | HTMLElement;
+1 -1
View File
@@ -61,7 +61,7 @@ interface IMenuItemBase<V> {
hidden$?: Observable<boolean>;
disabled$?: Observable<boolean>;
params?: any;
params?: any | Function;
/** On observable value that should emit the value of the corresponding selection component. */
value$?: Observable<V>;
}
@@ -18,15 +18,17 @@ 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 { borderBottomClassName, borderClassName, clsx, divideXClassName, Dropdown, HoverCard } from '@univerjs/design';
import { DatabaseIcon, EyeIcon, FunctionIcon, HomeIcon, InsertIcon, MoreDownIcon, MoreFunctionIcon } from '@univerjs/icons';
import { IUniverInstanceService, 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 { useDependency, useObservable } from '../../../utils/di';
import { ComponentContainer } from '../ComponentContainer';
import { ClassicMenu } from './ribbon-menu/ClassicMenu';
import { DefaultMenu } from './ribbon-menu/DefaultMenu';
import { toolbarButtonClassName } from './ToolbarButton';
import { ToolbarItem } from './ToolbarItem';
@@ -36,21 +38,12 @@ interface IRibbonProps {
headerMenu?: boolean;
}
const iconMap = {
[RibbonPosition.START]: HomeIcon,
[RibbonPosition.INSERT]: InsertIcon,
[RibbonPosition.FORMULAS]: FunctionIcon,
[RibbonPosition.DATA]: DatabaseIcon,
[RibbonPosition.VIEW]: EyeIcon,
[RibbonPosition.OTHERS]: MoreFunctionIcon,
};
export function Ribbon(props: IRibbonProps) {
const { ribbonType, headerMenuComponents, headerMenu = true } = props;
const menuManagerService = useDependency(IMenuManagerService);
const univerInstanceService = useDependency(IUniverInstanceService);
const localeService = useDependency(LocaleService);
const [menuChangedTimes, setMenuChangedTimes] = useState(0);
const focusedUnit = useObservable(univerInstanceService.focused$);
@@ -76,14 +69,12 @@ export function Ribbon(props: IRibbonProps) {
const [ribbon, setRibbon] = useState<IMenuSchema[]>([]);
const [activatedTab, setActivatedTab] = useState<string>(RibbonPosition.START);
const [groupSelectorVisible, setGroupSelectorVisible] = useState(false);
const [collapsedIds, setCollapsedIds] = useState<string[]>([]);
const [fakeToolbarVisible, setFakeToolbarVisible] = useState(false);
const handleSelectTab = useCallback((group: IMenuSchema) => {
toolbarItemRefs.current = {};
setActivatedTab(group.key);
setGroupSelectorVisible(false);
}, []);
// process menu changes
@@ -287,7 +278,7 @@ export function Ribbon(props: IRibbonProps) {
return (
<>
{headerMenu && (headerMenuComponents && headerMenuComponents.size > 0) && (
<header className={clsx('univer-relative univer-h-11 univer-select-none', borderBottomClassName)}>
<header className={clsx('univer-relative univer-h-10 univer-select-none', borderBottomClassName)}>
<div
className={`
univer-absolute univer-right-2 univer-top-0 univer-flex univer-h-full univer-items-center
@@ -302,97 +293,37 @@ export function Ribbon(props: IRibbonProps) {
</header>
)}
{ribbonType === 'classic' && ribbon.length > 1 && (
<ClassicMenu
ribbon={ribbon}
activatedTab={activatedTab}
onSelectTab={handleSelectTab}
/>
)}
<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]': ribbon.length > 1,
'univer-grid-cols-[1fr] univer-justify-center': ribbonType === 'classic',
'univer-grid-cols-[auto,1fr]': ribbon.length > 1 && ribbonType !== 'classic',
'univer-grid-cols-none': ribbon.length === 1,
}, borderBottomClassName)}
>
{/* <search className="univer-mr-1 univer-w-20">
<Input />
</search> */}
{ribbon.length > 1 && (
<HoverCard
className="univer-max-w-96 !univer-rounded-xl"
align="start"
open={groupSelectorVisible}
overlay={(
<div className="univer-grid univer-gap-1 univer-px-2 univer-py-1">
{ribbon.map((group) => {
const Icon = iconMap[group.key as RibbonPosition];
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
hover:univer-bg-gray-100
dark:hover:!univer-bg-gray-700
`}
onClick={() => handleSelectTab(group)}
>
<span
className={clsx(`
univer-box-border univer-flex univer-size-9 univer-flex-shrink-0
univer-items-center univer-justify-center univer-rounded-lg
`, borderClassName)}
>
<Icon
className={`
univer-text-gray-500
dark:!univer-text-gray-300
`}
/>
</span>
<span className="univer-flex univer-flex-col">
<strong
className={`
univer-text-sm univer-font-semibold univer-text-gray-800
dark:!univer-text-gray-200
`}
>
{localeService.t(group.key)}
</strong>
<span className="univer-text-xs univer-text-gray-400">
{localeService.t(`${group.key}Desc`)}
</span>
</span>
</a>
);
})}
</div>
)}
onOpenChange={setGroupSelectorVisible}
>
<a
className={`
univer-mr-2 univer-flex univer-h-7 univer-cursor-pointer univer-items-center
univer-gap-1.5 univer-whitespace-nowrap !univer-rounded-full univer-bg-gray-700
univer-pl-3 univer-pr-2 univer-text-sm univer-text-white
dark:!univer-bg-gray-200 dark:!univer-text-gray-800
`}
onClick={() => setGroupSelectorVisible(true)}
>
{localeService.t(activatedTab)}
<MoreDownIcon
className={`
univer-text-gray-200
dark:!univer-text-gray-500
`}
/>
</a>
</HoverCard>
{ribbonType === 'default' && ribbon.length > 1 && (
<DefaultMenu
ribbon={ribbon}
activatedTab={activatedTab}
onSelectTab={handleSelectTab}
/>
)}
<div
data-u-comp="ribbon-toolbar"
ref={containerRef}
className={clsx('univer-flex univer-overflow-hidden', divideXClassName)}
className={clsx('univer-flex univer-overflow-hidden', divideXClassName, {
'univer-justify-center': ribbonType === 'classic',
})}
>
{activeGroup.visibleGroups.map((groupItem) => (groupItem.children?.length || groupItem.item) && (
<Fragment key={groupItem.key}>
@@ -213,6 +213,8 @@ export const ToolbarItem = forwardRef<ITooltipWrapperRef, IDisplayMenuItem<IMenu
function renderButtonType() {
const isCustomComponent = componentManager.get(typeof label === 'string' ? label : label?.name ?? '');
const commandValue = value ?? typeof params === 'function' ? params() : params;
return (
<ToolbarButton
data-u-command={id}
@@ -220,7 +222,7 @@ export const ToolbarItem = forwardRef<ITooltipWrapperRef, IDisplayMenuItem<IMenu
noIcon={!icon}
active={activated}
disabled={disabled}
onClick={() => executeCommand(props.commandId ?? props.id, value ?? params)}
onClick={() => executeCommand(props.commandId ?? props.id, commandValue)}
onDoubleClick={() => props.subId && executeCommand(props.subId)}
>
{isCustomComponent
@@ -0,0 +1,76 @@
/**
* 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 { IMenuSchema } from '../../../../services/menu/menu-manager.service';
import { LocaleService } from '@univerjs/core';
import { clsx } from '@univerjs/design';
import { useDependency } from '../../../../utils/di';
export function ClassicMenu({
ribbon,
activatedTab,
onSelectTab,
}: {
ribbon: IMenuSchema[];
activatedTab: string;
onSelectTab: (tab: IMenuSchema) => void;
}) {
const localeService = useDependency(LocaleService);
return (
<div
className={`
univer-flex univer-h-9 univer-w-full univer-items-center univer-justify-center univer-gap-1
univer-overflow-x-auto univer-rounded-md univer-bg-gray-50 univer-px-3
dark:!univer-bg-gray-900
`}
role="tablist"
aria-label={localeService.t('ribbon.menu')}
>
{ribbon.map((group) => {
const isActive = activatedTab === group.key;
return (
<button
key={group.key}
type="button"
role="tab"
aria-selected={isActive}
title={localeService.t(group.key)}
onClick={() => onSelectTab(group)}
className={clsx(`
univer-focus:outline-none univer-focus:ring-2 univer-focus:ring-primary-500
dark:!univer-focus:ring-primary-300
univer-flex univer-cursor-pointer univer-appearance-none univer-items-center univer-gap-1
univer-rounded-sm univer-border-none univer-px-2 univer-py-1 univer-text-sm
univer-transition-colors
`, isActive
? `
univer-bg-primary-50 univer-font-semibold univer-text-primary-600 univer-shadow-sm
dark:!univer-bg-primary-900 dark:!univer-text-primary-300
`
: `
univer-hover:bg-gray-100
dark:!univer-hover:bg-gray-700
univer-bg-transparent univer-text-gray-700
dark:!univer-text-gray-200
`)}
>
{localeService.t(group.key)}
</button>
);
})}
</div>
);
}
@@ -0,0 +1,127 @@
/**
* 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 { IMenuSchema } from '../../../../services/menu/menu-manager.service';
import { LocaleService } from '@univerjs/core';
import { borderClassName, clsx, HoverCard } from '@univerjs/design';
import { DatabaseIcon, EyeIcon, FunctionIcon, HomeIcon, InsertIcon, MoreDownIcon, MoreFunctionIcon } from '@univerjs/icons';
import { useState } from 'react';
import { RibbonPosition } from '../../../../services/menu/types';
import { useDependency } from '../../../../utils/di';
const iconMap = {
[RibbonPosition.START]: HomeIcon,
[RibbonPosition.INSERT]: InsertIcon,
[RibbonPosition.FORMULAS]: FunctionIcon,
[RibbonPosition.DATA]: DatabaseIcon,
[RibbonPosition.VIEW]: EyeIcon,
[RibbonPosition.OTHERS]: MoreFunctionIcon,
};
export function DefaultMenu({
ribbon,
activatedTab,
onSelectTab,
}: {
ribbon: IMenuSchema[];
activatedTab: string;
onSelectTab: (tab: IMenuSchema) => void;
}) {
const localeService = useDependency(LocaleService);
const [groupSelectorVisible, setGroupSelectorVisible] = useState(false);
function handleSelectTab(tab: IMenuSchema) {
onSelectTab(tab);
setGroupSelectorVisible(false);
}
return (
<HoverCard
className="univer-max-w-96 !univer-rounded-xl"
align="start"
open={groupSelectorVisible}
overlay={(
<div className="univer-grid univer-gap-1 univer-px-2 univer-py-1">
{ribbon.map((group) => {
const Icon = iconMap[group.key as RibbonPosition];
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
hover:univer-bg-gray-100
dark:hover:!univer-bg-gray-700
`}
role="button"
onClick={() => handleSelectTab(group)}
>
<span
className={clsx(`
univer-box-border univer-flex univer-size-9 univer-flex-shrink-0
univer-items-center univer-justify-center univer-rounded-lg
`, borderClassName)}
>
<Icon
className={`
univer-text-gray-500
dark:!univer-text-gray-300
`}
/>
</span>
<span className="univer-flex univer-flex-col">
<strong
className={`
univer-text-sm univer-font-semibold univer-text-gray-800
dark:!univer-text-gray-200
`}
>
{localeService.t(group.key)}
</strong>
<span className="univer-text-xs univer-text-gray-400">
{localeService.t(`${group.key}Desc`)}
</span>
</span>
</a>
);
})}
</div>
)}
onOpenChange={setGroupSelectorVisible}
>
<a
className={`
univer-mr-2 univer-flex univer-h-7 univer-cursor-pointer univer-items-center univer-gap-1.5
univer-whitespace-nowrap !univer-rounded-full univer-bg-gray-700 univer-pl-3 univer-pr-2
univer-text-sm univer-text-white
dark:!univer-bg-gray-200 dark:!univer-text-gray-800
`}
onClick={() => setGroupSelectorVisible(true)}
>
{localeService.t(activatedTab)}
<MoreDownIcon
className={`
univer-text-gray-200
dark:!univer-text-gray-500
`}
/>
</a>
</HoverCard>
);
}
@@ -15,14 +15,14 @@
*/
import type { MenuSchemaType } from '@univerjs/ui';
import { RibbonStartGroup } from '@univerjs/ui';
import { RibbonOthersGroup } from '@univerjs/ui';
import { ToggleScriptPanelOperation } from '../commands/operations/panel.operation';
import { UniscriptMenuItemFactory } from './menu';
export const menuSchema: MenuSchemaType = {
[RibbonStartGroup.OTHERS]: {
[RibbonOthersGroup.OTHERS]: {
[ToggleScriptPanelOperation.id]: {
order: 2,
order: 0,
menuItemFactory: UniscriptMenuItemFactory,
},
},
+1047 -941
View File
File diff suppressed because it is too large Load Diff