diff --git a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts index 14b7936154a..b86fe0a748c 100644 --- a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts +++ b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts @@ -156,3 +156,137 @@ test('diff facade sheet hooks', async () => { const screenshot = await page.locator(SHEET_MAIN_CANVAS_ID).screenshot(); await expect(screenshot).toMatchSnapshot(filename, { maxDiffPixels: 5 }); }); + +test('diff set force string cell', async () => { + const browser = await chromium.launch({ + headless: !!isCI, // Set to false to see the browser window + }); + const context = await browser.newContext({ + viewport: { width: 1280, height: 1280 }, + deviceScaleFactor: 2, // Set your desired DPR + }); + const page = await context.newPage(); + await page.goto('http://localhost:3000/sheets/'); + await page.waitForTimeout(2000); + + await page.evaluate(() => window.E2EControllerAPI.loadDefaultStyleSheet()); + await page.waitForTimeout(2000); + + await page.evaluate(async () => { + const activeWorkbook = window.univerAPI.getActiveWorkbook(); + const activeSheet = activeWorkbook.getActiveSheet(); + + const sheetId = activeSheet.getSheetId(); + const unitId = activeWorkbook.getId(); + + await window.univerAPI.executeCommand('sheet.operation.set-selections', { + selections: [ + { + range: { + startRow: 0, + startColumn: 7, + endRow: 0, + endColumn: 7, + rangeType: 0, + unitId, + sheetId, + }, + primary: { + actualRow: 0, + actualColumn: 7, + isMerged: false, + isMergedMainCell: false, + startRow: 0, + startColumn: 7, + endRow: 0, + endColumn: 7, + }, + style: { + strokeWidth: 1, + stroke: '#274fee', + fill: 'rgba(39,79,238,0.07)', + widgets: {}, + widgetSize: 6, + widgetStrokeWidth: 1, + widgetStroke: '#ffffff', + autofillSize: 6, + autofillStrokeWidth: 1, + autofillStroke: '#ffffff', + rowHeaderFill: 'rgba(39,79,238,0.07)', + rowHeaderStroke: '#274fee', + rowHeaderStrokeWidth: 1, + columnHeaderFill: 'rgba(39,79,238,0.07)', + columnHeaderStroke: '#274fee', + columnHeaderStrokeWidth: 1, + expandCornerSize: 40, + }, + }, + ], + unitId, + subUnitId: sheetId, + type: 2, + }); + + activeWorkbook.startEditing(); + await window.univerAPI.getActiveDocument().appendText("'1"); + activeWorkbook.endEditing(true); + }); + + const filename = generateSnapshotName('set-force-string-cell'); + const screenshot = await page.locator(SHEET_MAIN_CANVAS_ID).screenshot(); + await expect(screenshot).toMatchSnapshot(filename, { maxDiffPixels: 5 }); + + await page.waitForTimeout(2000); + await browser.close(); +}); + +test('diff set text format number cell', async () => { + const browser = await chromium.launch({ + headless: !!isCI, // Set to false to see the browser window + }); + const context = await browser.newContext({ + viewport: { width: 1280, height: 1280 }, + deviceScaleFactor: 2, // Set your desired DPR + }); + const page = await context.newPage(); + await page.goto('http://localhost:3000/sheets/'); + await page.waitForTimeout(2000); + + await page.evaluate(() => window.E2EControllerAPI.loadDefaultStyleSheet()); + await page.waitForTimeout(2000); + + await page.evaluate(async () => { + await window.univerAPI.executeCommand('sheet.command.numfmt.set.numfmt', { + values: [ + { + row: 0, + col: 7, + pattern: '@@@', + type: 'text', + }, + ], + }); + + await window.univerAPI.getActiveWorkbook().getActiveSheet().getRange('H1').setValue(2); + + await window.univerAPI.getActiveWorkbook().getActiveSheet().getRange('I1').setValue(3); + + await window.univerAPI.executeCommand('sheet.command.numfmt.set.numfmt', { + values: [ + { + row: 0, + col: 8, + pattern: '@@@', + type: 'text', + }, + ], + }); + }); + + const filename = generateSnapshotName('set-text-format-number-cell'); + const screenshot = await page.locator(SHEET_MAIN_CANVAS_ID).screenshot(); + await expect(screenshot).toMatchSnapshot(filename, { maxDiffPixels: 5 }); + + await page.waitForTimeout(2000); + await browser.close(); +}); diff --git a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-force-string-cell-ci-chromium-linux.png b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-force-string-cell-ci-chromium-linux.png new file mode 100644 index 00000000000..a9b2e95d41f Binary files /dev/null and b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-force-string-cell-ci-chromium-linux.png differ diff --git a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-text-format-number-cell-ci-chromium-linux.png b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-text-format-number-cell-ci-chromium-linux.png new file mode 100644 index 00000000000..47a3b723c3a Binary files /dev/null and b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/set-text-format-number-cell-ci-chromium-linux.png differ diff --git a/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts b/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts index c9e3e4a0c2b..52150730e90 100644 --- a/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts +++ b/packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts @@ -1456,7 +1456,18 @@ export class LexerTreeBuilder extends Disposable { } else { const nextCurrentString = formulaStringArray[cur + 1]; if (nextCurrentString && nextCurrentString === matchToken.SINGLE_QUOTATION) { + // handle 'Sheet'1'!A1 + + // Add the first single quotation + this._pushSegment(currentString); + this._addSequenceArray(sequenceArray, currentString, cur); + cur++; + + // Add the second single quotation + this._pushSegment(nextCurrentString); + this._addSequenceArray(sequenceArray, nextCurrentString, cur); cur++; + continue; } else { this._closeSingleQuotation(); } diff --git a/packages/engine-formula/src/engine/utils/__tests__/reference.spec.ts b/packages/engine-formula/src/engine/utils/__tests__/reference.spec.ts index 5d225aefcd3..b2feb3724e2 100644 --- a/packages/engine-formula/src/engine/utils/__tests__/reference.spec.ts +++ b/packages/engine-formula/src/engine/utils/__tests__/reference.spec.ts @@ -101,6 +101,19 @@ describe('Test Reference', () => { }); it('serializeRangeToRefString', () => { + expect( + serializeRangeToRefString({ + range: { + startColumn: 0, + endColumn: 10, + startRow: 5, + endRow: 10, + rangeType: RANGE_TYPE.COLUMN, + }, + sheetName: '', + unitId: '', + }) + ).toEqual('A:K'); expect( serializeRangeToRefString({ range: { @@ -140,6 +153,45 @@ describe('Test Reference', () => { unitId: 'workbook-1', }) ).toEqual("'[workbook-1]sheet1'!16:16"); + expect( + serializeRangeToRefString({ + range: { + startColumn: Number.NaN, + endColumn: Number.NaN, + startRow: 15, + endRow: 15, + rangeType: RANGE_TYPE.ROW, + }, + sheetName: "sheet'1", + unitId: '', + }) + ).toEqual("'sheet''1'!16:16"); + expect( + serializeRangeToRefString({ + range: { + startColumn: Number.NaN, + endColumn: Number.NaN, + startRow: 15, + endRow: 15, + rangeType: RANGE_TYPE.ROW, + }, + sheetName: "sheet''1", + unitId: '', + }) + ).toEqual("'sheet''''1'!16:16"); + expect( + serializeRangeToRefString({ + range: { + startColumn: Number.NaN, + endColumn: Number.NaN, + startRow: 15, + endRow: 15, + rangeType: RANGE_TYPE.ROW, + }, + sheetName: "sheet'1", + unitId: 'workbook-1', + }) + ).toEqual("'[workbook-1]sheet''1'!16:16"); }); it('deserializeRangeWithSheet', () => { @@ -259,7 +311,7 @@ describe('Test Reference', () => { '12a', '💩a', '❤️b', - "Sheet'", + "Sheet'1", '!Sheet', '!Sheet', 'Sheet1(副本)', @@ -305,11 +357,40 @@ describe('Test Reference', () => { unitId: '', }); + // with single quote + expect(handleRefStringInfo("'sheet''1'!A1")).toStrictEqual({ + refBody: 'A1', + sheetName: "sheet'1", + unitId: '', + }); + + // with double quote + expect(handleRefStringInfo("'sheet''''1'!A1")).toStrictEqual({ + refBody: 'A1', + sheetName: "sheet''1", + unitId: '', + }); + expect(handleRefStringInfo("'[Book-1.xlsx]Sheet1'!$A$4")).toStrictEqual({ refBody: '$A$4', sheetName: 'Sheet1', unitId: 'Book-1.xlsx', }); + + // with single quote + expect(handleRefStringInfo("'[Book''1.xlsx]Sheet1'!$A$4")).toStrictEqual({ + refBody: '$A$4', + sheetName: 'Sheet1', + unitId: "Book'1.xlsx", + }); + + // with double quote + expect(handleRefStringInfo("'[Book''''1.xlsx]Sheet1'!$A$4")).toStrictEqual({ + refBody: '$A$4', + sheetName: 'Sheet1', + unitId: "Book''1.xlsx", + }); + expect(handleRefStringInfo("'[Book-1.xlsx]sheet-1'!$A$4")).toStrictEqual({ refBody: '$A$4', sheetName: 'sheet-1', diff --git a/packages/engine-formula/src/engine/utils/reference.ts b/packages/engine-formula/src/engine/utils/reference.ts index 31d0b06e516..9509d776557 100644 --- a/packages/engine-formula/src/engine/utils/reference.ts +++ b/packages/engine-formula/src/engine/utils/reference.ts @@ -153,10 +153,7 @@ export function serializeRange(range: IRange): string { * @param range */ export function serializeRangeWithSheet(sheetName: string, range: IRange): string { - if (needsQuoting(sheetName)) { - return `'${sheetName}'!${serializeRange(range)}`; - } - return `${sheetName}!${serializeRange(range)}`; + return `${addQuotesBothSides(sheetName)}!${serializeRange(range)}`; } /** @@ -167,7 +164,7 @@ export function serializeRangeWithSheet(sheetName: string, range: IRange): strin */ export function serializeRangeWithSpreadsheet(unit: string, sheetName: string, range: IRange): string { if (needsQuoting(unit) || needsQuoting(sheetName)) { - return `'[${unit}]${sheetName}'!${serializeRange(range)}`; + return `'[${quoteSheetName(unit)}]${quoteSheetName(sheetName)}'!${serializeRange(range)}`; } return `[${unit}]${sheetName}!${serializeRange(range)}`; @@ -206,7 +203,7 @@ export function handleRefStringInfo(refString: string) { if (unitIdMatch != null) { unitId = unitIdMatch[0].trim(); - unitId = unitId.slice(1, unitId.length - 1); + unitId = unquoteSheetName(unitId.slice(1, unitId.length - 1)); refString = refString.replace(UNIT_NAME_REGEX_PRECOMPILING, ''); } @@ -218,6 +215,8 @@ export function handleRefStringInfo(refString: string) { if (sheetName[0] === "'" && sheetName[sheetName.length - 1] === "'") { sheetName = sheetName.substring(1, sheetName.length - 1); } + + sheetName = unquoteSheetName(sheetName); refBody = refString.substring(sheetNameIndex + 1); } else { refBody = refString; @@ -426,6 +425,31 @@ export function needsQuoting(name: string) { return false; } +/** + * Add quotes to the sheet name + */ +export function addQuotesBothSides(name: string) { + return needsQuoting(name) ? `'${quoteSheetName(name)}'` : name; +} + +/** + * Add a single quote before the single quote + * @param name + * @returns Quoted name + */ +function quoteSheetName(name: string) { + return name.replace(/'/g, "''"); +} + +/** + * Replace double single quotes with single quotes + * @param name + * @returns Unquoted name + */ +function unquoteSheetName(name: string) { + return name.replace(/''/g, "'"); +} + function isA1Notation(name: string) { const match = name.match(/[1-9][0-9]{0,6}/); // Excel has a limit on the number of rows and columns: targetRow > 1048576 || targetColumn > 16384, Univer has no limit diff --git a/packages/engine-formula/src/functions/lookup/address/__tests__/index.spec.ts b/packages/engine-formula/src/functions/lookup/address/__tests__/index.spec.ts index 2192d29cef0..c43b6e8a85a 100644 --- a/packages/engine-formula/src/functions/lookup/address/__tests__/index.spec.ts +++ b/packages/engine-formula/src/functions/lookup/address/__tests__/index.spec.ts @@ -17,16 +17,16 @@ import { describe, expect, it } from 'vitest'; import { ErrorType } from '../../../../basics/error-type'; +import { ArrayValueObject, transformToValue, transformToValueObject } from '../../../../engine/value-object/array-value-object'; import { ErrorValueObject } from '../../../../engine/value-object/base-value-object'; import { BooleanValueObject, NumberValueObject, StringValueObject, } from '../../../../engine/value-object/primitive-object'; +import { getObjectValue } from '../../../__tests__/create-function-test-bed'; import { FUNCTION_NAMES_LOOKUP } from '../../function-names'; import { Address } from '../index'; -import { ArrayValueObject, transformToValue, transformToValueObject } from '../../../../engine/value-object/array-value-object'; -import { getObjectValue } from '../../../__tests__/create-function-test-bed'; describe('Test address', () => { const testFunction = new Address(FUNCTION_NAMES_LOOKUP.ADDRESS); @@ -65,6 +65,10 @@ describe('Test address', () => { expect(calculate(2, 3, 1, false, '[Book1]Sheet1')).toBe("'[Book1]Sheet1'!R2C3"); }); + it('Absolute reference to sheet name with single quote', async () => { + expect(calculate(1, 1, 1, true, "Sheet'1")).toBe("'Sheet''1'!$A$1"); + }); + it('Absolute reference to another worksheet', async () => { expect(calculate(2, 3, 1, false, 'EXCEL SHEET')).toBe("'EXCEL SHEET'!R2C3"); }); diff --git a/packages/engine-formula/src/functions/lookup/address/index.ts b/packages/engine-formula/src/functions/lookup/address/index.ts index a561282a83d..1fee6edc439 100644 --- a/packages/engine-formula/src/functions/lookup/address/index.ts +++ b/packages/engine-formula/src/functions/lookup/address/index.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { AbsoluteRefType, type IRange } from '@univerjs/core'; +import type { ArrayValueObject } from '../../../engine/value-object/array-value-object'; +import { AbsoluteRefType, type IRange } from '@univerjs/core'; import { ErrorType } from '../../../basics/error-type'; +import { expandArrayValueObject } from '../../../engine/utils/array-object'; import { serializeRangeToR1C1 } from '../../../engine/utils/r1c1-reference'; -import { needsQuoting, serializeRange } from '../../../engine/utils/reference'; +import { addQuotesBothSides, serializeRange } from '../../../engine/utils/reference'; import { type BaseValueObject, ErrorValueObject } from '../../../engine/value-object/base-value-object'; import { BooleanValueObject, NumberValueObject, StringValueObject } from '../../../engine/value-object/primitive-object'; import { BaseFunction } from '../../base-function'; -import type { ArrayValueObject } from '../../../engine/value-object/array-value-object'; -import { expandArrayValueObject } from '../../../engine/utils/array-object'; export class Address extends BaseFunction { override minParams = 2; @@ -132,7 +132,7 @@ export class Address extends BaseFunction { const absType = transformAbsoluteRefType(absNumberValue); const a1Value = this.getZeroOrOneByOneDefault(a1); const sheetTextValue = `${sheetText.getValue()}`; - const sheetName = needsQuoting(sheetTextValue) ? `'${sheetTextValue}'` : sheetTextValue; + const sheetName = addQuotesBothSides(sheetTextValue); const range: IRange = { startRow: row, diff --git a/packages/sheets-formula/src/facade/f-univer.ts b/packages/sheets-formula/src/facade/f-univer.ts index 648382fb875..374c835bf85 100644 --- a/packages/sheets-formula/src/facade/f-univer.ts +++ b/packages/sheets-formula/src/facade/f-univer.ts @@ -27,8 +27,6 @@ export interface IFUniverSheetsFormulaMixin { * @returns {IDisposable} The disposable instance. */ registerFunction(config: IRegisterFunctionParams): IDisposable; - - // TODO@Dushusir: this API should be implemented on FFormula. } export class FUniverSheetsFormulaMixin extends FUniver implements IFUniverSheetsFormulaMixin { diff --git a/packages/sheets-numfmt-ui/src/controllers/numfmt-alert-render.controller.ts b/packages/sheets-numfmt-ui/src/controllers/numfmt-alert-render.controller.ts new file mode 100644 index 00000000000..04c3380065c --- /dev/null +++ b/packages/sheets-numfmt-ui/src/controllers/numfmt-alert-render.controller.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * 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 type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import { Disposable, Inject, isRealNum, LocaleService } from '@univerjs/core'; +import { FormulaDataModel } from '@univerjs/engine-formula'; +import { DEFAULT_TEXT_FORMAT } from '@univerjs/engine-numfmt'; +import { INumfmtService } from '@univerjs/sheets'; +import { CellAlertManagerService, CellAlertType, HoverManagerService } from '@univerjs/sheets-ui'; +import { IZenZoneService } from '@univerjs/ui'; +import { debounceTime } from 'rxjs'; + +const ALERT_KEY = 'SHEET_NUMFMT_ALERT'; + +export class NumfmtAlertRenderController extends Disposable implements IRenderModule { + constructor( + private readonly _context: IRenderContext, + @Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService, + @Inject(CellAlertManagerService) private readonly _cellAlertManagerService: CellAlertManagerService, + @Inject(LocaleService) private readonly _localeService: LocaleService, + @Inject(FormulaDataModel) private readonly _formulaDataModel: FormulaDataModel, + @IZenZoneService private readonly _zenZoneService: IZenZoneService, + @Inject(INumfmtService) private _numfmtService: INumfmtService + ) { + super(); + this._init(); + } + + private _init() { + this._initCellAlertPopup(); + this._initZenService(); + } + + private _initCellAlertPopup() { + this.disposeWithMe(this._hoverManagerService.currentCell$.pipe(debounceTime(100)).subscribe((cellPos) => { + if (cellPos) { + const location = cellPos.location; + const workbook = this._context.unit; + const worksheet = workbook.getActiveSheet(); + if (!worksheet) return; + + const unitId = location.unitId; + const sheetId = location.subUnitId; + let numfmtValue; + + const cellData = worksheet.getCell(location.row, location.col); + + if (cellData?.s) { + const style = workbook.getStyles().get(cellData.s); + if (style?.n) { + numfmtValue = style.n; + } + } + + if (!numfmtValue) { + numfmtValue = this._numfmtService.getValue(unitId, sheetId, location.row, location.col); + } + + if (!numfmtValue) { + this._hideAlert(); + return; + } + + // Preventing blank object + if (numfmtValue.pattern === DEFAULT_TEXT_FORMAT && cellData?.v && isRealNum(cellData.v)) { + const currentAlert = this._cellAlertManagerService.currentAlert.get(ALERT_KEY); + const currentLoc = currentAlert?.alert?.location; + if ( + currentLoc && + currentLoc.row === location.row && + currentLoc.col === location.col && + currentLoc.subUnitId === location.subUnitId && + currentLoc.unitId === location.unitId + ) { + return; + } + + this._cellAlertManagerService.showAlert({ + type: CellAlertType.ERROR, + title: this._localeService.t('info.error'), + message: this._localeService.t('info.forceStringInfo'), + location, + width: 200, + height: 74, + key: ALERT_KEY, + }); + return; + } + } + + this._hideAlert(); + })); + } + + private _initZenService() { + this.disposeWithMe(this._zenZoneService.visible$.subscribe((visible) => { + if (visible) { + this._hideAlert(); + } + })); + } + + private _hideAlert() { + this._cellAlertManagerService.removeAlert(ALERT_KEY); + } +} diff --git a/packages/sheets-numfmt-ui/src/controllers/numfmt.editor.controller.ts b/packages/sheets-numfmt-ui/src/controllers/numfmt.editor.controller.ts index c7b3d6796f6..0dd45f12924 100644 --- a/packages/sheets-numfmt-ui/src/controllers/numfmt.editor.controller.ts +++ b/packages/sheets-numfmt-ui/src/controllers/numfmt.editor.controller.ts @@ -169,7 +169,8 @@ export class NumfmtEditorController extends Disposable { return next(value); } - if (currentNumfmtValue?.pattern === DEFAULT_TEXT_FORMAT) { + // if the cell is text format or force string, do not convert the value + if (currentNumfmtValue?.pattern === DEFAULT_TEXT_FORMAT || value.t === CellValueType.FORCE_STRING) { return next(value); } diff --git a/packages/sheets-numfmt-ui/src/plugin.ts b/packages/sheets-numfmt-ui/src/plugin.ts index 6eb523ba872..dedb63d0bcd 100644 --- a/packages/sheets-numfmt-ui/src/plugin.ts +++ b/packages/sheets-numfmt-ui/src/plugin.ts @@ -14,12 +14,15 @@ * limitations under the License. */ +import type { Dependency } from '@univerjs/core'; import type { IUniverSheetsNumfmtUIConfig } from './controllers/config.schema'; import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; import { UI_PLUGIN_CONFIG_KEY } from '@univerjs/ui'; import { defaultPluginConfig } from './controllers/config.schema'; +import { NumfmtAlertRenderController } from './controllers/numfmt-alert-render.controller'; import { SheetNumfmtUIController } from './controllers/numfmt.controller'; import { NumfmtEditorController } from './controllers/numfmt.editor.controller'; import { NumfmtMenuController } from './controllers/numfmt.menu.controller'; @@ -35,7 +38,8 @@ export class UniverSheetsNumfmtUIPlugin extends Plugin { constructor( private readonly _config: Partial = defaultPluginConfig, @Inject(Injector) override readonly _injector: Injector, - @IConfigService private readonly _configService: IConfigService + @IConfigService private readonly _configService: IConfigService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService ) { super(); @@ -62,10 +66,21 @@ export class UniverSheetsNumfmtUIPlugin extends Plugin { } override onRendered(): void { + this._registerRenderModules(); touchDependencies(this._injector, [ [SheetNumfmtUIController], [NumfmtEditorController], [NumfmtMenuController], ]); } + + private _registerRenderModules(): void { + const modules: Dependency[] = [ + [NumfmtAlertRenderController], + ]; + + modules.forEach((m) => { + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m)); + }); + } } diff --git a/packages/sheets-numfmt/package.json b/packages/sheets-numfmt/package.json index 8c81f42212b..94db153a476 100644 --- a/packages/sheets-numfmt/package.json +++ b/packages/sheets-numfmt/package.json @@ -68,6 +68,7 @@ }, "dependencies": { "@univerjs/core": "workspace:*", + "@univerjs/engine-numfmt": "workspace:*", "@univerjs/sheets": "workspace:*" }, "devDependencies": { diff --git a/packages/sheets-numfmt/src/controllers/numfmt-cell-content.controller.ts b/packages/sheets-numfmt/src/controllers/numfmt-cell-content.controller.ts index 1795ac23a49..2ab4cd973bf 100644 --- a/packages/sheets-numfmt/src/controllers/numfmt-cell-content.controller.ts +++ b/packages/sheets-numfmt/src/controllers/numfmt-cell-content.controller.ts @@ -26,6 +26,7 @@ import { ICommandService, Inject, InterceptorEffectEnum, + isRealNum, IUniverInstanceService, LocaleService, ObjectMatrix, @@ -33,6 +34,7 @@ import { ThemeService, UniverInstanceType, } from '@univerjs/core'; +import { DEFAULT_TEXT_FORMAT } from '@univerjs/engine-numfmt'; import { InterceptCellContentPriority, INTERCEPTOR_POINT, INumfmtService, SetNumfmtMutation, SetRangeValuesMutation, SheetInterceptorService } from '@univerjs/sheets'; import { of, skip, switchMap } from 'rxjs'; import { getPatternPreviewIgnoreGeneral } from '../utils/pattern'; @@ -52,6 +54,13 @@ export class SheetsNumfmtCellContentController extends Disposable { // eslint-disable-next-line max-lines-per-function private _initInterceptorCellContent() { + const TEXT_FORMAT_MARK = { + tl: { + size: 6, + color: '#409f11', + }, + }; + const renderCache = new ObjectMatrix<{ result: ICellData; parameters: string | number }>(); this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, { effect: InterceptorEffectEnum.Value | InterceptorEffectEnum.Style, @@ -64,11 +73,6 @@ export class SheetsNumfmtCellContentController extends Disposable { return next(cell); } - // just handle number - if (originCellValue.t !== CellValueType.NUMBER || originCellValue.v == null || Number.isNaN(originCellValue.v)) { - return next(cell); - } - if (cell?.s) { const style = location.workbook.getStyles().get(cell.s); if (style?.n) { @@ -79,10 +83,27 @@ export class SheetsNumfmtCellContentController extends Disposable { if (!numfmtValue) { numfmtValue = this._numfmtService.getValue(unitId, sheetId, location.row, location.col); } + if (!numfmtValue) { return next(cell); } + // Add error marker to text format number + if (numfmtValue.pattern === DEFAULT_TEXT_FORMAT && originCellValue.v && isRealNum(originCellValue.v)) { + return next({ + ...cell, + markers: { + ...cell?.markers, + ...TEXT_FORMAT_MARK, + }, + }); + } + + // just handle number + if (originCellValue.t !== CellValueType.NUMBER || originCellValue.v == null || Number.isNaN(originCellValue.v)) { + return next(cell); + } + let numfmtRes: string = ''; const cache = renderCache.getValue(location.row, location.col); if (cache && cache.parameters === `${originCellValue.v}_${numfmtValue.pattern}`) { diff --git a/packages/sheets-ui/src/controllers/force-string-alert-render.controller.ts b/packages/sheets-ui/src/controllers/force-string-alert-render.controller.ts index 6aaa9483b0b..17ed90e8c30 100644 --- a/packages/sheets-ui/src/controllers/force-string-alert-render.controller.ts +++ b/packages/sheets-ui/src/controllers/force-string-alert-render.controller.ts @@ -17,6 +17,7 @@ import type { Workbook } from '@univerjs/core'; import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; import { CellValueType, Disposable, Inject, isRealNum, LocaleService } from '@univerjs/core'; +import { IZenZoneService } from '@univerjs/ui'; import { CellAlertManagerService, CellAlertType } from '../services/cell-alert-manager.service'; import { HoverManagerService } from '../services/hover-manager.service'; @@ -27,7 +28,8 @@ export class ForceStringAlertRenderController extends Disposable implements IRen private readonly _context: IRenderContext, @Inject(HoverManagerService) private readonly _hoverManagerService: HoverManagerService, @Inject(CellAlertManagerService) private readonly _cellAlertManagerService: CellAlertManagerService, - @Inject(LocaleService) private readonly _localeService: LocaleService + @Inject(LocaleService) private readonly _localeService: LocaleService, + @IZenZoneService private readonly _zenZoneService: IZenZoneService ) { super(); this._init(); @@ -35,6 +37,7 @@ export class ForceStringAlertRenderController extends Disposable implements IRen private _init() { this._initCellAlertPopup(); + this._initZenService(); } private _initCellAlertPopup() { @@ -73,7 +76,19 @@ export class ForceStringAlertRenderController extends Disposable implements IRen } } - this._cellAlertManagerService.removeAlert(ALERT_KEY); + this._hideAlert(); })); } + + private _initZenService() { + this.disposeWithMe(this._zenZoneService.visible$.subscribe((visible) => { + if (visible) { + this._hideAlert(); + } + })); + } + + private _hideAlert() { + this._cellAlertManagerService.removeAlert(ALERT_KEY); + } } diff --git a/packages/sheets/src/controllers/basic-worksheet.controller.ts b/packages/sheets/src/controllers/basic-worksheet.controller.ts index 99fd7f03492..151a430c2df 100644 --- a/packages/sheets/src/controllers/basic-worksheet.controller.ts +++ b/packages/sheets/src/controllers/basic-worksheet.controller.ts @@ -193,6 +193,10 @@ export class BasicWorksheetController extends Disposable implements IDisposable SetNumfmtMutation, ReorderRangeMutation, EmptyMutation, + SetRowHiddenMutation, // formula SUBTOTAL + SetRowVisibleMutation, + SetColHiddenMutation, + SetColVisibleMutation, ] as IMutation[]).forEach((mutation) => { this._commandService.registerCommand(mutation); this._dataSyncPrimaryController?.registerSyncingMutations(mutation); @@ -240,8 +244,6 @@ export class BasicWorksheetController extends Disposable implements IDisposable SetBorderPositionCommand, SetBorderStyleCommand, SetColHiddenCommand, - SetColHiddenMutation, - SetColVisibleMutation, SetColWidthCommand, SetColDataCommand, SetColDataMutation, @@ -252,8 +254,6 @@ export class BasicWorksheetController extends Disposable implements IDisposable SetRangeValuesCommand, SetRowHeightCommand, SetRowHiddenCommand, - SetRowHiddenMutation, - SetRowVisibleMutation, SetRowDataCommand, SetRowDataMutation, SetSelectedColsVisibleCommand, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 093b92b6eff..e5f312273aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2689,6 +2689,9 @@ importers: '@univerjs/core': specifier: workspace:* version: link:../core + '@univerjs/engine-numfmt': + specifier: workspace:* + version: link:../engine-numfmt '@univerjs/sheets': specifier: workspace:* version: link:../sheets