Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
144 lines
5.5 KiB
TypeScript
144 lines
5.5 KiB
TypeScript
/**
|
|
* 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 { IConditionFormattingRule, IHighlightCell } from '../models/type';
|
|
import type { IDataBarCellData, IDataBarRenderParams, IIconSetCellData, IIconSetRenderParams } from '../render/type';
|
|
import { Disposable, Inject, LRUMap, merge } from '@univerjs/core';
|
|
import { CFRuleType } from '../base/const';
|
|
import { ConditionalFormattingRuleModel } from '../models/conditional-formatting-rule-model';
|
|
import { CONDITIONAL_FORMATTING_VIEWPORT_CACHE_LENGTH, ConditionalFormattingViewModel } from '../models/conditional-formatting-view-model';
|
|
|
|
type IComposedStyle = { style?: IHighlightCell['style'] } & IDataBarCellData & IIconSetCellData & { isShowValue: boolean };
|
|
|
|
export class ConditionalFormattingStyleComposer extends Disposable {
|
|
private _cache = new LRUMap<string, IComposedStyle | null>(CONDITIONAL_FORMATTING_VIEWPORT_CACHE_LENGTH);
|
|
|
|
constructor(
|
|
@Inject(ConditionalFormattingRuleModel) private _conditionalFormattingRuleModel: ConditionalFormattingRuleModel,
|
|
@Inject(ConditionalFormattingViewModel) private _conditionalFormattingViewModel: ConditionalFormattingViewModel
|
|
) {
|
|
super();
|
|
this.disposeWithMe(this._conditionalFormattingRuleModel.$ruleChange.subscribe(() => this._cache.clear()));
|
|
this.disposeWithMe(this._conditionalFormattingViewModel.markDirty$.subscribe(() => this._cache.clear()));
|
|
this.disposeWithMe(this._conditionalFormattingViewModel.cacheClear$.subscribe(() => this._cache.clear()));
|
|
}
|
|
|
|
override dispose(): void {
|
|
this._cache.clear();
|
|
super.dispose();
|
|
}
|
|
|
|
// Conditional formats need to be evaluated in priority order.
|
|
// Evaluation of subsequent rules stops only if the current rule is matched and stopIfTrue=true.
|
|
composeStyle(unitId: string, subUnitId: string, row: number, col: number) {
|
|
const cacheKey = `${unitId}_${subUnitId}_${row}_${col}`;
|
|
if (this._cache.has(cacheKey)) {
|
|
return this._cache.get(cacheKey) ?? null;
|
|
}
|
|
|
|
const cellCfs = this._conditionalFormattingViewModel.getCellCfs(unitId, subUnitId, row, col);
|
|
|
|
if (!cellCfs?.length) {
|
|
this._cache.set(cacheKey, null);
|
|
return null;
|
|
}
|
|
|
|
const matchedRules: Array<{
|
|
rule: IConditionFormattingRule;
|
|
cacheItem: (typeof cellCfs)[number];
|
|
}> = [];
|
|
|
|
let stopIfTrueIndex = -1;
|
|
for (const cacheItem of cellCfs) {
|
|
const rule = this._conditionalFormattingRuleModel.getRule(unitId, subUnitId, cacheItem.cfId);
|
|
if (!rule) {
|
|
continue;
|
|
}
|
|
|
|
matchedRules.push({ rule, cacheItem });
|
|
|
|
if (stopIfTrueIndex === -1 && rule.stopIfTrue && this._isRuleMatched(rule, cacheItem.result)) {
|
|
stopIfTrueIndex = matchedRules.length - 1;
|
|
}
|
|
}
|
|
|
|
if (!matchedRules.length) {
|
|
this._cache.set(cacheKey, null);
|
|
return null;
|
|
}
|
|
|
|
const effectiveRules = stopIfTrueIndex > -1
|
|
? matchedRules.slice(0, stopIfTrueIndex + 1)
|
|
: matchedRules;
|
|
|
|
const result = {} as IComposedStyle;
|
|
|
|
for (let i = effectiveRules.length - 1; i >= 0; i--) {
|
|
const { rule, cacheItem } = effectiveRules[i];
|
|
this._mergeComposeResult(result, rule, cacheItem.result);
|
|
}
|
|
|
|
this._cache.set(cacheKey, result);
|
|
return result;
|
|
}
|
|
|
|
private _mergeComposeResult(
|
|
result: { style?: IHighlightCell['style'] } & IDataBarCellData & IIconSetCellData & { isShowValue: boolean },
|
|
rule: IConditionFormattingRule,
|
|
ruleResult: unknown
|
|
) {
|
|
const type = rule.rule.type;
|
|
|
|
if (type === CFRuleType.highlightCell) {
|
|
ruleResult && merge(result, { style: ruleResult });
|
|
return;
|
|
}
|
|
|
|
if (type === CFRuleType.colorScale) {
|
|
if (ruleResult && typeof ruleResult === 'string') {
|
|
const preStyle = result.style || {};
|
|
result.style = { ...preStyle, bg: { rgb: ruleResult } };
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (type === CFRuleType.dataBar) {
|
|
const ruleCache = ruleResult as IDataBarRenderParams;
|
|
if (ruleCache) {
|
|
result.dataBar = ruleCache;
|
|
result.isShowValue = ruleCache.isShowValue;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (type === CFRuleType.iconSet) {
|
|
const ruleCache = ruleResult as IIconSetRenderParams;
|
|
if (ruleCache) {
|
|
result.iconSet = ruleCache;
|
|
result.isShowValue = ruleCache.isShowValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
private _isRuleMatched(rule: IConditionFormattingRule, ruleResult: unknown) {
|
|
if (rule.rule.type === CFRuleType.highlightCell) {
|
|
return !!ruleResult && typeof ruleResult === 'object' && Object.keys(ruleResult as object).length > 0;
|
|
}
|
|
|
|
return !!ruleResult;
|
|
}
|
|
}
|