Skip to content

Commit

Permalink
feat(conditional-formatting): support formatting painter
Browse files Browse the repository at this point in the history
  • Loading branch information
Gggpound committed Sep 12, 2024
1 parent d05b395 commit 8abca44
Show file tree
Hide file tree
Showing 2 changed files with 323 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
/**
* 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 { Disposable, Inject, Injector, IUniverInstanceService, LifecycleStages, ObjectMatrix, OnLifecycle, Range, Rectangle, Tools, UniverInstanceType } from '@univerjs/core';
import { createTopMatrixFromMatrix, findAllRectangle, SheetsSelectionsService } from '@univerjs/sheets';
import { AddConditionalRuleMutation, AddConditionalRuleMutationUndoFactory, ConditionalFormattingRuleModel, ConditionalFormattingViewModel, DeleteConditionalRuleMutation, DeleteConditionalRuleMutationUndoFactory, SetConditionalRuleMutation, setConditionalRuleMutationUndoFactory, SHEET_CONDITIONAL_FORMATTING_PLUGIN } from '@univerjs/sheets-conditional-formatting';
import { FormatPainterStatus, IFormatPainterService } from '@univerjs/sheets-ui';
import type { IMutationInfo, IRange, Workbook } from '@univerjs/core';
import type { IAddConditionalRuleMutationParams, IDeleteConditionalRuleMutationParams, ISetConditionalRuleMutationParams } from '@univerjs/sheets-conditional-formatting';
import type { IFormatPainterHook } from '@univerjs/sheets-ui';
import type { Nullable } from '../../../core/lib/types';

const repeatByRange = (sourceRange: IRange, targetRange: IRange) => {
const getRowLength = (range: IRange) => range.endRow - range.startRow + 1;
const getColLength = (range: IRange) => range.endColumn - range.startColumn + 1;
const rowMod = getRowLength(targetRange) % getRowLength(sourceRange);
const colMod = getColLength(targetRange) % getColLength(sourceRange);
const repeatRow = Math.floor(getRowLength(targetRange) / getRowLength(sourceRange));
const repeatCol = Math.floor(getColLength(targetRange) / getColLength(sourceRange));
const repeatList: Array<{ startRange: IRange; repeatRelativeRange: IRange }> = [];
const repeatRelativeRange: IRange = {
startRow: 0,
endRow: getRowLength(sourceRange) - 1,
startColumn: 0,
endColumn: getColLength(sourceRange) - 1,
};

for (let countRow = 0; countRow < (repeatRow + (rowMod ? 0.1 : 0)); countRow++) {
for (let countCol = 0; countCol < (repeatCol + (colMod ? 0.1 : 0)); countCol++) {
const row = getRowLength(sourceRange) * (countRow);
const col = getColLength(sourceRange) * (countCol);
const startRange: IRange = {
startRow: row + targetRange.startRow,
endRow: row + targetRange.startRow,
startColumn: col + targetRange.startColumn,
endColumn: col + targetRange.startColumn,
};
let _repeatRelativeRange = repeatRelativeRange;
if (countRow === repeatRow && rowMod) {
_repeatRelativeRange = { ..._repeatRelativeRange };
_repeatRelativeRange.endRow = _repeatRelativeRange.endRow - (getRowLength(sourceRange) - rowMod);
}
if (countCol === repeatCol && colMod) {
_repeatRelativeRange = { ..._repeatRelativeRange };
_repeatRelativeRange.endColumn = _repeatRelativeRange.endColumn - (getColLength(sourceRange) - colMod);
}
repeatList.push({ repeatRelativeRange: _repeatRelativeRange, startRange });
}
}
return repeatList;
};

@OnLifecycle(LifecycleStages.Rendered, ConditionalFormattingPainterController)
export class ConditionalFormattingPainterController extends Disposable {
private _painterConfig: Nullable<{ unitId: string; subUnitId: string; range: IRange }> = null;
constructor(
@Inject(Injector) private _injector: Injector,
@Inject(IUniverInstanceService) private _univerInstanceService: IUniverInstanceService,
@Inject(IFormatPainterService) private _formatPainterService: IFormatPainterService,
@Inject(SheetsSelectionsService) private _sheetsSelectionsService: SheetsSelectionsService,
@Inject(ConditionalFormattingRuleModel) private _conditionalFormattingRuleModel: ConditionalFormattingRuleModel,

@Inject(ConditionalFormattingViewModel) private _conditionalFormattingViewModel: ConditionalFormattingViewModel

) {
super();

this._initFormattingPainter();
}

// eslint-disable-next-line max-lines-per-function
private _initFormattingPainter() {
const noopReturnFunc = () => ({ redos: [], undos: [] });
// eslint-disable-next-line max-lines-per-function
const loopFunc = (
sourceStartCell: { row: number; col: number },
targetStartCell: { row: number; col: number },
relativeRange: IRange,
matrixMap: Map<string, ObjectMatrix<1>>,
config: {
targetUnitId: string;
targetSubUnitId: string;
}
) => {
const { unitId: sourceUnitId, subUnitId: sourceSubUnitId } = this._painterConfig!;
const { targetUnitId, targetSubUnitId } = config;

const sourceRange = {
startRow: sourceStartCell.row,
startColumn: sourceStartCell.col,
endColumn: sourceStartCell.col,
endRow: sourceStartCell.row,
};
const targetRange = {
startRow: targetStartCell.row,
startColumn: targetStartCell.col,
endColumn: targetStartCell.col,
endRow: targetStartCell.row,
};

Range.foreach(relativeRange, (row, col) => {
const sourcePositionRange = Rectangle.getPositionRange(
{
startRow: row,
startColumn: col,
endColumn: col,
endRow: row,
},
sourceRange
);
const targetPositionRange = Rectangle.getPositionRange(
{
startRow: row,
startColumn: col,
endColumn: col,
endRow: row,
},
targetRange
);

const sourceCellCf = this._conditionalFormattingViewModel.getCellCf(
sourceUnitId,
sourceSubUnitId,
sourcePositionRange.startRow,
sourcePositionRange.startColumn
);

const targetCellCf = this._conditionalFormattingViewModel.getCellCf(
targetUnitId,
targetSubUnitId,
targetPositionRange.startRow,
targetPositionRange.startColumn
);

if (targetCellCf) {
targetCellCf.cfList.forEach((cf) => {
let matrix = matrixMap.get(cf.cfId);
if (!matrixMap.get(cf.cfId)) {
const rule = this._conditionalFormattingRuleModel.getRule(targetUnitId, targetSubUnitId, cf.cfId);
if (!rule) {
return;
}
matrix = new ObjectMatrix();
rule.ranges.forEach((range) => {
Range.foreach(range, (row, col) => {
matrix!.setValue(row, col, 1);
});
});
matrixMap.set(cf.cfId, matrix);
}
matrix!.realDeleteValue(targetPositionRange.startRow, targetPositionRange.startColumn);
});
}

if (sourceCellCf) {
sourceCellCf.cfList.forEach((cf) => {
const matrix = matrixMap.get(cf.cfId);
matrix && matrix.setValue(targetPositionRange.startRow, targetPositionRange.startColumn, 1);
});
}
});
};

const generalApplyFunc = (targetUnitId: string, targetSubUnitId: string, targetRange: IRange) => {
const { range: sourceRange, unitId: sourceUnitId, subUnitId: sourceSubUnitId } = this._painterConfig!;
const isSkipSheet = targetUnitId !== sourceUnitId || sourceSubUnitId !== targetSubUnitId;
const matrixMap: Map<string, ObjectMatrix<1>> = new Map();

const redos: IMutationInfo[] = [];
const undos: IMutationInfo[] = [];
if (!targetUnitId || !targetSubUnitId || !sourceUnitId || !sourceSubUnitId) {
return noopReturnFunc();
}
const ruleList = this._conditionalFormattingRuleModel.getSubunitRules(sourceUnitId, sourceSubUnitId) ?? [];
ruleList?.forEach((rule) => {
const { ranges, cfId } = rule;
if (ranges.some((range) => Rectangle.intersects(sourceRange, range))) {
const matrix = new ObjectMatrix<1>();
if (!isSkipSheet) {
ranges.forEach((range) => {
Range.foreach(range, (row, col) => {
matrix.setValue(row, col, 1);
});
});
}
matrixMap.set(cfId, matrix);
}
});

const sourceStartCell = {
row: sourceRange.startRow,
col: sourceRange.startColumn,
};

const repeats = repeatByRange(sourceRange, targetRange);

repeats.forEach((repeat) => {
loopFunc(sourceStartCell, { row: repeat.startRange.startRow, col: repeat.startRange.startColumn }, repeat.repeatRelativeRange, matrixMap, { targetUnitId, targetSubUnitId });
});

matrixMap.forEach((item, cfId) => {
if (!isSkipSheet) {
const rule = this._conditionalFormattingRuleModel.getRule(sourceUnitId, sourceSubUnitId, cfId);
if (!rule) {
return;
}
const ranges = findAllRectangle(createTopMatrixFromMatrix(item));
if (ranges.length) {
const params: ISetConditionalRuleMutationParams = {
unitId: sourceUnitId, subUnitId: sourceSubUnitId, rule: { ...rule, ranges },
};
redos.push({ id: SetConditionalRuleMutation.id, params });
undos.push(...setConditionalRuleMutationUndoFactory(this._injector, params));
} else {
const params: IDeleteConditionalRuleMutationParams = {
unitId: sourceUnitId, subUnitId: sourceSubUnitId, cfId: rule.cfId,
};
redos.push({ id: DeleteConditionalRuleMutation.id, params });
undos.push(...DeleteConditionalRuleMutationUndoFactory(this._injector, params));
}
} else {
const rule = this._conditionalFormattingRuleModel.getRule(targetUnitId, targetSubUnitId, cfId);
const ranges = findAllRectangle(createTopMatrixFromMatrix(item));
if (!rule) {
if (ranges.length) {
const sourceRule = this._conditionalFormattingRuleModel.getRule(sourceUnitId, sourceSubUnitId, cfId);
if (sourceRule) {
const params: IAddConditionalRuleMutationParams = {
unitId: targetUnitId,
subUnitId: targetSubUnitId,
rule: {
...Tools.deepClone(sourceRule),
cfId: this._conditionalFormattingRuleModel.createCfId(targetUnitId, targetSubUnitId),
ranges,
},
};
redos.push({ id: AddConditionalRuleMutation.id, params });
undos.push(AddConditionalRuleMutationUndoFactory(this._injector, params));
}
}
} else {
if (ranges.length) {
const params: ISetConditionalRuleMutationParams = {
unitId: targetUnitId, subUnitId: targetSubUnitId, rule: { ...rule, ranges },
};
redos.push({ id: SetConditionalRuleMutation.id, params });
undos.push(...setConditionalRuleMutationUndoFactory(this._injector, params));
} else {
const params: IDeleteConditionalRuleMutationParams = {
unitId: targetUnitId, subUnitId: targetSubUnitId, cfId: rule.cfId,
};
redos.push({ id: DeleteConditionalRuleMutation.id, params });
undos.push(...DeleteConditionalRuleMutationUndoFactory(this._injector, params));
}
}
}
});
return {
undos,
redos,
};
};

const hook: IFormatPainterHook = {
id: SHEET_CONDITIONAL_FORMATTING_PLUGIN,
onStatusChange: (status) => {
switch (status) {
case FormatPainterStatus.INFINITE:
case FormatPainterStatus.ONCE: {
const unitId = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getUnitId();
const subUnitId = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getActiveSheet()?.getSheetId();
const selection = this._sheetsSelectionsService.getCurrentLastSelection();
const range = selection?.range;
if (unitId && subUnitId && range) {
this._painterConfig = { unitId, subUnitId, range };
}
break;
}
case FormatPainterStatus.OFF: {
this._painterConfig = null;
break;
}
}
},
onApply: (unitId, subUnitId, targetRange) => {
if (this._painterConfig) {
return generalApplyFunc(unitId, subUnitId, targetRange);
}
return {
redos: [],
undos: [],
};
},
};

this._formatPainterService.addHook(hook);
}
}
22 changes: 12 additions & 10 deletions packages/sheets-conditional-formatting-ui/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@univerjs/core';
import { SHEET_CONDITIONAL_FORMATTING_PLUGIN, UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting';
import { AddAverageCfCommand } from './commands/commands/add-average-cf.command';
import { AddCfCommand } from './commands/commands/add-cf.command';
import { AddColorScaleConditionalRuleCommand } from './commands/commands/add-color-scale-cf.command';
import { AddDataBarConditionalRuleCommand } from './commands/commands/add-data-bar-cf.command';
import { AddDuplicateValuesCfCommand } from './commands/commands/add-duplicate-values-cf.command';
Expand All @@ -35,24 +36,24 @@ import { AddTimePeriodCfCommand } from './commands/commands/add-time-period-cf.c
import { AddUniqueValuesCfCommand } from './commands/commands/add-unique-values-cf.command';
import { ClearRangeCfCommand } from './commands/commands/clear-range-cf.command';
import { ClearWorksheetCfCommand } from './commands/commands/clear-worksheet-cf.command';
import { OpenConditionalFormattingOperator } from './commands/operations/open-conditional-formatting-panel';
import { DeleteCfCommand } from './commands/commands/delete-cf.command';
import { SetCfCommand } from './commands/commands/set-cf.command';
import { MoveCfCommand } from './commands/commands/move-cf.command';
import { AddCfCommand } from './commands/commands/add-cf.command';
import { SetCfCommand } from './commands/commands/set-cf.command';
import { OpenConditionalFormattingOperator } from './commands/operations/open-conditional-formatting-panel';

import { SheetsCfRenderController } from './controllers/cf.render.controller';
import { ConditionalFormattingCopyPasteController } from './controllers/cf.copy-paste.controller';
import { ConditionalFormattingAutoFillController } from './controllers/cf.auto-fill.controller';
import { ConditionalFormattingClearController } from './controllers/cf.clear.controller';
import { ConditionalFormattingCopyPasteController } from './controllers/cf.copy-paste.controller';
import { ConditionalFormattingEditorController } from './controllers/cf.editor.controller';
import { ConditionalFormattingI18nController } from './controllers/cf.i18n.controller';
import { ConditionalFormattingMenuController } from './controllers/cf.menu.controller';
import { ConditionalFormattingPainterController } from './controllers/cf.painter.controller';
import { ConditionalFormattingPanelController } from './controllers/cf.panel.controller';
import { ConditionalFormattingI18nController } from './controllers/cf.i18n.controller';
import { SheetsCfRefRangeController } from './controllers/cf.ref-range.controller';
import { ConditionalFormattingEditorController } from './controllers/cf.editor.controller';
import { ConditionalFormattingClearController } from './controllers/cf.clear.controller';
import { ConditionalFormattingPermissionController } from './controllers/cf.permission.controller';
import type { IUniverSheetsConditionalFormattingUIConfig } from './controllers/config.schema';
import { SheetsCfRefRangeController } from './controllers/cf.ref-range.controller';
import { SheetsCfRenderController } from './controllers/cf.render.controller';
import { defaultPluginConfig, PLUGIN_CONFIG_KEY } from './controllers/config.schema';
import type { IUniverSheetsConditionalFormattingUIConfig } from './controllers/config.schema';

@DependentOn(UniverSheetsConditionalFormattingPlugin)
export class UniverSheetsConditionalFormattingUIPlugin extends Plugin {
Expand Down Expand Up @@ -86,6 +87,7 @@ export class UniverSheetsConditionalFormattingUIPlugin extends Plugin {
this._injector.add([ConditionalFormattingI18nController]);
this._injector.add([ConditionalFormattingEditorController]);
this._injector.add([ConditionalFormattingClearController]);
this._injector.add([ConditionalFormattingPainterController]);
}

private _initCommand() {
Expand Down

0 comments on commit 8abca44

Please sign in to comment.