feat: add a text-to-number function menu (#6514)

This commit is contained in:
wpxp123456
2026-01-23 21:01:07 +08:00
committed by GitHub
parent 5944c7beab
commit fe296bced8
16 changed files with 296 additions and 1 deletions
@@ -49,7 +49,7 @@ import {
SetWorksheetRowIsAutoHeightCommand,
ToggleGridlinesCommand,
} from '@univerjs/sheets';
import { ContextMenuGroup, ContextMenuPosition, RibbonPosition, RibbonStartGroup } from '@univerjs/ui';
import { ContextMenuGroup, ContextMenuPosition, RibbonDataGroup, RibbonPosition, RibbonStartGroup } from '@univerjs/ui';
import {
SheetCopyCommand,
SheetCutCommand,
@@ -183,6 +183,7 @@ import {
RenameSheetMenuItemFactory,
ShowMenuItemFactory,
} from './menu/sheet.menu';
import { Text2NumberContextMenuItemFactory, Text2NumberToolbarMenuItemFactory, TEXT_TO_NUMBER_CONTEXT_MENU_ID, TEXT_TO_NUMBER_TOOLBAR_MENU_ID } from './menu/text-to-number.menu';
export const menuSchema: MenuSchemaType = {
[RibbonPosition.START]: {
@@ -295,6 +296,14 @@ export const menuSchema: MenuSchemaType = {
},
},
},
[RibbonPosition.DATA]: {
[RibbonDataGroup.OTHERS]: {
[TEXT_TO_NUMBER_TOOLBAR_MENU_ID]: {
order: 0,
menuItemFactory: Text2NumberToolbarMenuItemFactory,
},
},
},
[ContextMenuPosition.MAIN_AREA]: {
[ContextMenuGroup.FORMAT]: {
[SheetCopyCommand.name]: {
@@ -349,6 +358,10 @@ export const menuSchema: MenuSchemaType = {
menuItemFactory: ClearSelectionAllMenuItemFactory,
},
},
[TEXT_TO_NUMBER_CONTEXT_MENU_ID]: {
order: 5,
menuItemFactory: Text2NumberContextMenuItemFactory,
},
},
[ContextMenuGroup.LAYOUT]: {
order: 1,
@@ -502,6 +515,10 @@ export const menuSchema: MenuSchemaType = {
menuItemFactory: ClearSelectionAllMenuItemFactory,
},
},
[TEXT_TO_NUMBER_CONTEXT_MENU_ID]: {
order: 5,
menuItemFactory: Text2NumberContextMenuItemFactory,
},
},
[ContextMenuGroup.LAYOUT]: {
order: 1,
@@ -631,6 +648,10 @@ export const menuSchema: MenuSchemaType = {
menuItemFactory: ClearSelectionAllMenuItemFactory,
},
},
[TEXT_TO_NUMBER_CONTEXT_MENU_ID]: {
order: 5,
menuItemFactory: Text2NumberContextMenuItemFactory,
},
},
[ContextMenuGroup.LAYOUT]: {
order: 1,
@@ -0,0 +1,109 @@
/**
* 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, Workbook } from '@univerjs/core';
import type { ISelectionWithStyle } from '@univerjs/sheets';
import type { IMenuButtonItem } from '@univerjs/ui';
import { CellValueType, isRealNum, isTextFormat, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import {
RangeProtectionPermissionEditPoint,
SheetsSelectionsService,
TextToNumberCommand,
WorkbookEditablePermission,
WorksheetEditPermission,
WorksheetSetCellValuePermission,
} from '@univerjs/sheets';
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
import { combineLatest, map, Observable } from 'rxjs';
import { getCurrentRangeDisable$, getObservableWithExclusiveRange$ } from './menu-util';
const getMenuHiddenByCurrentSelectionChangedObservable$ = (accessor: IAccessor): Observable<boolean> => {
const selectionManagerService = accessor.get(SheetsSelectionsService);
const univerInstanceService = accessor.get(IUniverInstanceService);
const worksheet = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getActiveSheet();
return new Observable((subscriber) => {
const update = (selections?: ISelectionWithStyle[]) => {
if (!worksheet || !selections || selections.length === 0) {
return subscriber.next(true);
}
for (let i = 0; i < selections.length; i++) {
const { range } = selections[i];
for (let r = range.startRow; r <= range.endRow; r++) {
for (let c = range.startColumn; c <= range.endColumn; c++) {
const cell = worksheet.getCellRaw(r, c);
const pattern = typeof cell?.s === 'string' ? worksheet.getStyleDataByHash(cell.s)?.n?.pattern : cell?.s?.n?.pattern;
if (cell && cell.v && (cell.t !== CellValueType.NUMBER || isTextFormat(pattern)) && isRealNum(cell.v)) {
return subscriber.next(false);
}
}
}
}
subscriber.next(true);
};
const subscription = selectionManagerService.selectionChanged$.subscribe((selections) => {
if (!selections || selections.length === 0) {
return subscriber.next(true);
}
update(selections);
});
update(selectionManagerService.getCurrentSelections() as ISelectionWithStyle[]);
return () => subscription.unsubscribe();
});
};
export const TEXT_TO_NUMBER_TOOLBAR_MENU_ID = 'sheet.toolbar.text-to-number';
export function Text2NumberToolbarMenuItemFactory(accessor: IAccessor): IMenuButtonItem<string> {
return {
id: TEXT_TO_NUMBER_TOOLBAR_MENU_ID,
commandId: TextToNumberCommand.id,
type: MenuItemType.BUTTON,
title: 'toolbar.textToNumber',
disabled$: getObservableWithExclusiveRange$(accessor, getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellValuePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
})),
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
export const TEXT_TO_NUMBER_CONTEXT_MENU_ID = 'sheet.contextMenu.text-to-number';
export function Text2NumberContextMenuItemFactory(accessor: IAccessor): IMenuButtonItem<string> {
return {
id: TEXT_TO_NUMBER_CONTEXT_MENU_ID,
commandId: TextToNumberCommand.id,
type: MenuItemType.BUTTON,
title: 'rightClick.textToNumber',
icon: 'PipingIcon',
disabled$: getObservableWithExclusiveRange$(accessor, getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellValuePermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
})),
hidden$: combineLatest([getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), getMenuHiddenByCurrentSelectionChangedObservable$(accessor)]).pipe(
map(([menuHidden, selectionHidden]) => menuHidden || selectionHidden)
),
};
}
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'Més',
toggleGridlines: 'Alternar línies de quadrícula',
textToNumber: 'Text a número',
},
align: {
left: 'esquerra',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'Eliminar l\'interval de protecció',
turnOnProtectRange: 'Afegir un interval de protecció',
viewAllProtectArea: 'Veure tots els intervals de protecció',
textToNumber: 'Text a número',
},
info: {
tooltip: 'Informació sobre l\'eina',
+2
View File
@@ -67,6 +67,7 @@ const locale = {
},
more: 'More',
toggleGridlines: 'Toggle Gridlines',
textToNumber: 'Text to Number',
},
align: {
left: 'left',
@@ -260,6 +261,7 @@ const locale = {
removeProtectRange: 'Remove Protection Range',
turnOnProtectRange: 'Add Protection Range',
viewAllProtectArea: 'View All Protection Ranges',
textToNumber: 'Text to Number',
},
info: {
tooltip: 'Tooltip',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'Más',
toggleGridlines: 'Alternar líneas de cuadrícula',
textToNumber: 'Texto a número',
},
align: {
left: 'izquierda',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'Eliminar rango de protección',
turnOnProtectRange: 'Añadir rango de protección',
viewAllProtectArea: 'Ver todos los rangos de protección',
textToNumber: 'Texto a número',
},
info: {
tooltip: 'Información sobre la herramienta',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'بیشتر',
toggleGridlines: 'تغییر وضعیت خطوط شبکه',
textToNumber: 'متن به عدد',
},
align: {
left: 'چپ',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'حذف محدوده محافظت',
turnOnProtectRange: 'اضافه کردن محدوده محافظت',
viewAllProtectArea: 'مشاهده همه محدوده‌های محافظت شده',
textToNumber: 'متن به عدد',
},
info: {
tooltip: 'راهنما',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'Plus',
toggleGridlines: 'Activer/désactiver le quadrillage',
textToNumber: 'Texte en nombre',
},
align: {
left: 'gauche',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'Supprimer la plage de protection',
turnOnProtectRange: 'Ajouter une plage de protection',
viewAllProtectArea: 'Voir toutes les zones de protection',
textToNumber: 'Texte en nombre',
},
info: {
tooltip: 'Info-bulle',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'その他',
toggleGridlines: 'グリッド線の表示/非表示',
textToNumber: 'テキストを数値に変換',
},
align: {
left: '左寄せ',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: '保護範囲を削除',
turnOnProtectRange: '保護範囲を追加',
viewAllProtectArea: 'すべての保護範囲を表示',
textToNumber: 'テキストを数値に変換',
},
info: {
tooltip: 'ツールチップ',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: '더 보기',
toggleGridlines: '눈금선 표시/숨기기',
textToNumber: '텍스트를 숫자로 변환',
},
align: {
left: '왼쪽',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: '보호 범위 제거',
turnOnProtectRange: '보호 범위 추가',
viewAllProtectArea: '모든 보호 범위 보기',
textToNumber: '텍스트를 숫자로 변환',
},
info: {
tooltip: '툴팁',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'Больше',
toggleGridlines: 'Переключить сетку',
textToNumber: 'Преобразовать текст в число',
},
align: {
left: 'Лево',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'Удалить защиту диапазона',
turnOnProtectRange: 'Включить защиту диапазона',
viewAllProtectArea: 'Просмотреть все защищенные области',
textToNumber: 'Преобразовать текст в число',
},
info: {
tooltip: 'Подсказка',
+2
View File
@@ -69,6 +69,7 @@ const locale: typeof enUS = {
},
more: 'Thêm',
toggleGridlines: 'Chuyển đổi đường lưới',
textToNumber: 'Chuyển văn bản thành số',
},
align: {
left: 'Căn trái',
@@ -262,6 +263,7 @@ const locale: typeof enUS = {
removeProtectRange: 'Bỏ phạm vi bảo vệ',
turnOnProtectRange: 'Thêm phạm vi bảo vệ',
viewAllProtectArea: 'Xem tất cả khu vực được bảo vệ',
textToNumber: 'Chuyển văn bản thành số',
},
info: {
tooltip: 'Gợi ý',
+2
View File
@@ -70,6 +70,7 @@ const locale: typeof enUS = {
},
more: '更多',
toggleGridlines: '切换网格线',
textToNumber: '文本转数字',
},
align: {
left: '左对齐',
@@ -263,6 +264,7 @@ const locale: typeof enUS = {
removeProtectRange: '移除保护范围',
turnOnProtectRange: '新增保护范围',
viewAllProtectArea: '查看所有保护范围',
textToNumber: '文本转数字',
},
info: {
tooltip: '提示',
+2
View File
@@ -70,6 +70,7 @@ const locale: typeof enUS = {
},
more: '更多',
toggleGridlines: '切換網格線',
textToNumber: '文字轉數字',
},
align: {
left: '靠左對齊',
@@ -263,6 +264,7 @@ const locale: typeof enUS = {
removeProtectRange: '移除保護範圍',
turnOnProtectRange: '新增保護範圍',
viewAllProtectArea: '查看所有保護範圍',
textToNumber: '文字轉數字',
},
info: {
tooltip: '提示',
@@ -0,0 +1,139 @@
/**
* 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, ICellData, ICommand, IRange } from '@univerjs/core';
import type { IRemoveNumfmtMutationParams } from '../mutations/numfmt-mutation';
import type { ISetRangeValuesMutationParams } from '../mutations/set-range-values.mutation';
import type { ISheetCommandSharedParams } from '../utils/interface';
import {
CellValueType,
CommandType,
ICommandService,
isRealNum,
isTextFormat,
IUndoRedoService,
IUniverInstanceService,
ObjectMatrix,
sequenceExecute,
} from '@univerjs/core';
import { SheetsSelectionsService } from '../../services/selections/selection.service';
import { factoryRemoveNumfmtUndoMutation, RemoveNumfmtMutation } from '../mutations/numfmt-mutation';
import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '../mutations/set-range-values.mutation';
import { getSheetCommandTarget } from './utils/target-util';
export interface ITextToNumberCommandParams extends Partial<ISheetCommandSharedParams> {
ranges?: IRange[];
}
/**
* The command to convert text to number in selected cells.
*/
export const TextToNumberCommand: ICommand = {
id: 'sheet.command.text-to-number',
type: CommandType.COMMAND,
// eslint-disable-next-line max-lines-per-function, complexity
handler: (accessor: IAccessor, params: ITextToNumberCommandParams) => {
const target = getSheetCommandTarget(accessor.get(IUniverInstanceService), params);
if (!target) return false;
const ranges = params?.ranges || accessor.get(SheetsSelectionsService).getCurrentSelections()?.map((s) => s.range);
if (!ranges?.length) {
return false;
}
const commandService = accessor.get(ICommandService);
const undoRedoService = accessor.get(IUndoRedoService);
const { worksheet, unitId, subUnitId } = target;
const newCellValue = new ObjectMatrix<ICellData>();
const removeNumfmtRanges: IRange[] = [];
for (let i = 0; i < ranges.length; i++) {
for (let r = ranges[i].startRow; r <= ranges[i].endRow; r++) {
for (let c = ranges[i].startColumn; c <= ranges[i].endColumn; c++) {
if (newCellValue.getValue(r, c)) {
continue;
}
const cell = worksheet.getCellRaw(r, c);
const pattern = typeof cell?.s === 'string' ? worksheet.getStyleDataByHash(cell.s)?.n?.pattern : cell?.s?.n?.pattern;
if (cell && cell.v && (cell.t !== CellValueType.NUMBER || isTextFormat(pattern)) && isRealNum(cell.v)) {
newCellValue.setValue(r, c, {
v: Number(cell.v),
t: CellValueType.NUMBER,
});
if (isTextFormat(pattern)) {
removeNumfmtRanges.push({
startRow: r,
endRow: r,
startColumn: c,
endColumn: c,
});
}
}
}
}
}
const setRangeValuesMutationParams: ISetRangeValuesMutationParams = {
subUnitId,
unitId,
cellValue: newCellValue.getMatrix(),
};
const redos = [
{
id: SetRangeValuesMutation.id,
params: setRangeValuesMutationParams,
},
];
const undos = [
{
id: SetRangeValuesMutation.id,
params: SetRangeValuesUndoMutationFactory(accessor, setRangeValuesMutationParams),
},
];
if (removeNumfmtRanges.length) {
const removeNumfmtMutationParams: IRemoveNumfmtMutationParams = {
unitId,
subUnitId,
ranges: removeNumfmtRanges,
};
redos.push({
id: RemoveNumfmtMutation.id,
params: removeNumfmtMutationParams,
});
undos.push(...factoryRemoveNumfmtUndoMutation(accessor, removeNumfmtMutationParams));
}
const result = sequenceExecute(redos, commandService).result;
if (result) {
undoRedoService.pushUndoRedo({
unitID: unitId,
undoMutations: undos,
redoMutations: redos,
});
return true;
}
return false;
},
};
@@ -112,6 +112,7 @@ import {
} from '../commands/commands/set-worksheet-row-height.command';
import { SetWorksheetShowCommand } from '../commands/commands/set-worksheet-show.command';
import { SplitTextToColumnsCommand } from '../commands/commands/split-text-to-columns.command';
import { TextToNumberCommand } from '../commands/commands/text-to-number.command';
import { ToggleCellCheckboxCommand } from '../commands/commands/toggle-checkbox.command';
import { ToggleGridlinesCommand } from '../commands/commands/toggle-gridlines.command';
import { UnregisterWorksheetRangeThemeStyleCommand } from '../commands/commands/unregister-range-theme.command';
@@ -328,6 +329,8 @@ export class BasicWorksheetController extends Disposable implements IDisposable
SetGridlinesColorCommand,
SetGridlinesColorMutation,
TextToNumberCommand,
// permissions range protection
SetWorksheetPermissionPointsCommand,
AddWorksheetProtectionMutation,
+1
View File
@@ -195,6 +195,7 @@ export { SetWorksheetShowCommand } from './commands/commands/set-worksheet-show.
export type { ISetWorksheetShowCommandParams } from './commands/commands/set-worksheet-show.command';
export { SplitTextToColumnsCommand } from './commands/commands/split-text-to-columns.command';
export type { ISplitTextToColumnsCommandParams } from './commands/commands/split-text-to-columns.command';
export { type ITextToNumberCommandParams, TextToNumberCommand } from './commands/commands/text-to-number.command';
export { type IToggleCellCheckboxCommandParams, ToggleCellCheckboxCommand } from './commands/commands/toggle-checkbox.command';
export { type IToggleGridlinesCommandParams, ToggleGridlinesCommand } from './commands/commands/toggle-gridlines.command';
export { UnregisterWorksheetRangeThemeStyleCommand } from './commands/commands/unregister-range-theme.command';