diff --git a/packages/sheets/src/facade/f-range.ts b/packages/sheets/src/facade/f-range.ts index 749fa56baec..7d98c72947e 100644 --- a/packages/sheets/src/facade/f-range.ts +++ b/packages/sheets/src/facade/f-range.ts @@ -766,6 +766,6 @@ export class FRange extends FBase { unitId: this.getUnitId(), subUnitId: this._worksheet.getSheetId(), }; - this._injector.get(SheetRangeThemeService).registerRangeThemeStyles(themeName, rangeInfo); + this._injector.get(SheetRangeThemeService).registerRangeThemeStyle(themeName, rangeInfo); } } diff --git a/packages/sheets/src/index.ts b/packages/sheets/src/index.ts index 3eb7f296bef..67a32201462 100644 --- a/packages/sheets/src/index.ts +++ b/packages/sheets/src/index.ts @@ -45,8 +45,7 @@ export { createTopMatrixFromMatrix, createTopMatrixFromRanges, findAllRectangle, export { type IUniverSheetsConfig } from './controllers/config.schema'; export { MAX_CELL_PER_SHEET_KEY } from './controllers/config/config'; export { BorderStyleManagerService, type IBorderInfo } from './services/border-style-manager.service'; -export { type IRangeThemeStyleItem, RangeThemeStyle } from './services/theme-range/range-theme-util'; -export { SheetRangeThemeService } from './services/theme-range/range-theme-service'; +export { SheetRangeThemeService } from './services/range-theme-service'; export * from './services/permission/permission-point'; export { WorksheetPermissionService } from './services/permission/worksheet-permission/worksheet-permission.service'; export { WorkbookPermissionService } from './services/permission/workbook-permission/workbook-permission.service'; diff --git a/packages/sheets/src/model/range-theme-model.ts b/packages/sheets/src/model/range-theme-model.ts new file mode 100644 index 00000000000..d37630b10a0 --- /dev/null +++ b/packages/sheets/src/model/range-theme-model.ts @@ -0,0 +1,223 @@ +/** + * 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 { ICellDataForSheetInterceptor, IRange, Nullable } from '@univerjs/core'; +import type { IRangeThemeStyleItem } from './range-theme-util'; +import { Disposable, generateRandomId, Inject, InterceptorEffectEnum, IResourceManagerService, RTree, UniverInstanceType } from '@univerjs/core'; +import { INTERCEPTOR_POINT } from '../services/sheet-interceptor/interceptor-const'; +import { SheetInterceptorService } from '../services/sheet-interceptor/sheet-interceptor.service'; +import { RangeThemeStyle } from './range-theme-util'; +import defaultRangeThemeStyle from './range-themes/default'; + +export interface IRangeThemeRangeInfo { + range: IRange; + unitId: string; + subUnitId: string; +} + +export interface IRangeThemeStyleRule { + rangeInfo: IRangeThemeRangeInfo; + themeName: string; +} + +const SHEET_RANGE_THEME_MODEL_PLUGIN = 'SHEET_RANGE_THEME_MODEL_PLUGIN'; + +export class SheetRangeThemeModel extends Disposable { + private _rangeThemeStyleMap: Map> = new Map(); + private _rangeThemeStyleRuleMap: Map> = new Map(); + private _rTreeCollection: Map = new Map(); + private _defaultRangeThemeMap: Map = new Map(); + constructor( + @Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService, + @Inject(IResourceManagerService) private _resourceManagerService: IResourceManagerService + ) { + super(); + this._registerIntercept(); + this.registerDefaultRangeTheme(defaultRangeThemeStyle); + this._initSnapshot(); + } + + private _ensureRangeThemeStyleMap(unitId: string) { + if (!this._rangeThemeStyleMap.has(unitId)) { + this._rangeThemeStyleMap.set(unitId, new Map()); + } + return this._rangeThemeStyleMap.get(unitId)!; + } + + private _ensureRangeThemeStyleRuleMap(unitId: string) { + if (!this._rangeThemeStyleRuleMap.has(unitId)) { + this._rangeThemeStyleRuleMap.set(unitId, new Map()); + } + return this._rangeThemeStyleRuleMap.get(unitId)!; + } + + private _ensureRTreeCollection(unitId: string) { + if (!this._rTreeCollection.has(unitId)) { + this._rTreeCollection.set(unitId, new RTree()); + } + return this._rTreeCollection.get(unitId)!; + } + + /** + * Register range theme styles + * @param {string} themeName + * @param {IRangeThemeRangeInfo} rangeInfo + */ + registerRangeThemeRule(themeName: string, rangeInfo: IRangeThemeRangeInfo): void { + const { unitId, subUnitId, range } = rangeInfo; + const id = generateRandomId(); + const ruleMap = this._ensureRangeThemeStyleRuleMap(unitId); + const rTreeCollection = this._ensureRTreeCollection(unitId); + ruleMap.set(id, { rangeInfo, themeName }); + rTreeCollection.insert({ unitId, sheetId: subUnitId, range, id }); + } + + registerDefaultRangeTheme(rangeThemeStyle: RangeThemeStyle): void { + this._defaultRangeThemeMap.set(rangeThemeStyle.getName(), rangeThemeStyle); + } + + registerRangeThemeStyle(unitId: string, rangeThemeStyle: RangeThemeStyle): void { + this._ensureRangeThemeStyleMap(unitId).set(rangeThemeStyle.getName(), rangeThemeStyle); + } + + getALLRegisteredTheme(): string[] { + return Array.from(this._rangeThemeStyleMap.keys()); + } + + getRangeThemeStyle(unitId: string, name: string): RangeThemeStyle { + if (this._defaultRangeThemeMap.has(name)) { + return this._defaultRangeThemeMap.get(name)!; + } + return this._ensureRangeThemeStyleMap(unitId).get(name)!; + } + + public getCellStyle(unitId: string, subUnitId: string, row: number, col: number): Nullable { + const range = { startRow: row, startColumn: col, endRow: row, endColumn: col }; + const rTreeCollection = this._ensureRTreeCollection(unitId); + const themes = Array.from(rTreeCollection.bulkSearch([{ unitId, sheetId: subUnitId, range }])); + if (themes[0]) { + const themeRuleMap = this._ensureRangeThemeStyleRuleMap(unitId); + const themeRule = themeRuleMap.get(themes[0] as string); + if (themeRule) { + const { rangeInfo, themeName } = themeRule; + const offsetRow = row - rangeInfo.range.startRow; + const offsetCol = col - rangeInfo.range.startColumn; + const theme = this.getRangeThemeStyle(unitId, themeName); + return theme.getStyle(offsetRow, offsetCol); + } + } + return undefined; + } + + private _registerIntercept(): void { + this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, { + effect: InterceptorEffectEnum.Style, + handler: (cell, context, next) => { + const { row, col, unitId, subUnitId } = context; + const style = this.getCellStyle(unitId, subUnitId, row, col); + + if (style) { + const newCell: ICellDataForSheetInterceptor = { ...cell }; + newCell.s = style; + return next(newCell); + } + + return next(cell); + }, + })); + } + + toJson(unitId: string) { + const ruleMap = this._ensureRangeThemeStyleRuleMap(unitId); + const rangeThemeStyleRuleMap: Record = {}; + ruleMap.forEach((value, key) => { + rangeThemeStyleRuleMap[key] = value; + }); + + const rangeThemeStyleMap = this._ensureRangeThemeStyleMap(unitId); + const rangeThemeStyleMapJson: Record> = {}; + rangeThemeStyleMap.forEach((value, key) => { + rangeThemeStyleMapJson[key] = value.toJSON(); + }); + + return JSON.stringify({ + rangeThemeStyleRuleMap, + rangeThemeStyleMapJson, + }); + } + + fromJSON(json: string) { + const { rangeThemeStyleRuleMap, rangeThemeStyleMapJson } = JSON.parse(json); + Object.keys(rangeThemeStyleRuleMap).forEach((key) => { + this._ensureRangeThemeStyleRuleMap(key).clear(); + this._ensureRTreeCollection(key).clear(); + const ruleMap = rangeThemeStyleRuleMap[key]; + Object.keys(ruleMap).forEach((ruleKey) => { + const rule = ruleMap[ruleKey]; + this.registerRangeThemeRule(rule.themeName, rule.rangeInfo); + const rTreeCollection = this._ensureRTreeCollection(key); + rTreeCollection.insert({ unitId: key, sheetId: rule.rangeInfo.subUnitId, range: rule.rangeInfo.range, id: ruleKey }); + }); + }); + + Object.keys(rangeThemeStyleMapJson).forEach((key) => { + const styleMap = rangeThemeStyleMapJson[key]; + const style = new RangeThemeStyle(styleMap.name); + style.fromJSON(styleMap); + this._ensureRangeThemeStyleMap(key).set(style.getName(), style); + }); + } + + deleteUnitId(unitId: string) { + this._rangeThemeStyleMap.delete(unitId); + this._rangeThemeStyleRuleMap.delete(unitId); + this._rTreeCollection.delete(unitId); + } + + private _initSnapshot(): void { + this.disposeWithMe(this._resourceManagerService.registerPluginResource({ + toJson: (unitId: string) => { + return this.toJson(unitId); + }, + parseJson: (json: string) => { + if (!json) { + return {}; + } + try { + return JSON.parse(json); + // eslint-disable-next-line unused-imports/no-unused-vars + } catch (error) { + return {}; + } + }, + businesses: [UniverInstanceType.UNIVER_SHEET], + pluginName: SHEET_RANGE_THEME_MODEL_PLUGIN, + onLoad: (_unitId, resources) => { + this.fromJSON(resources); + }, + onUnLoad: (unitId) => { + this.deleteUnitId(unitId); + }, + })); + } + + override dispose(): void { + super.dispose(); + this._rangeThemeStyleMap.clear(); + this._rangeThemeStyleRuleMap.clear(); + this._rTreeCollection.clear(); + } +} diff --git a/packages/sheets/src/services/theme-range/range-theme-util.ts b/packages/sheets/src/model/range-theme-util.ts similarity index 75% rename from packages/sheets/src/services/theme-range/range-theme-util.ts rename to packages/sheets/src/model/range-theme-util.ts index 79433a51e0b..4d989201c4f 100644 --- a/packages/sheets/src/services/theme-range/range-theme-util.ts +++ b/packages/sheets/src/model/range-theme-util.ts @@ -137,5 +137,32 @@ export class RangeThemeStyle { return this.wholeStyle; } } + + toJSON(): Record { + return { + name: this._name, + wholeStyle: JSON.stringify(this.wholeStyle), + headerRowStyle: JSON.stringify(this.headerRowStyle), + headerColumnStyle: JSON.stringify(this.headerColumnStyle), + firstRowStyle: JSON.stringify(this.firstRowStyle), + secondRowStyle: JSON.stringify(this.secondRowStyle), + lastRowStyle: JSON.stringify(this.lastRowStyle), + firstColumnStyle: JSON.stringify(this.firstColumnStyle), + secondColumnStyle: JSON.stringify(this.secondColumnStyle), + lastColumnStyle: JSON.stringify(this.lastColumnStyle), + }; + } + + fromJSON(json: Record): void { + this.wholeStyle = JSON.parse(json.wholeStyle); + this.headerRowStyle = JSON.parse(json.headerRowStyle); + this.headerColumnStyle = JSON.parse(json.headerColumnStyle); + this.firstRowStyle = JSON.parse(json.firstRowStyle); + this.secondRowStyle = JSON.parse(json.secondRowStyle); + this.lastRowStyle = JSON.parse(json.lastRowStyle); + this.firstColumnStyle = JSON.parse(json.firstColumnStyle); + this.secondColumnStyle = JSON.parse(json.secondColumnStyle); + this.lastColumnStyle = JSON.parse(json.lastColumnStyle); + } } diff --git a/packages/sheets/src/services/theme-range/range-themes/default.ts b/packages/sheets/src/model/range-themes/default.ts similarity index 100% rename from packages/sheets/src/services/theme-range/range-themes/default.ts rename to packages/sheets/src/model/range-themes/default.ts diff --git a/packages/sheets/src/services/range-theme-service.ts b/packages/sheets/src/services/range-theme-service.ts new file mode 100644 index 00000000000..ffd1f8764f5 --- /dev/null +++ b/packages/sheets/src/services/range-theme-service.ts @@ -0,0 +1,40 @@ +/** + * 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 { IRangeThemeRangeInfo } from '../model/range-theme-model'; +import type { RangeThemeStyle } from '../model/range-theme-util'; +import { Disposable, Inject } from '@univerjs/core'; +import { SheetRangeThemeModel } from '../model/range-theme-model'; + +export class SheetRangeThemeService extends Disposable { + constructor( + @Inject(SheetRangeThemeModel) private _sheetRangeThemeModel: SheetRangeThemeModel + ) { + super(); + } + + registerRangeTheme(unitId: string, rangeThemeStyle: RangeThemeStyle): void { + this._sheetRangeThemeModel.registerRangeThemeStyle(unitId, rangeThemeStyle); + } + + getALLRegisterThemes(): string[] { + return this._sheetRangeThemeModel.getALLRegisteredTheme(); + } + + registerRangeThemeStyle(themeName: string, rangeInfo: IRangeThemeRangeInfo): void { + this._sheetRangeThemeModel.registerRangeThemeRule(themeName, rangeInfo); + } +} diff --git a/packages/sheets/src/services/theme-range/range-theme-service.ts b/packages/sheets/src/services/theme-range/range-theme-service.ts deleted file mode 100644 index 57297cd78d5..00000000000 --- a/packages/sheets/src/services/theme-range/range-theme-service.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * 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 { ICellDataForSheetInterceptor, IRange, Nullable } from '@univerjs/core'; -import type { IRangeThemeStyleItem, RangeThemeStyle } from './range-theme-util'; -import { Disposable, generateRandomId, Inject, InterceptorEffectEnum, RTree } from '@univerjs/core'; -import { INTERCEPTOR_POINT } from '../sheet-interceptor/interceptor-const'; -import { SheetInterceptorService } from '../sheet-interceptor/sheet-interceptor.service'; -import defaultRangeThemeStyle from './range-themes/default'; - -interface IRangeThemeRangeInfo { - range: IRange; - unitId: string; - subUnitId: string; -} -interface IRangeThemeStyleRule { - rangeInfo: IRangeThemeRangeInfo; - themeName: string; -} -export class SheetRangeThemeService extends Disposable { - private _rangeThemeStyleMap: Map = new Map(); - private _rTreeCollection: RTree = new RTree(); - private _rangeThemeStyleRuleMap: Map = new Map(); - constructor( - @Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService - ) { - super(); - this.registerRangeThemeStyle(defaultRangeThemeStyle); - this._registerIntercept(); - } - - private _registerIntercept(): void { - this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, { - effect: InterceptorEffectEnum.Style, - handler: (cell, context, next) => { - const { row, col, unitId, subUnitId } = context; - const style = this.getCellStyle(unitId, subUnitId, row, col); - if (style) { - const newCell: ICellDataForSheetInterceptor = { ...cell }; - newCell.s = style; - return next(newCell); - } - return next(cell); - }, - })); - } - - registerRangeThemeStyle(rangeThemeStyle: RangeThemeStyle): void { - this._rangeThemeStyleMap.set(rangeThemeStyle.getName(), rangeThemeStyle); - } - - getALLRegisterThemeStyle(): string[] { - return Array.from(this._rangeThemeStyleMap.keys()); - } - - /** - * Register range theme styles - * @param {string} themeName - * @param {IRangeThemeRangeInfo} rangeInfo - */ - registerRangeThemeStyles(themeName: string, rangeInfo: IRangeThemeRangeInfo): void { - const { unitId, subUnitId, range } = rangeInfo; - const id = generateRandomId(); - this._rangeThemeStyleRuleMap.set(id, { rangeInfo, themeName }); - this._rTreeCollection.insert({ unitId, sheetId: subUnitId, range, id }); - } - - getRangeThemeStyle(name: string): RangeThemeStyle { - return this._rangeThemeStyleMap.get(name)!; - } - - getCellStyle(unitId: string, subUnitId: string, row: number, col: number): Nullable { - const range = { startRow: row, startColumn: col, endRow: row, endColumn: col }; - const themes = Array.from(this._rTreeCollection.bulkSearch([{ unitId, sheetId: subUnitId, range }])); - if (themes[0]) { - const themeRule = this._rangeThemeStyleRuleMap.get(themes[0] as string); - if (themeRule) { - const { rangeInfo, themeName } = themeRule; - const offsetRow = row - rangeInfo.range.startRow; - const offsetCol = col - rangeInfo.range.startColumn; - const theme = this.getRangeThemeStyle(themeName); - return theme.getStyle(offsetRow, offsetCol); - } - } - return undefined; - } - - override dispose(): void { - this._rangeThemeStyleMap.clear(); - this._rTreeCollection.clear(); - } -} diff --git a/packages/sheets/src/sheets-plugin.ts b/packages/sheets/src/sheets-plugin.ts index 0b66665bbe1..0a5a659ee77 100644 --- a/packages/sheets/src/sheets-plugin.ts +++ b/packages/sheets/src/sheets-plugin.ts @@ -28,8 +28,9 @@ import { NumberCellDisplayController } from './controllers/number-cell.controlle import { RangeProtectionRenderModel } from './model/range-protection-render.model'; import { RangeProtectionRuleModel } from './model/range-protection-rule.model'; import { RangeProtectionCache } from './model/range-protection.cache'; -import { BorderStyleManagerService } from './services/border-style-manager.service'; +import { SheetRangeThemeModel } from './model/range-theme-model'; +import { BorderStyleManagerService } from './services/border-style-manager.service'; import { ExclusiveRangeService, IExclusiveRangeService } from './services/exclusive-range/exclusive-range-service'; import { NumfmtService } from './services/numfmt/numfmt.service'; import { INumfmtService } from './services/numfmt/type'; @@ -37,10 +38,10 @@ import { RangeProtectionRefRangeService } from './services/permission/range-perm import { RangeProtectionService } from './services/permission/range-permission/range-protection.service'; import { WorkbookPermissionService } from './services/permission/workbook-permission/workbook-permission.service'; import { WorksheetPermissionService, WorksheetProtectionPointModel, WorksheetProtectionRuleModel } from './services/permission/worksheet-permission'; +import { SheetRangeThemeService } from './services/range-theme-service'; import { RefRangeService } from './services/ref-range/ref-range.service'; import { SheetsSelectionsService } from './services/selections/selection.service'; import { SheetInterceptorService } from './services/sheet-interceptor/sheet-interceptor.service'; -import { SheetRangeThemeService } from './services/theme-range/range-theme-service'; const PLUGIN_NAME = 'SHEET_PLUGIN'; @@ -99,6 +100,9 @@ export class UniverSheetsPlugin extends Plugin { [WorksheetProtectionRuleModel], [WorksheetProtectionPointModel], + // range theme + [SheetRangeThemeModel], + // range protection [RangeProtectionRenderModel], [RangeProtectionRuleModel],