mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
feat(sheet): support data binding in sheet (#4461)
This commit is contained in:
@@ -52,6 +52,7 @@
|
||||
"@univerjs/sheets-numfmt-ui": "workspace:*",
|
||||
"@univerjs/sheets-sort": "workspace:*",
|
||||
"@univerjs/sheets-sort-ui": "workspace:*",
|
||||
"@univerjs/sheets-source-binding": "workspace:*",
|
||||
"@univerjs/sheets-thread-comment": "workspace:*",
|
||||
"@univerjs/sheets-thread-comment-ui": "workspace:*",
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
|
||||
@@ -35,6 +35,7 @@ import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link';
|
||||
import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt';
|
||||
import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui';
|
||||
import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
|
||||
import { UniverSheetsBindingSourcePlugin } from '@univerjs/sheets-source-binding';
|
||||
import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment';
|
||||
import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
@@ -59,6 +60,7 @@ import '@univerjs/sheets-conditional-formatting/facade';
|
||||
import '@univerjs/sheets-find-replace/facade';
|
||||
import '@univerjs/sheets-drawing-ui/facade';
|
||||
import '@univerjs/sheets-zen-editor/facade';
|
||||
import '@univerjs/sheets-source-binding/facade';
|
||||
import '../global.css';
|
||||
import './styles';
|
||||
|
||||
@@ -118,6 +120,7 @@ univer.registerPlugin(UniverSheetsHyperLinkPlugin);
|
||||
univer.registerPlugin(UniverThreadCommentUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsThreadCommentPlugin);
|
||||
univer.registerPlugin(UniverSheetsThreadCommentUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsBindingSourcePlugin);
|
||||
|
||||
// If we are running in e2e platform, we should immediately register the debugger plugin.
|
||||
if (IS_E2E) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# @univerjs/sheets-source-binding
|
||||
|
||||
[](https://npmjs.org/packages/@univerjs/sheets-source-binding)
|
||||
[](https://img.shields.io/npm/l/@univerjs/sheets-source-binding)
|
||||
|
||||
## Introduction
|
||||
|
||||
> TODO: Introduction
|
||||
|
||||
## Usage
|
||||
|
||||
### Installation
|
||||
|
||||
```shell
|
||||
npm i @univerjs/sheets-source-binding
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-source-binding",
|
||||
"version": "0.5.3",
|
||||
"private": false,
|
||||
"description": "A library for connect and bind data from other sources to Univer Sheets",
|
||||
"author": "DreamNum <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/univer"
|
||||
},
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dream-num/univer/issues"
|
||||
},
|
||||
"keywords": [],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./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"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"build": "univer-cli build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/sheets": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"rxjs": "^7.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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 { ICellBindingNode, ICellBindingNodeParam } from '../types';
|
||||
import { Disposable, generateRandomId } from '@univerjs/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { SheetBindingModel } from '../model/binding-model';
|
||||
import { BindingSourceChangeTypeEnum } from '../types';
|
||||
|
||||
interface IBindingNodeInfo {
|
||||
unitId: string;
|
||||
subunitId: string;
|
||||
sourceId: string;
|
||||
nodeId: string;
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export class SheetsBindingManager extends Disposable {
|
||||
modelMap: Map<string, Map<string, SheetBindingModel>> = new Map();
|
||||
|
||||
private _cellBindInfoUpdate$ = new Subject<IBindingNodeInfo & { changeType: BindingSourceChangeTypeEnum;oldSourceId?: string }>();
|
||||
cellBindInfoUpdate$ = this._cellBindInfoUpdate$.asObservable();
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getBindingModelBySourceId(sourceId: string) {
|
||||
const rs: IBindingNodeInfo[] = [];
|
||||
this.modelMap.forEach((subMap, unitId) => {
|
||||
subMap.forEach((model, subunitId) => {
|
||||
const nodes = model.getBindingNodesBySourceId(sourceId);
|
||||
if (nodes) {
|
||||
for (const node of nodes) {
|
||||
rs.push({
|
||||
unitId,
|
||||
subunitId,
|
||||
sourceId,
|
||||
nodeId: node.nodeId!,
|
||||
row: node.row,
|
||||
column: node.column,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return rs;
|
||||
}
|
||||
|
||||
addModel(unitId: string, subunitId: string, model: SheetBindingModel): void {
|
||||
if (!this.modelMap.has(unitId)) {
|
||||
this.modelMap.set(unitId, new Map());
|
||||
}
|
||||
this.modelMap.get(unitId)?.set(subunitId, model);
|
||||
}
|
||||
|
||||
getModel(unitId: string, subunitId: string): SheetBindingModel | undefined {
|
||||
return this.modelMap.get(unitId)?.get(subunitId);
|
||||
}
|
||||
|
||||
setBindingNode(unitId: string, subunitId: string, node: ICellBindingNodeParam): void {
|
||||
let model = this.getModel(unitId, subunitId);
|
||||
if (!model) {
|
||||
model = new SheetBindingModel();
|
||||
this.addModel(unitId, subunitId, model);
|
||||
}
|
||||
if (!node.nodeId) {
|
||||
node.nodeId = generateRandomId();
|
||||
}
|
||||
const { row, column } = node;
|
||||
if (row === undefined || column === undefined) {
|
||||
throw new Error('row and column is required');
|
||||
}
|
||||
const oldNode = model.getBindingNode(row, column);
|
||||
model.setBindingNode(row, column, { ...node, row, column } as ICellBindingNode);
|
||||
this._cellBindInfoUpdate$.next({
|
||||
unitId,
|
||||
subunitId,
|
||||
sourceId: node.sourceId,
|
||||
nodeId: node.nodeId,
|
||||
row,
|
||||
column,
|
||||
changeType: oldNode ? BindingSourceChangeTypeEnum.Update : BindingSourceChangeTypeEnum.Add,
|
||||
oldSourceId: oldNode?.sourceId,
|
||||
});
|
||||
}
|
||||
|
||||
removeBindingNode(unitId: string, subunitId: string, row: number, column: number): void {
|
||||
const model = this.getModel(unitId, subunitId);
|
||||
if (model) {
|
||||
const node = model.getBindingNode(row, column);
|
||||
if (node) {
|
||||
model.removeBindingNode(row, column);
|
||||
this._cellBindInfoUpdate$.next({
|
||||
unitId,
|
||||
subunitId,
|
||||
sourceId: node.sourceId,
|
||||
nodeId: node.nodeId!,
|
||||
row,
|
||||
column,
|
||||
changeType: BindingSourceChangeTypeEnum.Remove,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBindingNode(unitId: string, subunitId: string, row: number, column: number): ICellBindingNode | undefined {
|
||||
const model = this.getModel(unitId, subunitId);
|
||||
if (model) {
|
||||
return model.getBindingNode(row, column);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
createModel(unitId: string, subunitId: string, json?: any): SheetBindingModel {
|
||||
const model = new SheetBindingModel(json);
|
||||
this.addModel(unitId, subunitId, model);
|
||||
return model;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.modelMap.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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 { ISourceEvent } from '../types';
|
||||
import { Disposable, generateRandomId } from '@univerjs/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ListSourceModel, ObjectSourceModel, SourceModelBase } from '../model/source-model';
|
||||
import { BindingSourceChangeTypeEnum, DataBindingNodeTypeEnum } from '../types';
|
||||
|
||||
export class SheetsSourceManager extends Disposable {
|
||||
sourceMap: Map<string, Map<string, SourceModelBase> > = new Map();
|
||||
private _sourceDataUpdate$ = new Subject<ISourceEvent>();
|
||||
sourceDataUpdate$ = this._sourceDataUpdate$.asObservable();
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private _ensureUnitMap(unitId: string): Map<string, SourceModelBase> {
|
||||
let unit = this.sourceMap.get(unitId);
|
||||
if (!unit) {
|
||||
unit = new Map();
|
||||
this.sourceMap.set(unitId, unit);
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
private _getUnitMap(unitId: string): Map<string, SourceModelBase> | undefined {
|
||||
return this.sourceMap.get(unitId);
|
||||
}
|
||||
|
||||
public getSource(unitId: string, id: string): SourceModelBase | undefined {
|
||||
const unitMap = this._getUnitMap(unitId);
|
||||
return unitMap?.get(id);
|
||||
}
|
||||
|
||||
createSource(unitId: string, type: DataBindingNodeTypeEnum, isListObject?: boolean, id?: string): SourceModelBase {
|
||||
const sourceId = id === undefined ? generateRandomId() : id;
|
||||
let source;
|
||||
switch (type) {
|
||||
case DataBindingNodeTypeEnum.List:
|
||||
source = new ListSourceModel(sourceId, isListObject);
|
||||
break;
|
||||
case DataBindingNodeTypeEnum.Object:
|
||||
source = new ObjectSourceModel(sourceId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid source type: ${type}`);
|
||||
}
|
||||
const unitMap = this._ensureUnitMap(unitId);
|
||||
unitMap.set(sourceId, source);
|
||||
return source;
|
||||
}
|
||||
|
||||
updateSourceData(unitId: string, idOrInstance: string | SourceModelBase, data: any): void {
|
||||
const unitMap = this._getUnitMap(unitId);
|
||||
const sourceId = idOrInstance instanceof SourceModelBase ? idOrInstance.getId() : idOrInstance;
|
||||
const source = unitMap?.get(sourceId);
|
||||
if (source) {
|
||||
source.setSourceData(data);
|
||||
this._sourceDataUpdate$.next({ ...source.getSourceInfo(), unitId, changeType: BindingSourceChangeTypeEnum.Add });
|
||||
} else {
|
||||
throw new Error(`Source not found: ${sourceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
removeSource(unitId: string, id: string): void {
|
||||
const unitMap = this._getUnitMap(unitId);
|
||||
const source = unitMap?.get(id);
|
||||
if (source) {
|
||||
unitMap?.delete(id);
|
||||
this._sourceDataUpdate$.next({ ...source.getSourceInfo(), unitId, changeType: BindingSourceChangeTypeEnum.Remove });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 { FEnum } from '@univerjs/core';
|
||||
import { BindModeEnum, DataBindingNodeTypeEnum } from '@univerjs/sheets-source-binding';
|
||||
|
||||
export interface ISourceBindingEnumMixin {
|
||||
/**
|
||||
* Represents the type of data binding node.{@link DataBindingNodeTypeEnum}
|
||||
*/
|
||||
DataBindingNodeTypeEnum: typeof DataBindingNodeTypeEnum;
|
||||
/**
|
||||
* Represents the type of data binding node. {@link BindModeEnum}
|
||||
*/
|
||||
BindModeEnum: typeof BindModeEnum;
|
||||
}
|
||||
class FSourceBindingEnum extends FEnum implements ISourceBindingEnumMixin {
|
||||
override get DataBindingNodeTypeEnum(): typeof DataBindingNodeTypeEnum {
|
||||
return DataBindingNodeTypeEnum;
|
||||
}
|
||||
|
||||
override get BindModeEnum(): typeof BindModeEnum {
|
||||
return BindModeEnum;
|
||||
}
|
||||
}
|
||||
|
||||
FEnum.extend(FSourceBindingEnum);
|
||||
|
||||
declare module '@univerjs/core' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FEnum extends ISourceBindingEnumMixin { }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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 { DataBindingNodeTypeEnum, SourceModelBase } from '@univerjs/sheets-source-binding';
|
||||
import { SheetsSourceBindService } from '@univerjs/sheets-source-binding';
|
||||
import { FWorkbook } from '@univerjs/sheets/facade';
|
||||
|
||||
export interface IFWorkbookSourceBindingMixin {
|
||||
/**
|
||||
* Create a source model with the specified type.
|
||||
* @param {DataBindingNodeTypeEnum} type The source type.
|
||||
* @param {boolean} [isListObject] Whether the source is a list object.
|
||||
* @returns {SourceModelBase} The source data of sheet.
|
||||
*/
|
||||
createSource(type: DataBindingNodeTypeEnum, isListObject?: boolean): SourceModelBase;
|
||||
/**
|
||||
* Switch to path mode.In this mode, the path will show in cell.
|
||||
*/
|
||||
usePathMode(): void;
|
||||
/**
|
||||
* Switch to value mode.This is the default mode. In this mode, the cell value will fulfill by source values.
|
||||
*/
|
||||
useValueMode(): void;
|
||||
/**
|
||||
* Get the source model by the specified source id.
|
||||
* @param {string} sourceId The source id.
|
||||
*/
|
||||
getSource(sourceId: string): SourceModelBase | undefined;
|
||||
}
|
||||
|
||||
export class FWorkbookSourceBinding extends FWorkbook implements IFWorkbookSourceBindingMixin {
|
||||
override createSource(type: DataBindingNodeTypeEnum, isListObject?: boolean): SourceModelBase {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
return sheetsSourceBindService.createSource(this.getId(), type, isListObject);
|
||||
}
|
||||
|
||||
override getSource(sourceId: string): SourceModelBase | undefined {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
return sheetsSourceBindService.getSource(this.getId(), sourceId);
|
||||
}
|
||||
|
||||
override usePathMode(): void {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
sheetsSourceBindService.usePathMode();
|
||||
}
|
||||
|
||||
override useValueMode(): void {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
sheetsSourceBindService.useValueMode();
|
||||
}
|
||||
}
|
||||
|
||||
FWorkbook.extend(FWorkbookSourceBinding);
|
||||
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorkbook extends IFWorkbookSourceBindingMixin { }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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 { ICellBindingNode, ICellBindingNodeParam } from '@univerjs/sheets-source-binding';
|
||||
import { SheetsSourceBindService } from '@univerjs/sheets-source-binding';
|
||||
import { FWorksheet } from '@univerjs/sheets/facade';
|
||||
|
||||
export interface IFworksheetSourceBindingMixin {
|
||||
/**
|
||||
* Set the binding node for the specified cell.
|
||||
* @param bindingNode The binding node to set. {@link ICellBindingNodeParam}
|
||||
*/
|
||||
setBindingNode(bindingNode: ICellBindingNodeParam): void;
|
||||
/**
|
||||
* Remove the binding node for the specified cell.
|
||||
* @param {number} row The row index of the cell.
|
||||
* @param {number} column The column index of the cell.
|
||||
*/
|
||||
removeBindingNode(row: number, column: number): void;
|
||||
/**
|
||||
* Get the binding node for the specified cell.
|
||||
* @param {number} row The row index of the cell.
|
||||
* @param {number} column The column index of the cell.
|
||||
* @returns The binding node of the cell. {@link ICellBindingNode} or undefined if not found.
|
||||
*/
|
||||
getBindingNode(row: number, column: number): ICellBindingNode | undefined;
|
||||
}
|
||||
|
||||
export class FWorksheetSourceBinding extends FWorksheet implements IFworksheetSourceBindingMixin {
|
||||
override setBindingNode(bindingNode: ICellBindingNodeParam): void {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
const unitId = this._workbook.getUnitId();
|
||||
sheetsSourceBindService.setBindingNode(unitId, this.getSheetId(), bindingNode);
|
||||
}
|
||||
|
||||
override removeBindingNode(row: number, column: number): void {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
const unitId = this._workbook.getUnitId();
|
||||
sheetsSourceBindService.removeBindingNode(unitId, this.getSheetId(), row, column);
|
||||
}
|
||||
|
||||
override getBindingNode(row: number, column: number): ICellBindingNode | undefined {
|
||||
const injector = this._injector;
|
||||
const sheetsSourceBindService = injector.get(SheetsSourceBindService);
|
||||
const unitId = this._workbook.getUnitId();
|
||||
return sheetsSourceBindService.getBindingNode(unitId, this.getSheetId(), row, column);
|
||||
}
|
||||
}
|
||||
|
||||
FWorksheet.extend(FWorksheetSourceBinding);
|
||||
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorksheet extends IFworksheetSourceBindingMixin { }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 './f-enum';
|
||||
import './f-workbook';
|
||||
import './f-worksheet';
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { SheetBindingModel as BindingModel } from './model/binding-model';
|
||||
export { SourceModelBase } from './model/source-model';
|
||||
export { UniverSheetsBindingSourcePlugin } from './plugin';
|
||||
export { BindModeEnum, DataBindingNodeTypeEnum } from './types';
|
||||
export { SheetsSourceBindService } from './services/source-binding-service';
|
||||
export type { ICellBindingNode, ICellBindingNodeParam } from './types';
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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 { ICellBindingNode, IDataBindingModelMatrix } from '../types';
|
||||
|
||||
export class SheetBindingModel {
|
||||
private _matrix: IDataBindingModelMatrix = {};
|
||||
|
||||
private _nodeMap: Map<string, ICellBindingNode> = new Map();
|
||||
|
||||
private _sourceIdMap: Map<string, string[]> = new Map();
|
||||
|
||||
constructor(json?: any) {
|
||||
if (json) {
|
||||
this._init(json);
|
||||
}
|
||||
}
|
||||
|
||||
_init(json: any): void {
|
||||
this.fromJSON();
|
||||
}
|
||||
|
||||
getBindingNodesBySourceId(sourceId: string): ICellBindingNode[] | undefined {
|
||||
const nodeIds = this._sourceIdMap.get(sourceId);
|
||||
if (nodeIds) {
|
||||
return nodeIds.map((nodeId) => this._nodeMap.get(nodeId)) as ICellBindingNode[];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public setBindingNode(row: number, column: number, node: ICellBindingNode): void {
|
||||
if (!this._matrix[row]) {
|
||||
this._matrix[row] = {};
|
||||
}
|
||||
|
||||
if (!this._matrix[row][column]) {
|
||||
this._matrix[row][column] = node;
|
||||
}
|
||||
this._nodeMap.set(node.nodeId, node);
|
||||
// update sourceId map, keep the sourceId and nodeId mapping
|
||||
const nodeIds = this._sourceIdMap.get(node.sourceId);
|
||||
if (nodeIds) {
|
||||
nodeIds.push(node.nodeId);
|
||||
} else {
|
||||
this._sourceIdMap.set(node.sourceId, [node.nodeId]);
|
||||
}
|
||||
}
|
||||
|
||||
public getBindingNode(row: number, column: number): ICellBindingNode {
|
||||
return this._matrix[row]?.[column];
|
||||
}
|
||||
|
||||
public removeBindingNode(row: number, column: number): void {
|
||||
const node = this._matrix[row]?.[column];
|
||||
if (node) {
|
||||
// @ts-ignore
|
||||
this._matrix[row][column] = undefined;
|
||||
this._nodeMap.delete(node.nodeId);
|
||||
const nodeIds = this._sourceIdMap.get(node.sourceId);
|
||||
if (nodeIds) {
|
||||
const index = nodeIds.indexOf(node.nodeId);
|
||||
if (index >= 0) {
|
||||
nodeIds.splice(index, 1);
|
||||
}
|
||||
if (nodeIds.length === 0) {
|
||||
this._sourceIdMap.delete(node.sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getBindingNodeById(nodeId: string): ICellBindingNode | undefined {
|
||||
return this._nodeMap.get(nodeId);
|
||||
}
|
||||
|
||||
fromJSON(): void {
|
||||
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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 { ICellBindingNode, IListDataBindingNode, IListSourceData, IListSourceInfo, IObjectSourceInfo } from '../types';
|
||||
import { DataBindingNodeTypeEnum } from '../types';
|
||||
|
||||
export abstract class SourceModelBase {
|
||||
protected _data: any;
|
||||
readonly id: string;
|
||||
private _hasData: boolean = false;
|
||||
readonly type: DataBindingNodeTypeEnum;
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getType(): DataBindingNodeTypeEnum {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
hasData(): boolean {
|
||||
return this._hasData;
|
||||
}
|
||||
|
||||
setSourceData(data: any): void {
|
||||
this._data = data;
|
||||
this._hasData = true;
|
||||
}
|
||||
|
||||
abstract getSourceInfo(): any;
|
||||
|
||||
abstract getData(node: ICellBindingNode, row: number, col: number): string | number | boolean | null;
|
||||
}
|
||||
|
||||
export class ListSourceModel extends SourceModelBase {
|
||||
override readonly type = DataBindingNodeTypeEnum.List;
|
||||
private _isListObject: boolean;
|
||||
private _fieldIndexMap: Map<string, number> = new Map();
|
||||
protected override _data: IListSourceData = { fields: [], records: [] };
|
||||
constructor(id: string, isListObject?: boolean) {
|
||||
super(id);
|
||||
this._isListObject = isListObject ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the list object mode. The default value is true.
|
||||
* In the list object mode, the records is an array of objects. Such as [{name: 'Tom', age: 20}, {name: 'Jerry', age: 18}].
|
||||
* In the list array mode, the records is an array of arrays. Such as [['Tom', 20], ['Jerry', 18]].
|
||||
*/
|
||||
toggleListObject(isListObject: boolean) {
|
||||
this._isListObject = isListObject;
|
||||
}
|
||||
|
||||
getData(node: IListDataBindingNode, row: number): string | number | boolean | null {
|
||||
const { path, row: baseRow } = node;
|
||||
const colIndex = this._fieldIndexMap.get(path)!;
|
||||
const rowIndex = row - baseRow;
|
||||
if (rowIndex === 0) {
|
||||
return this._data.fields[colIndex];
|
||||
}
|
||||
if (this._isListObject) {
|
||||
return (this._data.records as Record<string | number, any>)[rowIndex - 1][path];
|
||||
}
|
||||
return this._data.records[rowIndex - 1][colIndex];
|
||||
}
|
||||
|
||||
override setSourceData(data: IListSourceData): void {
|
||||
super.setSourceData(data);
|
||||
const { fields } = data;
|
||||
this._fieldIndexMap.clear();
|
||||
fields.forEach((field, index) => {
|
||||
this._fieldIndexMap.set(field, index);
|
||||
});
|
||||
}
|
||||
|
||||
override getSourceInfo(): IListSourceInfo {
|
||||
return {
|
||||
sourceId: this.id,
|
||||
sourceType: this.type,
|
||||
fields: this._data.fields,
|
||||
recordCount: this._data.records.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ObjectSourceModel extends SourceModelBase {
|
||||
override readonly type = DataBindingNodeTypeEnum.Object;
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
getData(node: ICellBindingNode): string | number | boolean | null {
|
||||
const path = node.path;
|
||||
const paths = path.split('.');
|
||||
let data = this._data;
|
||||
for (const p of paths) {
|
||||
data = data[p];
|
||||
if (data === undefined) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
override getSourceInfo(): IObjectSourceInfo {
|
||||
return {
|
||||
sourceId: this.id,
|
||||
sourceType: DataBindingNodeTypeEnum.Object,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 { Dependency } from '@univerjs/core';
|
||||
import { IConfigService, Inject, Injector, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsBindingManager } from './controllers/binding-manager';
|
||||
import { SheetsSourceManager } from './controllers/source-manager';
|
||||
import { SheetsSourceBindService } from './services/source-binding-service';
|
||||
|
||||
interface IUniverSheetsBindingSourceConfig {}
|
||||
|
||||
export class UniverSheetsBindingSourcePlugin extends Plugin {
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
static override pluginName = 'SHEET_BINDING_SOURCE_PLUGIN';
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsBindingSourceConfig> = {},
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[SheetsBindingManager],
|
||||
[SheetsSourceManager],
|
||||
[SheetsSourceBindService],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsBindingManager],
|
||||
[SheetsSourceManager],
|
||||
[SheetsSourceBindService],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* 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 { ICellBindingNodeParam, IListSourceInfo } from '../types';
|
||||
import { Disposable, Inject, InterceptorEffectEnum, RTree } from '@univerjs/core';
|
||||
import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets';
|
||||
|
||||
import { SheetsBindingManager } from '../controllers/binding-manager';
|
||||
import { SheetsSourceManager } from '../controllers/source-manager';
|
||||
import { BindingSourceChangeTypeEnum, BindModeEnum, DataBindingNodeTypeEnum } from '../types';
|
||||
|
||||
export class SheetsSourceBindService extends Disposable {
|
||||
private _bindingModel: BindModeEnum = BindModeEnum.Value;
|
||||
private _bindModelRTreeCollection = new Map<string, RTree>();
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(SheetsBindingManager) private _sheetsBindingManager: SheetsBindingManager,
|
||||
@Inject(SheetsSourceManager) private _sheetsSourceManager: SheetsSourceManager
|
||||
) {
|
||||
super();
|
||||
this._registerInterceptor();
|
||||
this._registerSourceChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the binding model to path mode, in this mode, the binding path will show in the cell.
|
||||
*/
|
||||
usePathMode() {
|
||||
this._bindingModel = BindModeEnum.Path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the binding model to value mode, in this mode, the value of source will show in the cell.
|
||||
*/
|
||||
useValueMode() {
|
||||
this._bindingModel = BindModeEnum.Value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current binding model.
|
||||
* @returns the current binding model
|
||||
*/
|
||||
getBindingModel() {
|
||||
return this._bindingModel;
|
||||
}
|
||||
|
||||
createBindModel(unitId: string, subUnitId: string) {
|
||||
return this._sheetsBindingManager.createModel(unitId, subUnitId);
|
||||
}
|
||||
|
||||
setBindingNode(unitId: string, subUnitId: string, node: ICellBindingNodeParam) {
|
||||
this._sheetsBindingManager.setBindingNode(unitId, subUnitId, node);
|
||||
}
|
||||
|
||||
removeBindingNode(unitId: string, subUnitId: string, row: number, column: number) {
|
||||
this._sheetsBindingManager.removeBindingNode(unitId, subUnitId, row, column);
|
||||
}
|
||||
|
||||
getBindingNode(unitId: string, subUnitId: string, row: number, column: number) {
|
||||
return this._sheetsBindingManager.getBindingNode(unitId, subUnitId, row, column);
|
||||
}
|
||||
|
||||
getSource(unitId: string, id: string) {
|
||||
return this._sheetsSourceManager.getSource(unitId, id);
|
||||
}
|
||||
|
||||
createSource(unitId: string, type: DataBindingNodeTypeEnum, isListObject?: boolean, id?: string) {
|
||||
return this._sheetsSourceManager.createSource(unitId, type, isListObject, id);
|
||||
}
|
||||
|
||||
private _ensureRTreeCollection(unitId: string) {
|
||||
if (!this._bindModelRTreeCollection.has(unitId)) {
|
||||
this._bindModelRTreeCollection.set(unitId, new RTree());
|
||||
}
|
||||
return this._bindModelRTreeCollection.get(unitId) as RTree;
|
||||
}
|
||||
|
||||
private _getRTeeCollection(unitId: string) {
|
||||
return this._bindModelRTreeCollection.get(unitId);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _registerSourceChange() {
|
||||
// Update the RTree collection when the source data updated
|
||||
this.disposeWithMe(this._sheetsSourceManager.sourceDataUpdate$.subscribe((sourceInfo) => {
|
||||
const { sourceId, sourceType, unitId: sourceUnitId, changeType } = sourceInfo;
|
||||
|
||||
if (sourceType === DataBindingNodeTypeEnum.List) {
|
||||
// Remove the old data
|
||||
if (changeType === BindingSourceChangeTypeEnum.Remove) {
|
||||
const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId);
|
||||
const recordCount = sourceInfo.recordCount;
|
||||
for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) {
|
||||
const rTreeCollection = this._getRTeeCollection(sourceUnitId);
|
||||
if (rTreeCollection) {
|
||||
const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column };
|
||||
rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (changeType === BindingSourceChangeTypeEnum.Update) {
|
||||
const oldRecordCount = sourceInfo.oldRecordCount!;
|
||||
const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId);
|
||||
for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) {
|
||||
const rTreeCollection = this._getRTeeCollection(sourceUnitId);
|
||||
if (rTreeCollection) {
|
||||
const oldRange = { startRow: row, startColumn: column, endRow: row + oldRecordCount, endColumn: column };
|
||||
const range = { startRow: row, startColumn: column, endRow: row + sourceInfo.recordCount, endColumn: column };
|
||||
rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range: oldRange });
|
||||
rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the new data
|
||||
const source = this._sheetsSourceManager.getSource(sourceUnitId, sourceId);
|
||||
if (source && source.hasData()) {
|
||||
const sourceInfo = source.getSourceInfo() as IListSourceInfo;
|
||||
const recordCount = sourceInfo.recordCount;
|
||||
const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId);
|
||||
|
||||
for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) {
|
||||
const rTreeCollection = this._ensureRTreeCollection(unitId);
|
||||
const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column };
|
||||
rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Update the RTree collection when the binding node updated
|
||||
this.disposeWithMe(this._sheetsBindingManager.cellBindInfoUpdate$.subscribe((nodeInfo) => {
|
||||
const { unitId, subunitId, sourceId, nodeId, row, column, changeType } = nodeInfo;
|
||||
const rTreeCollection = this._ensureRTreeCollection(unitId);
|
||||
const source = this._sheetsSourceManager.getSource(unitId, sourceId);
|
||||
|
||||
if (source && source.hasData()) {
|
||||
const sourceInfo = source.getSourceInfo() as IListSourceInfo;
|
||||
if (sourceInfo.sourceType === DataBindingNodeTypeEnum.List) {
|
||||
const recordCount = sourceInfo.recordCount;
|
||||
const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column };
|
||||
if (changeType === BindingSourceChangeTypeEnum.Add) {
|
||||
rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
} else if (changeType === BindingSourceChangeTypeEnum.Remove) {
|
||||
rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
} else if (changeType === BindingSourceChangeTypeEnum.Update) {
|
||||
const oldSourceId = nodeInfo.oldSourceId!;
|
||||
// remove the old range
|
||||
const oldSource = this._sheetsSourceManager.getSource(unitId, oldSourceId);
|
||||
if (oldSource && oldSource.hasData()) {
|
||||
const oldSourceInfo = oldSource.getSourceInfo() as IListSourceInfo;
|
||||
const oldRecordCount = oldSourceInfo.recordCount;
|
||||
const oldRange = { startRow: row, startColumn: column, endRow: row + oldRecordCount, endColumn: column };
|
||||
rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range: oldRange });
|
||||
}
|
||||
rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range });
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _getPathModeCellValue(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
const model = this._sheetsBindingManager.getModel(unitId, subUnitId);
|
||||
const node = model?.getBindingNode(row, col);
|
||||
if (node) {
|
||||
const nodeType = node.type;
|
||||
if (nodeType === DataBindingNodeTypeEnum.List) {
|
||||
return {
|
||||
v: `#{${node.path}}`,
|
||||
s: { cl: { rgb: 'blue' } },
|
||||
};
|
||||
} else if (nodeType === DataBindingNodeTypeEnum.Object) {
|
||||
return {
|
||||
v: `[${node.path}]`,
|
||||
s: { cl: { rgb: 'blue' } },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getValueModeCellValue(unitId: string, subUnitId: string, row: number, col: number) {
|
||||
const model = this._sheetsBindingManager.getModel(unitId, subUnitId);
|
||||
// object mode
|
||||
if (model) {
|
||||
const node = model.getBindingNode(row, col);
|
||||
if (node) {
|
||||
const { sourceId } = node;
|
||||
const source = this._sheetsSourceManager.getSource(unitId, sourceId);
|
||||
if (source && source.hasData()) {
|
||||
return {
|
||||
v: source?.getData(node, row, col) || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
const rTreeCollection = this._getRTeeCollection(unitId);
|
||||
if (model && rTreeCollection) {
|
||||
const range = { startRow: row, startColumn: col, endRow: row, endColumn: col };
|
||||
const nodeIds = Array.from(rTreeCollection.bulkSearch([{ unitId, sheetId: subUnitId, range }]));
|
||||
if (nodeIds.length > 0) {
|
||||
const node = model.getBindingNodeById(nodeIds[0] as string);
|
||||
if (node) {
|
||||
const { sourceId } = node;
|
||||
const source = this._sheetsSourceManager.getSource(unitId, sourceId);
|
||||
if (source && source.hasData()) {
|
||||
return {
|
||||
v: source?.getData(node, row, col) || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _registerInterceptor() {
|
||||
this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, {
|
||||
effect: InterceptorEffectEnum.Value,
|
||||
handler: (cell, context, next) => {
|
||||
const { row, col, unitId, subUnitId } = context;
|
||||
let value = null;
|
||||
if (this._bindingModel === BindModeEnum.Path) {
|
||||
value = this._getPathModeCellValue(unitId, subUnitId, row, col);
|
||||
} else {
|
||||
value = this._getValueModeCellValue(unitId, subUnitId, row, col);
|
||||
}
|
||||
if (value !== null) {
|
||||
return next({ ...cell, ...value });
|
||||
}
|
||||
|
||||
return next(cell);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the type of data binding node.Which config the array or object like data source or path.
|
||||
*/
|
||||
export enum DataBindingNodeTypeEnum {
|
||||
/**
|
||||
* The array like data source.
|
||||
*/
|
||||
List = 'list',
|
||||
|
||||
// Cell = 'cell',
|
||||
/**
|
||||
* The object like data source.
|
||||
*/
|
||||
Object = 'object',
|
||||
}
|
||||
|
||||
export enum ListDataBindingNodeDirectionEnum {
|
||||
Horizontal = 'horizontal',
|
||||
Vertical = 'vertical',
|
||||
}
|
||||
/**
|
||||
* Represents the type of data binding node.Which config show Binding path or value.
|
||||
*/
|
||||
export enum BindModeEnum {
|
||||
/**
|
||||
* The path mode, show the binding path.
|
||||
*/
|
||||
Path = 'path',
|
||||
/**
|
||||
* The value mode, show the binding value.
|
||||
*/
|
||||
Value = 'value',
|
||||
}
|
||||
|
||||
export type sourceDataType = string | number | boolean | object | null;
|
||||
|
||||
export interface IDataSource {
|
||||
id: string;
|
||||
getData(row: number, column: number): sourceDataType;
|
||||
|
||||
}
|
||||
|
||||
export interface IListDataSource extends IDataSource {
|
||||
getLength(): number;
|
||||
getColumns(): string[];
|
||||
}
|
||||
export interface IDataBindingModelMatrix {
|
||||
[row: number]: {
|
||||
[column: number]: ICellBindingNode;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDataBindingModel {
|
||||
themeName?: string;
|
||||
matrix: IDataBindingModelMatrix;
|
||||
setBindingNode(row: number, column: number, node: ICellBindingNode): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The binding node of cell, which config the source id, path, row, column.
|
||||
*/
|
||||
export interface ICellBindingNodeParam {
|
||||
/**
|
||||
* The binding node type, the node type should be same as provide source type.
|
||||
*/
|
||||
type: DataBindingNodeTypeEnum;
|
||||
/**
|
||||
* The path of the binding node, the path should be same as provide source path.
|
||||
* @example
|
||||
* for object type : the source is :{user: {name :'Tom'}}, we can set path 'user.name' to represent the Tom.
|
||||
* for list type : the source is :{fields:['name', 'age'], records:[['Tom', 18],['Jerry', 20]]}, we can set path 'name' to represent the all names in data.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* The source id of the binding node, the source id should be same as provide source id.
|
||||
*/
|
||||
sourceId: string;
|
||||
/**
|
||||
* The target row of the binding node.
|
||||
*/
|
||||
row: number;
|
||||
/**
|
||||
* The target column of the binding node.
|
||||
*/
|
||||
column: number;
|
||||
nodeId?: string; // optional in ICellBindingNodeParam
|
||||
}
|
||||
|
||||
export interface ICellBindingNode extends ICellBindingNodeParam {
|
||||
/**
|
||||
* The node id of ICellBindingNode, if not provide, will generate a random id.
|
||||
*/
|
||||
nodeId: string;
|
||||
}
|
||||
export interface IListDataBindingNode extends ICellBindingNode {
|
||||
type: DataBindingNodeTypeEnum.List;
|
||||
limit?: number;
|
||||
direction?: ListDataBindingNodeDirectionEnum;
|
||||
range?: { startRow: number; startColumn: number; endRow: number; endColumn: number };
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export interface IDataBindingService {
|
||||
/**
|
||||
* A flag to control show path or show value
|
||||
*/
|
||||
isBindingMode: boolean;
|
||||
/**
|
||||
* Change the binding mode
|
||||
* @param {BindModeEnum} mode Update the binding mode
|
||||
*/
|
||||
setBindingMode(mode: BindModeEnum): void;
|
||||
|
||||
}
|
||||
|
||||
export interface IListSourceInfo {
|
||||
sourceId: string;
|
||||
sourceType: DataBindingNodeTypeEnum.List;
|
||||
fields: string[];
|
||||
recordCount: number;
|
||||
|
||||
// range: null|{startRow:number, startColumn:number, endRow:number, endColumn:number};
|
||||
}
|
||||
|
||||
export interface IObjectSourceInfo {
|
||||
sourceId: string;
|
||||
sourceType: DataBindingNodeTypeEnum.Object;
|
||||
|
||||
}
|
||||
|
||||
export type ISourceEvent = (IListSourceInfo | IObjectSourceInfo) & {
|
||||
unitId: string;
|
||||
changeType: BindingSourceChangeTypeEnum;
|
||||
oldRecordCount?: number;
|
||||
};
|
||||
|
||||
export interface IListSourceData {
|
||||
fields: string[];
|
||||
records: any[][] | Record<string | number, any>[];
|
||||
}
|
||||
|
||||
export enum BindingSourceChangeTypeEnum {
|
||||
Add = 'add',
|
||||
Remove = 'remove',
|
||||
Update = 'update',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"references": [{ "path": "./tsconfig.node.json" }],
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"include": ["vitest.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Generated
+30
-7
@@ -376,6 +376,9 @@ importers:
|
||||
'@univerjs/sheets-sort-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/sheets-sort-ui
|
||||
'@univerjs/sheets-source-binding':
|
||||
specifier: workspace:*
|
||||
version: link:../packages-experimental/sheets-source-binding
|
||||
'@univerjs/sheets-thread-comment':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/sheets-thread-comment
|
||||
@@ -749,6 +752,31 @@ importers:
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/node@22.10.5)(happy-dom@15.0.0)(jsdom@24.1.1)(less@4.2.1)(sass@1.77.5)(terser@5.37.0)
|
||||
|
||||
packages-experimental/sheets-source-binding:
|
||||
dependencies:
|
||||
'@univerjs/core':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core
|
||||
'@univerjs/sheets':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sheets
|
||||
devDependencies:
|
||||
'@univerjs-infra/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../common/shared
|
||||
rxjs:
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
typescript:
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
vite:
|
||||
specifier: ^6.0.7
|
||||
version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.1)(sass@1.77.5)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)
|
||||
vitest:
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/node@22.10.5)(happy-dom@15.0.0)(jsdom@24.1.1)(less@4.2.1)(sass@1.77.5)(terser@5.37.0)
|
||||
|
||||
packages-experimental/uni-docs-ui:
|
||||
dependencies:
|
||||
'@univerjs/core':
|
||||
@@ -10423,9 +10451,6 @@ packages:
|
||||
tslib@2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
|
||||
tslib@2.6.3:
|
||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -18188,7 +18213,7 @@ snapshots:
|
||||
|
||||
regexp-util@2.0.0:
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
tslib: 2.8.1
|
||||
|
||||
regexp.prototype.flags@1.5.3:
|
||||
dependencies:
|
||||
@@ -18381,7 +18406,7 @@ snapshots:
|
||||
|
||||
rxjs@7.8.1:
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
tslib: 2.8.1
|
||||
|
||||
safe-array-concat@1.1.2:
|
||||
dependencies:
|
||||
@@ -19004,8 +19029,6 @@ snapshots:
|
||||
|
||||
tslib@2.5.0: {}
|
||||
|
||||
tslib@2.6.3: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsx@4.19.2:
|
||||
|
||||
Reference in New Issue
Block a user