pub(crate) mod a1_shift;
pub(crate) mod executor;
pub(crate) mod query;
pub(crate) mod spec;
pub(crate) mod translate;
pub(crate) mod resolve;
use imbl::{HashMap, Vector};
use logisheets_base::{CellId, ColId, RowId, SheetId};
use logisheets_workbook::prelude::CtCfRule;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CfRange {
Rect(CellId, CellId),
RowRange(RowId, RowId),
ColRange(ColId, ColId),
}
#[derive(Debug, Clone)]
pub struct CfRule {
pub id: u32,
pub rule: CtCfRule,
}
#[derive(Debug, Clone)]
pub struct CfBlock {
pub ranges: Vector<CfRange>,
pub rules: Vector<CfRule>,
pub pivot: bool,
}
#[derive(Debug, Clone, Default)]
pub struct ConditionalFormattingManager {
pub data: HashMap<SheetId, Vector<CfBlock>>,
next_rule_id: u32,
}
impl ConditionalFormattingManager {
pub fn new() -> Self {
Self::default()
}
pub fn set_sheet(&mut self, sheet_id: SheetId, blocks: Vector<CfBlock>) {
if blocks.is_empty() {
self.data.remove(&sheet_id);
} else {
self.data.insert(sheet_id, blocks);
}
}
pub fn get_sheet(&self, sheet_id: SheetId) -> Option<&Vector<CfBlock>> {
self.data.get(&sheet_id)
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn mint_rule_id(&mut self) -> u32 {
let id = self.next_rule_id;
self.next_rule_id += 1;
id
}
pub fn max_priority(&self, sheet_id: SheetId) -> i32 {
self.data
.get(&sheet_id)
.map(|blocks| {
blocks
.iter()
.flat_map(|b| b.rules.iter())
.map(|r| r.rule.priority)
.max()
.unwrap_or(0)
})
.unwrap_or(0)
}
pub fn add_rule(&mut self, sheet_id: SheetId, ranges: Vector<CfRange>, rule: CtCfRule) -> u32 {
let id = self.mint_rule_id();
let block = CfBlock {
ranges,
rules: Vector::from_iter([CfRule { id, rule }]),
pivot: false,
};
let mut blocks = self.data.get(&sheet_id).cloned().unwrap_or_default();
blocks.push_back(block);
self.data.insert(sheet_id, blocks);
id
}
pub fn remove_rule(&mut self, id: u32) -> bool {
let mut touched = None;
for (sheet_id, blocks) in self.data.iter() {
if blocks.iter().any(|b| b.rules.iter().any(|r| r.id == id)) {
touched = Some(*sheet_id);
break;
}
}
let Some(sheet_id) = touched else {
return false;
};
let blocks = self.data.get(&sheet_id).cloned().unwrap_or_default();
let kept: Vector<CfBlock> = blocks
.into_iter()
.filter_map(|mut b| {
b.rules = b.rules.into_iter().filter(|r| r.id != id).collect();
if b.rules.is_empty() { None } else { Some(b) }
})
.collect();
self.set_sheet(sheet_id, kept);
true
}
pub fn replace_rule(&mut self, id: u32, rule: CtCfRule) -> bool {
for (sheet_id, blocks) in self.data.clone().iter() {
if !blocks.iter().any(|b| b.rules.iter().any(|r| r.id == id)) {
continue;
}
let updated: Vector<CfBlock> = blocks
.iter()
.cloned()
.map(|mut b| {
b.rules = b
.rules
.into_iter()
.map(|r| {
if r.id == id {
CfRule {
id,
rule: rule.clone(),
}
} else {
r
}
})
.collect();
b
})
.collect();
self.data.insert(*sheet_id, updated);
return true;
}
false
}
pub fn set_rule_ranges(&mut self, id: u32, ranges: Vector<CfRange>) -> bool {
for (sheet_id, blocks) in self.data.clone().iter() {
if !blocks.iter().any(|b| b.rules.iter().any(|r| r.id == id)) {
continue;
}
let updated: Vector<CfBlock> = blocks
.iter()
.cloned()
.map(|mut b| {
if b.rules.iter().any(|r| r.id == id) {
b.ranges = ranges.clone();
}
b
})
.collect();
self.data.insert(*sheet_id, updated);
return true;
}
false
}
}