mirror of
https://github.com/dream-num/univer.git
synced 2026-08-28 23:01:30 +08:00
feat: add shared formula packages (#7294)
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@univerjs/drawing-ui": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/find-replace": "workspace:*",
|
||||
"@univerjs/formula": "workspace:*",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-conditional-formatting": "workspace:*",
|
||||
"@univerjs/sheets-conditional-formatting-ui": "workspace:*",
|
||||
|
||||
@@ -529,6 +529,14 @@ export interface IGridCellSelection {
|
||||
viewId: ViewId;
|
||||
recordId: RecordId;
|
||||
fieldId: FieldId;
|
||||
mode?: 'cell' | 'row' | 'column';
|
||||
anchorRecordId?: RecordId;
|
||||
focusRecordId?: RecordId;
|
||||
anchorFieldId?: FieldId;
|
||||
focusFieldId?: FieldId;
|
||||
selectedRecordIds?: RecordId[];
|
||||
showBorder?: boolean;
|
||||
virtual?: boolean;
|
||||
}
|
||||
|
||||
export interface IGridGroupSelection {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const FOCUSING_UNIT = 'FOCUSING_UNIT';
|
||||
export const FOCUSING_SHEET = 'FOCUSING_SHEET';
|
||||
export const FOCUSING_DOC = 'FOCUSING_DOC';
|
||||
export const FOCUSING_SLIDE = 'FOCUSING_SLIDE';
|
||||
export const FOCUSING_BOARD = 'FOCUSING_BOARD';
|
||||
|
||||
/** @deprecated */
|
||||
export const FOCUSING_EDITOR_BUT_HIDDEN = 'FOCUSING_EDITOR_BUT_HIDDEN';
|
||||
|
||||
@@ -24,7 +24,7 @@ import { Injector } from '../../../common/di';
|
||||
import { UnitModel, UniverInstanceType } from '../../../common/unit';
|
||||
import { DocumentDataModel } from '../../../docs/data-model/document-data-model';
|
||||
import { Workbook as WorkbookModel } from '../../../sheets/workbook';
|
||||
import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../../context/context';
|
||||
import { FOCUSING_BOARD, FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../../context/context';
|
||||
import { ContextService, IContextService } from '../../context/context.service';
|
||||
import { DesktopLogService, ILogService, LogLevel } from '../../log/log.service';
|
||||
import { IUniverInstanceService, UniverInstanceService } from '../instance.service';
|
||||
@@ -145,24 +145,35 @@ describe('UniverInstanceService', () => {
|
||||
expect(service.getUnitType('missing')).toBe(UniverInstanceType.UNRECOGNIZED);
|
||||
});
|
||||
|
||||
it('should focus sheet, doc, slide and reset contexts on null focus', () => {
|
||||
it('should focus sheet, doc, slide, board and reset contexts on null focus', () => {
|
||||
const workbook = service.createUnit<Partial<IWorkbookData>, WorkbookModel>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
const doc = service.createUnit<Partial<IDocumentData>, DocumentDataModel>(UniverInstanceType.UNIVER_DOC, createDocData());
|
||||
const slide = service.createUnit<object, MockSlideUnit>(UniverInstanceType.UNIVER_SLIDE, {});
|
||||
const board = service.createUnit<object, MockBoardUnit>(UniverInstanceType.UNIVER_BOARD, {});
|
||||
|
||||
service.focusUnit(workbook.getUnitId());
|
||||
expect(contextService.getContextValue(FOCUSING_UNIT)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
|
||||
|
||||
service.focusUnit(doc.getUnitId());
|
||||
expect(service.getFocusedUnit()?.getUnitId()).toBe(doc.getUnitId());
|
||||
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
|
||||
|
||||
service.focusUnit(slide.getUnitId());
|
||||
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
|
||||
|
||||
service.focusUnit(board.getUnitId());
|
||||
expect(contextService.getContextValue(FOCUSING_UNIT)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(true);
|
||||
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(false);
|
||||
|
||||
service.focusUnit(null);
|
||||
expect(service.getFocusedUnit()).toBeNull();
|
||||
@@ -170,6 +181,7 @@ describe('UniverInstanceService', () => {
|
||||
expect(contextService.getContextValue(FOCUSING_DOC)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_SHEET)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_SLIDE)).toBe(false);
|
||||
expect(contextService.getContextValue(FOCUSING_BOARD)).toBe(false);
|
||||
});
|
||||
|
||||
it('should replace docs and dispose units while resetting focus and current', () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { UniverInstanceType } from '../../common/unit';
|
||||
import { DocumentDataModel } from '../../docs/data-model/document-data-model';
|
||||
import { Disposable } from '../../shared/lifecycle';
|
||||
import { Workbook } from '../../sheets/workbook';
|
||||
import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../context/context';
|
||||
import { FOCUSING_BOARD, FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE, FOCUSING_UNIT } from '../context/context';
|
||||
import { IContextService } from '../context/context.service';
|
||||
import { ILogService } from '../log/log.service';
|
||||
|
||||
@@ -298,24 +298,35 @@ export class UniverInstanceService extends Disposable implements IUniverInstance
|
||||
this._contextService.setContextValue(FOCUSING_DOC, false);
|
||||
this._contextService.setContextValue(FOCUSING_SHEET, true);
|
||||
this._contextService.setContextValue(FOCUSING_SLIDE, false);
|
||||
this._contextService.setContextValue(FOCUSING_BOARD, false);
|
||||
this.setCurrentUnitForType(id!);
|
||||
} else if (this.focused instanceof DocumentDataModel) {
|
||||
this._contextService.setContextValue(FOCUSING_UNIT, true);
|
||||
this._contextService.setContextValue(FOCUSING_DOC, true);
|
||||
this._contextService.setContextValue(FOCUSING_SHEET, false);
|
||||
this._contextService.setContextValue(FOCUSING_SLIDE, false);
|
||||
this._contextService.setContextValue(FOCUSING_BOARD, false);
|
||||
this.setCurrentUnitForType(id!);
|
||||
} else if (this.focused?.type === UniverInstanceType.UNIVER_SLIDE) {
|
||||
this._contextService.setContextValue(FOCUSING_UNIT, true);
|
||||
this._contextService.setContextValue(FOCUSING_DOC, false);
|
||||
this._contextService.setContextValue(FOCUSING_SHEET, false);
|
||||
this._contextService.setContextValue(FOCUSING_SLIDE, true);
|
||||
this._contextService.setContextValue(FOCUSING_BOARD, false);
|
||||
this.setCurrentUnitForType(id!);
|
||||
} else if (this.focused?.type === UniverInstanceType.UNIVER_BOARD) {
|
||||
this._contextService.setContextValue(FOCUSING_UNIT, true);
|
||||
this._contextService.setContextValue(FOCUSING_DOC, false);
|
||||
this._contextService.setContextValue(FOCUSING_SHEET, false);
|
||||
this._contextService.setContextValue(FOCUSING_SLIDE, false);
|
||||
this._contextService.setContextValue(FOCUSING_BOARD, true);
|
||||
this.setCurrentUnitForType(id!);
|
||||
} else {
|
||||
this._contextService.setContextValue(FOCUSING_UNIT, false);
|
||||
this._contextService.setContextValue(FOCUSING_DOC, false);
|
||||
this._contextService.setContextValue(FOCUSING_SHEET, false);
|
||||
this._contextService.setContextValue(FOCUSING_SLIDE, false);
|
||||
this._contextService.setContextValue(FOCUSING_BOARD, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,10 +39,12 @@ import {
|
||||
IconManager,
|
||||
IPlatformService,
|
||||
IShortcutService,
|
||||
IUIRuntimeScopeService,
|
||||
KeyCode,
|
||||
PlatformService,
|
||||
RediContext,
|
||||
ShortcutService,
|
||||
UIRuntimeScopeService,
|
||||
} from '@univerjs/ui';
|
||||
import { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
@@ -146,6 +148,7 @@ function createQuickInsertPopupTestBed(options?: {
|
||||
injector.add([ICommandService, { useClass: CommandService }]);
|
||||
injector.add([LocaleService, { useClass: LocaleService }]);
|
||||
injector.add([IPlatformService, { useClass: PlatformService }]);
|
||||
injector.add([IUIRuntimeScopeService, { useClass: UIRuntimeScopeService }]);
|
||||
injector.add([IShortcutService, { useClass: ShortcutService }]);
|
||||
injector.add([ComponentManager, { useClass: ComponentManager }]);
|
||||
injector.add([IconManager, { useClass: IconManager }]);
|
||||
|
||||
@@ -129,7 +129,12 @@ export class FFormula extends FBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the calculation of the formula.
|
||||
* Forces formula calculation explicitly.
|
||||
*
|
||||
* Normal facade mutations such as `range.setFormula()` already mark formula
|
||||
* data dirty and schedule calculation automatically. Use this method only when
|
||||
* an external data change did not produce a dirty command or an explicit full
|
||||
* recalculation is required.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
||||
@@ -126,6 +126,38 @@ describe('RegisterOtherFormulaService', () => {
|
||||
expect((value?.result as FormulaResultMatrix | undefined)?.[0]?.[0]?.[0]?.v).toBe(2);
|
||||
});
|
||||
|
||||
it('should acknowledge other-formula application after cache and result subscribers update', async () => {
|
||||
const { service, commandService } = createService();
|
||||
const formulaId = service.registerFormulaWithRange('unit-1', 'sheet-1', '=A1+1');
|
||||
const resultPayload = {
|
||||
unitData: {},
|
||||
unitOtherData: {
|
||||
'unit-1': {
|
||||
'sheet-1': {
|
||||
[formulaId]: { 0: { 0: [{ v: 2 }] } },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let resultDistributed = false;
|
||||
let acknowledgedPayload: unknown;
|
||||
|
||||
const resultSubscription = service.formulaResult$.subscribe(() => {
|
||||
resultDistributed = true;
|
||||
});
|
||||
const appliedSubscription = service.otherFormulaResultApplied$.subscribe((payload) => {
|
||||
expect(resultDistributed).toBe(true);
|
||||
expect(service.getFormulaValueSync('unit-1', 'sheet-1', formulaId)?.status).toBe(FormulaResultStatus.SUCCESS);
|
||||
acknowledgedPayload = payload;
|
||||
});
|
||||
|
||||
await commandService.executeCommand(SetFormulaCalculationResultMutation.id, resultPayload);
|
||||
|
||||
expect(acknowledgedPayload).toBe(resultPayload);
|
||||
resultSubscription.unsubscribe();
|
||||
appliedSubscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('should support delete and dirty marking', async () => {
|
||||
const { service, commandService } = createService();
|
||||
const executedIds: string[] = [];
|
||||
|
||||
@@ -37,6 +37,7 @@ export enum OtherFormulaBizType {
|
||||
CONDITIONAL_FORMATTING = 'cf',
|
||||
DOC = 'doc',
|
||||
SLIDE = 'slide',
|
||||
SHAPE = 'shape',
|
||||
}
|
||||
|
||||
export class RegisterOtherFormulaService extends Disposable {
|
||||
@@ -49,6 +50,9 @@ export class RegisterOtherFormulaService extends Disposable {
|
||||
private _formulaResult$ = new Subject<Record<string, Record<string, IOtherFormulaResult[]>>>();
|
||||
public formulaResult$ = this._formulaResult$.asObservable();
|
||||
|
||||
private _otherFormulaResultApplied$ = new Subject<ISetFormulaCalculationResultMutation>();
|
||||
public otherFormulaResultApplied$ = this._otherFormulaResultApplied$.asObservable();
|
||||
|
||||
public calculateStarted$ = new BehaviorSubject(false);
|
||||
|
||||
constructor(
|
||||
@@ -66,6 +70,7 @@ export class RegisterOtherFormulaService extends Disposable {
|
||||
|
||||
this._formulaChangeWithRange$.complete();
|
||||
this._formulaResult$.complete();
|
||||
this._otherFormulaResultApplied$.complete();
|
||||
this.calculateStarted$.complete();
|
||||
}
|
||||
|
||||
@@ -198,6 +203,7 @@ export class RegisterOtherFormulaService extends Disposable {
|
||||
}
|
||||
}
|
||||
this._formulaResult$.next(results);
|
||||
this._otherFormulaResultApplied$.next(params);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "@univerjs/formula-ui",
|
||||
"version": "1.0.0-alpha.4",
|
||||
"private": false,
|
||||
"description": "Shared formula search and highlighting helpers for Univer editors.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/engine-formula": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/formula": "workspace:*",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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 { FunctionType, matchToken, sequenceNodeType } from '@univerjs/engine-formula';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
findFormulaStructuredReferences,
|
||||
getFormulaHighlightDataStream,
|
||||
getFormulaReplaceResult,
|
||||
resolveFormulaReferenceEditingContext,
|
||||
} from '../formula-editor-helpers';
|
||||
|
||||
describe('formula editor helpers', () => {
|
||||
it('replaces a partial function and appends its opening bracket', () => {
|
||||
expect(getFormulaReplaceResult(['SU'], 0, 'SUM', FunctionType.Math)).toEqual({
|
||||
text: `SUM${matchToken.OPEN_BRACKET}`,
|
||||
offset: -2,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not append a bracket for table references', () => {
|
||||
expect(getFormulaReplaceResult(['SalesTa'], 0, 'SalesTable', FunctionType.Table)).toEqual({
|
||||
text: 'SalesTable',
|
||||
offset: -3,
|
||||
});
|
||||
});
|
||||
|
||||
it('serializes parsed references into an editor document stream', () => {
|
||||
const nodes = [{
|
||||
token: '[Book]Sheet1!A1',
|
||||
nodeType: sequenceNodeType.REFERENCE,
|
||||
startIndex: 0,
|
||||
endIndex: 15,
|
||||
}];
|
||||
|
||||
expect(getFormulaHighlightDataStream('=', nodes)).toBe('=[Book]Sheet1!A1\r\n');
|
||||
});
|
||||
|
||||
it('uses the same cursor rules for adding and replacing references', () => {
|
||||
const reference = {
|
||||
token: 'A1:B2',
|
||||
nodeType: sequenceNodeType.REFERENCE,
|
||||
startIndex: 4,
|
||||
endIndex: 8,
|
||||
};
|
||||
const sequenceNodes = ['SUM(', reference, '+'];
|
||||
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM(A1:B2+',
|
||||
sequenceNodes,
|
||||
offset: 4,
|
||||
})).toMatchObject({ mode: 'add', referenceIndex: -1 });
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM(A1:B2+',
|
||||
sequenceNodes,
|
||||
offset: 9,
|
||||
})).toMatchObject({ mode: 'replace', referenceIndex: 0 });
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM(A1:B2+',
|
||||
sequenceNodes,
|
||||
offset: 10,
|
||||
})).toMatchObject({ mode: 'add', referenceIndex: -1 });
|
||||
});
|
||||
|
||||
it('does not draw a reference in plain formula text', () => {
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM',
|
||||
sequenceNodes: ['SUM'],
|
||||
offset: 3,
|
||||
})).toMatchObject({ mode: 'none', referenceIndex: -1 });
|
||||
});
|
||||
|
||||
it('allows adding a reference when the cursor is before the first sequence node', () => {
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM(A1)',
|
||||
sequenceNodes: [
|
||||
'SUM(',
|
||||
{
|
||||
token: 'A1',
|
||||
nodeType: sequenceNodeType.REFERENCE,
|
||||
startIndex: 4,
|
||||
endIndex: 5,
|
||||
},
|
||||
')',
|
||||
],
|
||||
offset: 0,
|
||||
})).toMatchObject({
|
||||
mode: 'add',
|
||||
nodeIndex: -1,
|
||||
referenceIndex: -1,
|
||||
});
|
||||
});
|
||||
|
||||
it('edits structured table references as selectable references', () => {
|
||||
expect(resolveFormulaReferenceEditingContext({
|
||||
formulaText: 'SUM(SalesTable[Amount])',
|
||||
sequenceNodes: [
|
||||
'SUM(',
|
||||
{
|
||||
token: 'SalesTable[Amount]',
|
||||
nodeType: sequenceNodeType.TABLE,
|
||||
startIndex: 4,
|
||||
endIndex: 21,
|
||||
},
|
||||
')',
|
||||
],
|
||||
offset: 10,
|
||||
})).toMatchObject({ mode: 'replace', referenceIndex: 0 });
|
||||
});
|
||||
|
||||
it('finds cross-unit structured references that are not resolved by the local table map', () => {
|
||||
expect(findFormulaStructuredReferences('SUM([Sales Base]!Orders[Amount])')).toEqual([{
|
||||
token: '[Sales Base]!Orders[Amount]',
|
||||
startIndex: 4,
|
||||
endIndex: 30,
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* 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 { ITextRun } from '@univerjs/core';
|
||||
import type { ISequenceNode } from '@univerjs/engine-formula';
|
||||
import type { IDescriptionService, ISearchItemWithType } from '@univerjs/formula';
|
||||
import { FunctionType, isFormulaLexerToken, matchRefDrawToken, matchToken, sequenceNodeType } from '@univerjs/engine-formula';
|
||||
|
||||
export type FormulaSequenceNode = string | ISequenceNode;
|
||||
|
||||
export interface IFormulaHighlightColors {
|
||||
formulaRefColors: string[];
|
||||
numberColor: string;
|
||||
stringColor: string;
|
||||
plainTextColor: string;
|
||||
}
|
||||
|
||||
export interface IFormulaRefSelection {
|
||||
refIndex: number;
|
||||
themeColor: string;
|
||||
token: string;
|
||||
nodeType: sequenceNodeType;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface IFormulaStructuredReferenceRange {
|
||||
token: string;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
|
||||
export type FormulaReferenceEditingMode = 'add' | 'replace' | 'none';
|
||||
|
||||
export interface IFormulaReferenceEditingContext {
|
||||
mode: FormulaReferenceEditingMode;
|
||||
nodeIndex: number;
|
||||
referenceIndex: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
function shouldAppendOpenBracket(functionType: FunctionType): boolean {
|
||||
return functionType !== FunctionType.DefinedName && functionType !== FunctionType.Table;
|
||||
}
|
||||
|
||||
export function getFormulaReplaceResult(
|
||||
nodes: FormulaSequenceNode[],
|
||||
index: number,
|
||||
formulaName: string,
|
||||
functionType: FunctionType
|
||||
): { text: string; offset: number } | undefined {
|
||||
if (index === -1) {
|
||||
return undefined;
|
||||
}
|
||||
const cloneNodes = [...nodes];
|
||||
const lastNodes = cloneNodes.splice(index + 1);
|
||||
const oldNode = cloneNodes.pop() ?? '';
|
||||
let offset = (typeof oldNode === 'string' ? oldNode.length : oldNode.token.length) - formulaName.length;
|
||||
cloneNodes.push(formulaName);
|
||||
if (lastNodes[0] !== matchToken.OPEN_BRACKET && shouldAppendOpenBracket(functionType)) {
|
||||
cloneNodes.push(matchToken.OPEN_BRACKET);
|
||||
offset -= 1;
|
||||
}
|
||||
return {
|
||||
text: [...cloneNodes, ...lastNodes].map((node) => typeof node === 'string' ? node : node.token).join(''),
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
export function searchFormulaFunctions(
|
||||
descriptionService: IDescriptionService,
|
||||
token: string,
|
||||
limit = 10
|
||||
): ISearchItemWithType[] {
|
||||
return descriptionService.getSearchListByNameFirstLetter(token).slice(0, limit);
|
||||
}
|
||||
|
||||
export function findFormulaStructuredReferences(formulaText: string): IFormulaStructuredReferenceRange[] {
|
||||
const referencePattern = /\[[^\]\r\n]+\]!(?:'(?:[^']|'')+'|[^\s()[\],:+\-*/&=<>!]+)(?:\[\[[^\r\n]*?\]\]|\[[^\]\r\n]+\])/g;
|
||||
return Array.from(formulaText.matchAll(referencePattern), (match) => ({
|
||||
token: match[0],
|
||||
startIndex: match.index,
|
||||
endIndex: match.index + match[0].length - 1,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getFormulaSequenceNodeIndex(
|
||||
sequenceNodes: FormulaSequenceNode[],
|
||||
offset: number,
|
||||
isEqual = false
|
||||
): number {
|
||||
let currentOffset = 0;
|
||||
for (let index = 0; index < sequenceNodes.length; index++) {
|
||||
const node = sequenceNodes[index];
|
||||
const nextOffset = currentOffset + (typeof node === 'string' ? node.length : node.token.length);
|
||||
if (isEqual ? nextOffset === offset : offset > currentOffset && offset <= nextOffset) {
|
||||
return index;
|
||||
}
|
||||
currentOffset = nextOffset;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function getFormulaReferenceIndex(sequenceNodes: FormulaSequenceNode[], nodeIndex: number): number {
|
||||
if (nodeIndex < 0 || nodeIndex >= sequenceNodes.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const node = sequenceNodes[nodeIndex];
|
||||
if (typeof node === 'string' || !isFormulaReferenceNode(node)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let referenceIndex = -1;
|
||||
for (let index = 0; index <= nodeIndex; index++) {
|
||||
const currentNode = sequenceNodes[index];
|
||||
if (typeof currentNode !== 'string' && isFormulaReferenceNode(currentNode)) {
|
||||
referenceIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return referenceIndex;
|
||||
}
|
||||
|
||||
export function getFormulaSequenceCharacterAtOffset(
|
||||
sequenceNodes: Array<string | { token: string }>,
|
||||
offset: number
|
||||
): string | undefined {
|
||||
let currentOffset = 0;
|
||||
for (const node of sequenceNodes) {
|
||||
const text = typeof node === 'string' ? node : node.token;
|
||||
const nextOffset = currentOffset + text.length;
|
||||
if (offset > currentOffset && offset <= nextOffset) {
|
||||
return text[offset - currentOffset - 1];
|
||||
}
|
||||
currentOffset = nextOffset;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isFormulaReferenceAddingContext(
|
||||
sequenceNodes: Array<string | { token: string }>,
|
||||
offset: number
|
||||
): boolean {
|
||||
const character = getFormulaSequenceCharacterAtOffset(sequenceNodes, offset);
|
||||
return Boolean(character && matchRefDrawToken(character));
|
||||
}
|
||||
|
||||
export function isFormulaReferenceAddingTextContext(formulaText: string, offset: number): boolean {
|
||||
const character = formulaText[offset - 1];
|
||||
const nextCharacter = formulaText[offset];
|
||||
return Boolean(
|
||||
character &&
|
||||
matchRefDrawToken(character) &&
|
||||
(!nextCharacter || (isFormulaLexerToken(nextCharacter) && nextCharacter !== matchToken.OPEN_BRACKET))
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveFormulaReferenceEditingContext(options: {
|
||||
formulaText: string;
|
||||
sequenceNodes: FormulaSequenceNode[];
|
||||
offset: number;
|
||||
selectionStart?: number;
|
||||
selectionEnd?: number;
|
||||
}): IFormulaReferenceEditingContext {
|
||||
const { formulaText, sequenceNodes } = options;
|
||||
const offset = Math.max(0, Math.min(options.offset, formulaText.length));
|
||||
let nodeIndex = getFormulaSequenceNodeIndex(sequenceNodes, offset);
|
||||
|
||||
const selectionStart = options.selectionStart ?? offset;
|
||||
const selectionEnd = options.selectionEnd ?? selectionStart;
|
||||
if (selectionEnd > selectionStart) {
|
||||
nodeIndex = sequenceNodes.findIndex((node) =>
|
||||
typeof node !== 'string' &&
|
||||
isFormulaReferenceNode(node) &&
|
||||
node.startIndex < selectionEnd &&
|
||||
node.endIndex + 1 > selectionStart
|
||||
);
|
||||
}
|
||||
|
||||
const referenceIndex = getFormulaReferenceIndex(sequenceNodes, nodeIndex);
|
||||
if (referenceIndex !== -1) {
|
||||
return { mode: 'replace', nodeIndex, referenceIndex, offset };
|
||||
}
|
||||
|
||||
const canAdd = offset === 0 ||
|
||||
isFormulaReferenceAddingContext(sequenceNodes, offset) ||
|
||||
isFormulaReferenceAddingTextContext(formulaText, offset);
|
||||
return {
|
||||
mode: canAdd ? 'add' : 'none',
|
||||
nodeIndex,
|
||||
referenceIndex: -1,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFormulaHighlightDataStream(
|
||||
leadingCharacter: string,
|
||||
sequenceNodes: FormulaSequenceNode[],
|
||||
sourceText?: string
|
||||
): string {
|
||||
const text = sourceText ?? sequenceNodes.map((node) => typeof node === 'string' ? node : node.token).join('');
|
||||
return `${leadingCharacter}${text}\r\n`;
|
||||
}
|
||||
|
||||
export function buildFormulaTextRuns(
|
||||
descriptionService: IDescriptionService,
|
||||
colors: IFormulaHighlightColors,
|
||||
sequenceNodes: FormulaSequenceNode[],
|
||||
options?: { includeTableReferences?: boolean }
|
||||
): { textRuns: ITextRun[]; refSelections: IFormulaRefSelection[] } {
|
||||
const textRuns: ITextRun[] = [];
|
||||
const refSelections: IFormulaRefSelection[] = [];
|
||||
const referenceColors = new Map<string, string>();
|
||||
let refColorIndex = 0;
|
||||
|
||||
for (let index = 0; index < sequenceNodes.length; index++) {
|
||||
const node = sequenceNodes[index];
|
||||
if (typeof node === 'string') {
|
||||
const start = textRuns.at(-1)?.ed ?? 0;
|
||||
textRuns.push(createTextRun(start, start + node.length, colors.plainTextColor));
|
||||
continue;
|
||||
}
|
||||
|
||||
let color = colors.plainTextColor;
|
||||
if (!descriptionService.hasDefinedNameDescription(node.token.trim())) {
|
||||
if (isFormulaReferenceNode(node)) {
|
||||
color = referenceColors.get(node.token) ?? colors.formulaRefColors[refColorIndex % colors.formulaRefColors.length];
|
||||
if (!referenceColors.has(node.token)) {
|
||||
referenceColors.set(node.token, color);
|
||||
refColorIndex += 1;
|
||||
}
|
||||
if (node.nodeType === sequenceNodeType.REFERENCE || options?.includeTableReferences) {
|
||||
refSelections.push({
|
||||
refIndex: index,
|
||||
themeColor: color,
|
||||
token: node.token,
|
||||
nodeType: node.nodeType,
|
||||
startIndex: node.startIndex,
|
||||
endIndex: node.endIndex,
|
||||
index: refSelections.length,
|
||||
});
|
||||
}
|
||||
} else if (node.nodeType === sequenceNodeType.NUMBER) {
|
||||
color = colors.numberColor;
|
||||
} else if (node.nodeType === sequenceNodeType.STRING || node.nodeType === sequenceNodeType.ARRAY) {
|
||||
color = colors.stringColor;
|
||||
}
|
||||
}
|
||||
textRuns.push(createTextRun(node.startIndex, node.endIndex + 1, color));
|
||||
}
|
||||
|
||||
if (options?.includeTableReferences) {
|
||||
const formulaText = sequenceNodes.map((node) => typeof node === 'string' ? node : node.token).join('');
|
||||
const structuredReferences = findFormulaStructuredReferences(formulaText);
|
||||
for (const reference of structuredReferences) {
|
||||
if (refSelections.some((selection) => selection.startIndex === reference.startIndex && selection.endIndex === reference.endIndex)) {
|
||||
continue;
|
||||
}
|
||||
const color = colors.formulaRefColors[refColorIndex % colors.formulaRefColors.length];
|
||||
refColorIndex += 1;
|
||||
applyTextRunColor(textRuns, reference.startIndex, reference.endIndex + 1, color);
|
||||
refSelections.push({
|
||||
...reference,
|
||||
refIndex: sequenceNodes.length + refSelections.length,
|
||||
themeColor: color,
|
||||
nodeType: sequenceNodeType.TABLE,
|
||||
index: refSelections.length,
|
||||
});
|
||||
}
|
||||
refSelections.sort((first, second) => first.startIndex - second.startIndex);
|
||||
refSelections.forEach((selection, index) => {
|
||||
selection.index = index;
|
||||
});
|
||||
}
|
||||
|
||||
return { textRuns, refSelections };
|
||||
}
|
||||
|
||||
function applyTextRunColor(textRuns: ITextRun[], start: number, end: number, color: string): void {
|
||||
const nextRuns: ITextRun[] = [];
|
||||
for (const run of textRuns) {
|
||||
if (run.ed <= start || run.st >= end) {
|
||||
nextRuns.push(run);
|
||||
continue;
|
||||
}
|
||||
if (run.st < start) {
|
||||
nextRuns.push(createTextRun(run.st, start, run.ts?.cl?.rgb ?? color));
|
||||
}
|
||||
nextRuns.push(createTextRun(Math.max(run.st, start), Math.min(run.ed, end), color));
|
||||
if (run.ed > end) {
|
||||
nextRuns.push(createTextRun(end, run.ed, run.ts?.cl?.rgb ?? color));
|
||||
}
|
||||
}
|
||||
textRuns.splice(0, textRuns.length, ...nextRuns);
|
||||
}
|
||||
|
||||
function isFormulaReferenceNode(node: ISequenceNode): boolean {
|
||||
return node.nodeType === sequenceNodeType.REFERENCE || node.nodeType === sequenceNodeType.TABLE;
|
||||
}
|
||||
|
||||
function createTextRun(start: number, end: number, color: string): ITextRun {
|
||||
return {
|
||||
st: start,
|
||||
ed: end,
|
||||
ts: { cl: { rgb: color }, fs: 11 },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 {
|
||||
buildFormulaTextRuns,
|
||||
findFormulaStructuredReferences,
|
||||
getFormulaHighlightDataStream,
|
||||
getFormulaReferenceIndex,
|
||||
getFormulaReplaceResult,
|
||||
getFormulaSequenceCharacterAtOffset,
|
||||
getFormulaSequenceNodeIndex,
|
||||
isFormulaReferenceAddingContext,
|
||||
isFormulaReferenceAddingTextContext,
|
||||
resolveFormulaReferenceEditingContext,
|
||||
searchFormulaFunctions,
|
||||
} from './formula-editor-helpers';
|
||||
export type {
|
||||
FormulaReferenceEditingMode,
|
||||
FormulaSequenceNode,
|
||||
IFormulaHighlightColors,
|
||||
IFormulaReferenceEditingContext,
|
||||
IFormulaRefSelection,
|
||||
IFormulaStructuredReferenceRange,
|
||||
} from './formula-editor-helpers';
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@univerjs/formula",
|
||||
"version": "1.0.0-alpha.4",
|
||||
"private": false,
|
||||
"description": "Shared formula function catalog and registration services for Univer.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./locale/*": "./src/locale/*.ts",
|
||||
"./facade": "./src/facade/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.d.ts"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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 { ICommandInfo } from '@univerjs/core';
|
||||
import type { ISetFormulaCalculationNotificationMutation, ISetFormulaCalculationResultMutation } from '@univerjs/engine-formula';
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import {
|
||||
RegisterOtherFormulaService,
|
||||
SetFormulaCalculationNotificationMutation,
|
||||
SetFormulaCalculationResultMutation,
|
||||
SetFormulaCalculationStartMutation,
|
||||
} from '@univerjs/engine-formula';
|
||||
import { FormulaCalculationSessionService, FormulaResultApplicationType } from '../services/formula-calculation-session.service';
|
||||
|
||||
function isFormulaCalculationNotification(
|
||||
params: object | undefined
|
||||
): params is ISetFormulaCalculationNotificationMutation {
|
||||
return params != null;
|
||||
}
|
||||
|
||||
function isFormulaCalculationResult(
|
||||
params: object | undefined
|
||||
): params is ISetFormulaCalculationResultMutation {
|
||||
return params != null && 'unitData' in params && 'unitOtherData' in params;
|
||||
}
|
||||
|
||||
export class FormulaCalculationSessionController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(FormulaCalculationSessionService) private readonly _sessionService: FormulaCalculationSessionService,
|
||||
@Inject(RegisterOtherFormulaService) private readonly _registerOtherFormulaService: RegisterOtherFormulaService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._sessionService.initialize();
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
private _initialize(): void {
|
||||
this.disposeWithMe(this._registerOtherFormulaService.otherFormulaResultApplied$.subscribe((result) => {
|
||||
this._sessionService.markResultApplied(FormulaResultApplicationType.OTHER_FORMULA, result);
|
||||
}));
|
||||
|
||||
this.disposeWithMe(
|
||||
this._commandService.onCommandExecuted((command: ICommandInfo) => {
|
||||
if (command.id === SetFormulaCalculationStartMutation.id) {
|
||||
this._sessionService.start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.id === SetFormulaCalculationNotificationMutation.id) {
|
||||
if (isFormulaCalculationNotification(command.params)) {
|
||||
this._handleNotification(command.params);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.id === SetFormulaCalculationResultMutation.id) {
|
||||
if (isFormulaCalculationResult(command.params)) {
|
||||
this._handleResult(command.params);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _handleNotification(params: ISetFormulaCalculationNotificationMutation): void {
|
||||
if (params.stageInfo != null) {
|
||||
this._sessionService.updateProgress(params.stageInfo);
|
||||
}
|
||||
|
||||
if (params.functionsExecutedState !== undefined) {
|
||||
this._sessionService.markCompleted(params.functionsExecutedState);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleResult(params: ISetFormulaCalculationResultMutation): void {
|
||||
const pendingApplications: FormulaResultApplicationType[] = [];
|
||||
|
||||
if (this._hasSheetResultToApply(params)) {
|
||||
pendingApplications.push(FormulaResultApplicationType.SHEET);
|
||||
}
|
||||
|
||||
if (this._hasOtherFormulaResultToApply(params)) {
|
||||
pendingApplications.push(FormulaResultApplicationType.OTHER_FORMULA);
|
||||
}
|
||||
|
||||
this._sessionService.markResultEmitted(params, pendingApplications);
|
||||
}
|
||||
|
||||
private _hasSheetResultToApply(result: ISetFormulaCalculationResultMutation): boolean {
|
||||
return Object.values(result.unitData).some((sheetData) =>
|
||||
sheetData != null && Object.values(sheetData).some((cellData) => cellData != null)
|
||||
);
|
||||
}
|
||||
|
||||
private _hasOtherFormulaResultToApply(result: ISetFormulaCalculationResultMutation): boolean {
|
||||
return Object.values(result.unitOtherData).some((subUnitData) =>
|
||||
subUnitData != null && Object.values(subUnitData).some((formulaData) =>
|
||||
formulaData != null && Object.keys(formulaData).length > 0
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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 { IDisposable } from '@univerjs/core';
|
||||
import type { ISetFormulaCalculationResultMutation } from '@univerjs/engine-formula';
|
||||
import { FFormula } from '@univerjs/engine-formula/facade';
|
||||
import { FormulaCalculationSessionService } from '@univerjs/formula';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFFormulaCalculationSessionMixin {
|
||||
/**
|
||||
* Listens for formula results after every affected model has applied them.
|
||||
* @param callback Called with the calculation result payload.
|
||||
* @returns A disposable used to unsubscribe.
|
||||
* @example
|
||||
* ```ts
|
||||
* const workbook = univerAPI.getActiveWorkbook();
|
||||
* if (!workbook) throw new Error('No active workbook.');
|
||||
* const range = workbook.getActiveSheet().getRange('A1');
|
||||
* const formula = univerAPI.getFormula();
|
||||
* const applied = formula.onCalculationResultApplied(10_000);
|
||||
* const disposable = formula.calculationResultApplied((result) => {
|
||||
* console.log('Formula results applied:', result);
|
||||
* });
|
||||
*
|
||||
* // Setting a formula automatically schedules calculation.
|
||||
* range.setFormula('=SUM(B1:B3)');
|
||||
* await applied;
|
||||
*
|
||||
* // Dispose the listener when it is no longer needed.
|
||||
* disposable.dispose();
|
||||
* ```
|
||||
*/
|
||||
calculationResultApplied(callback: (result: ISetFormulaCalculationResultMutation) => void): IDisposable;
|
||||
|
||||
/**
|
||||
* Waits until the latest formula-calculation results have been applied.
|
||||
* Create the returned promise before the mutation whose result you need to
|
||||
* observe. Formula-aware facade mutations schedule calculation automatically.
|
||||
* @param timeout Optional timeout in milliseconds.
|
||||
* @returns A promise that resolves after result application, or when no calculation starts.
|
||||
* @example
|
||||
* ```ts
|
||||
* const workbook = univerAPI.getActiveWorkbook();
|
||||
* if (!workbook) throw new Error('No active workbook.');
|
||||
* const range = workbook.getActiveSheet().getRange('A1');
|
||||
* const formula = univerAPI.getFormula();
|
||||
* const applied = formula.onCalculationResultApplied(10_000);
|
||||
* range.setFormula('=SUM(B1:B3)');
|
||||
* await applied;
|
||||
* console.log(range.getValue());
|
||||
* ```
|
||||
*/
|
||||
onCalculationResultApplied(timeout?: number): Promise<void>;
|
||||
}
|
||||
|
||||
export class FFormulaCalculationSessionMixin extends FFormula implements IFFormulaCalculationSessionMixin {
|
||||
override calculationResultApplied(callback: (result: ISetFormulaCalculationResultMutation) => void): IDisposable {
|
||||
const subscription = this._injector.get(FormulaCalculationSessionService).resultApplied$.subscribe((result) => {
|
||||
if (typeof requestIdleCallback === 'function') {
|
||||
requestIdleCallback(() => callback(result));
|
||||
return;
|
||||
}
|
||||
|
||||
queueMicrotask(() => callback(result));
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => subscription.unsubscribe(),
|
||||
};
|
||||
}
|
||||
|
||||
override onCalculationResultApplied(timeout?: number): Promise<void> {
|
||||
return this._injector.get(FormulaCalculationSessionService).waitForLatestApplied(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
FFormula.extend(FFormulaCalculationSessionMixin);
|
||||
declare module '@univerjs/engine-formula/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FFormula extends IFFormulaCalculationSessionMixin {}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 './f-formula';
|
||||
|
||||
export type * from './f-formula';
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 { FormulaCalculationSessionController } from './controllers/formula-calculation-session.controller';
|
||||
export { UniverFormulaPlugin } from './plugin';
|
||||
export { DescriptionService, IDescriptionService } from './services/description.service';
|
||||
export type { ISearchItem, ISearchItemWithType } from './services/description.service';
|
||||
export { FormulaCalculationSessionService, FormulaResultApplicationType } from './services/formula-calculation-session.service';
|
||||
export type { IFormulaCalculationSessionState } from './services/formula-calculation-session.service';
|
||||
export { FUNCTION_LIST_ARRAY } from './services/function-list/array';
|
||||
export { FUNCTION_LIST_COMPATIBILITY } from './services/function-list/compatibility';
|
||||
export { FUNCTION_LIST_CUBE } from './services/function-list/cube';
|
||||
export { FUNCTION_LIST_DATABASE } from './services/function-list/database';
|
||||
export { FUNCTION_LIST_DATE } from './services/function-list/date';
|
||||
export { FUNCTION_LIST_ENGINEERING } from './services/function-list/engineering';
|
||||
export { FUNCTION_LIST_FINANCIAL } from './services/function-list/financial';
|
||||
export { FUNCTION_LIST } from './services/function-list/function-list';
|
||||
export { FUNCTION_LIST_INFORMATION } from './services/function-list/information';
|
||||
export { FUNCTION_LIST_LOGICAL } from './services/function-list/logical';
|
||||
export { FUNCTION_LIST_LOOKUP } from './services/function-list/lookup';
|
||||
export { FUNCTION_LIST_MATH } from './services/function-list/math';
|
||||
export { FUNCTION_LIST_STATISTICAL } from './services/function-list/statistical';
|
||||
export { FUNCTION_LIST_TEXT } from './services/function-list/text';
|
||||
export { FUNCTION_LIST_UNIVER } from './services/function-list/univer';
|
||||
export { FUNCTION_LIST_WEB } from './services/function-list/web';
|
||||
export {
|
||||
IRegisterFunctionService,
|
||||
RegisterFunctionService,
|
||||
} from './services/register-function.service';
|
||||
export type {
|
||||
IRegisterAsyncFunction,
|
||||
IRegisterFunction,
|
||||
IRegisterFunctionParams,
|
||||
ISingleFunctionRegisterParams,
|
||||
IUnregisterFunctionParams,
|
||||
} from './services/register-function.service';
|
||||
export { generateParam, getFunctionName } from './services/utils';
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/ar-SA';
|
||||
import compatibility from './function-list/compatibility/ar-SA';
|
||||
import cube from './function-list/cube/ar-SA';
|
||||
import database from './function-list/database/ar-SA';
|
||||
import date from './function-list/date/ar-SA';
|
||||
import engineering from './function-list/engineering/ar-SA';
|
||||
import financial from './function-list/financial/ar-SA';
|
||||
import information from './function-list/information/ar-SA';
|
||||
import logical from './function-list/logical/ar-SA';
|
||||
import lookup from './function-list/lookup/ar-SA';
|
||||
import math from './function-list/math/ar-SA';
|
||||
import statistical from './function-list/statistical/ar-SA';
|
||||
import text from './function-list/text/ar-SA';
|
||||
import univer from './function-list/univer/ar-SA';
|
||||
import web from './function-list/web/ar-SA';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/ca-ES';
|
||||
import compatibility from './function-list/compatibility/ca-ES';
|
||||
import cube from './function-list/cube/ca-ES';
|
||||
import database from './function-list/database/ca-ES';
|
||||
import date from './function-list/date/ca-ES';
|
||||
import engineering from './function-list/engineering/ca-ES';
|
||||
import financial from './function-list/financial/ca-ES';
|
||||
import information from './function-list/information/ca-ES';
|
||||
import logical from './function-list/logical/ca-ES';
|
||||
import lookup from './function-list/lookup/ca-ES';
|
||||
import math from './function-list/math/ca-ES';
|
||||
import statistical from './function-list/statistical/ca-ES';
|
||||
import text from './function-list/text/ca-ES';
|
||||
import univer from './function-list/univer/ca-ES';
|
||||
import web from './function-list/web/ca-ES';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/de-DE';
|
||||
import compatibility from './function-list/compatibility/de-DE';
|
||||
import cube from './function-list/cube/de-DE';
|
||||
import database from './function-list/database/de-DE';
|
||||
import date from './function-list/date/de-DE';
|
||||
import engineering from './function-list/engineering/de-DE';
|
||||
import financial from './function-list/financial/de-DE';
|
||||
import information from './function-list/information/de-DE';
|
||||
import logical from './function-list/logical/de-DE';
|
||||
import lookup from './function-list/lookup/de-DE';
|
||||
import math from './function-list/math/de-DE';
|
||||
import statistical from './function-list/statistical/de-DE';
|
||||
import text from './function-list/text/de-DE';
|
||||
import univer from './function-list/univer/de-DE';
|
||||
import web from './function-list/web/de-DE';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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 array from './function-list/array/en-US';
|
||||
import compatibility from './function-list/compatibility/en-US';
|
||||
import cube from './function-list/cube/en-US';
|
||||
import database from './function-list/database/en-US';
|
||||
import date from './function-list/date/en-US';
|
||||
import engineering from './function-list/engineering/en-US';
|
||||
import financial from './function-list/financial/en-US';
|
||||
import information from './function-list/information/en-US';
|
||||
import logical from './function-list/logical/en-US';
|
||||
import lookup from './function-list/lookup/en-US';
|
||||
import math from './function-list/math/en-US';
|
||||
import statistical from './function-list/statistical/en-US';
|
||||
import text from './function-list/text/en-US';
|
||||
import univer from './function-list/univer/en-US';
|
||||
import web from './function-list/web/en-US';
|
||||
|
||||
const locale = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/es-ES';
|
||||
import compatibility from './function-list/compatibility/es-ES';
|
||||
import cube from './function-list/cube/es-ES';
|
||||
import database from './function-list/database/es-ES';
|
||||
import date from './function-list/date/es-ES';
|
||||
import engineering from './function-list/engineering/es-ES';
|
||||
import financial from './function-list/financial/es-ES';
|
||||
import information from './function-list/information/es-ES';
|
||||
import logical from './function-list/logical/es-ES';
|
||||
import lookup from './function-list/lookup/es-ES';
|
||||
import math from './function-list/math/es-ES';
|
||||
import statistical from './function-list/statistical/es-ES';
|
||||
import text from './function-list/text/es-ES';
|
||||
import univer from './function-list/univer/es-ES';
|
||||
import web from './function-list/web/es-ES';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/en-US';
|
||||
import compatibility from './function-list/compatibility/en-US';
|
||||
import cube from './function-list/cube/en-US';
|
||||
import database from './function-list/database/en-US';
|
||||
import date from './function-list/date/en-US';
|
||||
import engineering from './function-list/engineering/en-US';
|
||||
import financial from './function-list/financial/en-US';
|
||||
import information from './function-list/information/en-US';
|
||||
import logical from './function-list/logical/en-US';
|
||||
import lookup from './function-list/lookup/en-US';
|
||||
import math from './function-list/math/en-US';
|
||||
import statistical from './function-list/statistical/en-US';
|
||||
import text from './function-list/text/en-US';
|
||||
import univer from './function-list/univer/en-US';
|
||||
import web from './function-list/web/en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 enUS from './en-US';
|
||||
|
||||
import array from './function-list/array/fr-FR';
|
||||
import compatibility from './function-list/compatibility/fr-FR';
|
||||
import cube from './function-list/cube/fr-FR';
|
||||
import database from './function-list/database/fr-FR';
|
||||
import date from './function-list/date/fr-FR';
|
||||
import engineering from './function-list/engineering/fr-FR';
|
||||
import financial from './function-list/financial/fr-FR';
|
||||
import information from './function-list/information/fr-FR';
|
||||
import logical from './function-list/logical/fr-FR';
|
||||
import lookup from './function-list/lookup/fr-FR';
|
||||
import math from './function-list/math/fr-FR';
|
||||
import statistical from './function-list/statistical/fr-FR';
|
||||
import text from './function-list/text/fr-FR';
|
||||
import univer from './function-list/univer/fr-FR';
|
||||
import web from './function-list/web/fr-FR';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
formula: {
|
||||
functionList: {
|
||||
...array,
|
||||
...compatibility,
|
||||
...cube,
|
||||
...database,
|
||||
...date,
|
||||
...engineering,
|
||||
...financial,
|
||||
...information,
|
||||
...logical,
|
||||
...lookup,
|
||||
...math,
|
||||
...statistical,
|
||||
...text,
|
||||
...univer,
|
||||
...web,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user