200 lines
5.2 KiB
TypeScript
200 lines
5.2 KiB
TypeScript
import {
|
|
type Model,
|
|
type Range,
|
|
type Reference,
|
|
type TokenType,
|
|
getTokens,
|
|
} from "@ironcalc/wasm";
|
|
import type { ActiveRange } from "../workbookState";
|
|
|
|
function sliceString(
|
|
text: string,
|
|
startScalar: number,
|
|
endScalar: number,
|
|
): string {
|
|
const scalarValues = Array.from(text);
|
|
const sliced = scalarValues.slice(startScalar, endScalar);
|
|
return sliced.join("");
|
|
}
|
|
|
|
export function tokenIsReferenceType(token: TokenType): token is Reference {
|
|
return typeof token === "object" && "Reference" in token;
|
|
}
|
|
|
|
export function tokenIsRangeType(token: TokenType): token is Range {
|
|
return typeof token === "object" && "Range" in token;
|
|
}
|
|
|
|
export function isInReferenceMode(text: string, cursor: number): boolean {
|
|
// FIXME
|
|
// This is a gross oversimplification
|
|
// Returns true if both are true:
|
|
// 1. Cursor is at the end
|
|
// 2. Last char is one of [',', '(', '+', '*', '-', '/', '<', '>', '=', '&']
|
|
// This has many false positives like '="1+' and also likely some false negatives
|
|
// The right way of doing this is to have a partial parse of the formula tree
|
|
// and check if the next token could be a reference
|
|
if (!text.startsWith("=")) {
|
|
return false;
|
|
}
|
|
if (text === "=") {
|
|
return true;
|
|
}
|
|
const l = text.length;
|
|
const chars = [",", "(", "+", "*", "-", "/", "<", ">", "=", "&"];
|
|
if (cursor === l && chars.includes(text[l - 1])) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// IronCalc Color Palette
|
|
export function getColor(index: number, alpha = 1): string {
|
|
const colors = [
|
|
{
|
|
name: "Cyan",
|
|
rgba: [89, 185, 188, 1],
|
|
hex: "#59B9BC",
|
|
},
|
|
{
|
|
name: "Flamingo",
|
|
rgba: [236, 87, 83, 1],
|
|
hex: "#EC5753",
|
|
},
|
|
{
|
|
hex: "#3358B7",
|
|
rgba: [51, 88, 183, 1],
|
|
name: "Blue",
|
|
},
|
|
{
|
|
hex: "#F8CD3C",
|
|
rgba: [248, 205, 60, 1],
|
|
name: "Yellow",
|
|
},
|
|
{
|
|
hex: "#3BB68A",
|
|
rgba: [59, 182, 138, 1],
|
|
name: "Emerald",
|
|
},
|
|
{
|
|
hex: "#523E93",
|
|
rgba: [82, 62, 147, 1],
|
|
name: "Violet",
|
|
},
|
|
{
|
|
hex: "#A23C52",
|
|
rgba: [162, 60, 82, 1],
|
|
name: "Burgundy",
|
|
},
|
|
{
|
|
hex: "#8CB354",
|
|
rgba: [162, 60, 82, 1],
|
|
name: "Wasabi",
|
|
},
|
|
{
|
|
hex: "#D03627",
|
|
rgba: [208, 54, 39, 1],
|
|
name: "Red",
|
|
},
|
|
{
|
|
hex: "#1B717E",
|
|
rgba: [27, 113, 126, 1],
|
|
name: "Teal",
|
|
},
|
|
];
|
|
if (alpha === 1) {
|
|
return colors[index % 10].hex;
|
|
}
|
|
const { rgba } = colors[index % 10];
|
|
return `rgba(${rgba[0]}, ${rgba[1]}, ${rgba[2]}, ${alpha})`;
|
|
}
|
|
|
|
function getFormulaHTML(
|
|
model: Model,
|
|
text: string,
|
|
): { html: JSX.Element[]; activeRanges: ActiveRange[] } {
|
|
let html: JSX.Element[] = [];
|
|
const activeRanges: ActiveRange[] = [];
|
|
let colorCount = 0;
|
|
if (text.startsWith("=")) {
|
|
const formula = text.slice(1);
|
|
const tokens = getTokens(formula);
|
|
const tokenCount = tokens.length;
|
|
const usedColors: Record<string, string> = {};
|
|
const sheet = model.getSelectedSheet();
|
|
const sheetList = model.getWorksheetsProperties().map((s) => s.name);
|
|
for (let index = 0; index < tokenCount; index += 1) {
|
|
const { token, start, end } = tokens[index];
|
|
if (tokenIsReferenceType(token)) {
|
|
const { sheet: refSheet, row, column } = token.Reference;
|
|
const sheetIndex = refSheet ? sheetList.indexOf(refSheet) : sheet;
|
|
const key = `${sheetIndex}-${row}-${column}`;
|
|
let color = usedColors[key];
|
|
if (!color) {
|
|
color = getColor(colorCount);
|
|
usedColors[key] = color;
|
|
colorCount += 1;
|
|
}
|
|
html.push(
|
|
<span key={index} style={{ color }}>
|
|
{sliceString(formula, start, end)}
|
|
</span>,
|
|
);
|
|
activeRanges.push({
|
|
sheet: sheetIndex,
|
|
rowStart: row,
|
|
columnStart: column,
|
|
rowEnd: row,
|
|
columnEnd: column,
|
|
color,
|
|
});
|
|
} else if (tokenIsRangeType(token)) {
|
|
let {
|
|
sheet: refSheet,
|
|
left: { row: rowStart, column: columnStart },
|
|
right: { row: rowEnd, column: columnEnd },
|
|
} = token.Range;
|
|
const sheetIndex = refSheet ? sheetList.indexOf(refSheet) : sheet;
|
|
|
|
const key = `${sheetIndex}-${rowStart}-${columnStart}:${rowEnd}-${columnEnd}`;
|
|
let color = usedColors[key];
|
|
if (!color) {
|
|
color = getColor(colorCount);
|
|
usedColors[key] = color;
|
|
colorCount += 1;
|
|
}
|
|
|
|
if (rowStart > rowEnd) {
|
|
[rowStart, rowEnd] = [rowEnd, rowStart];
|
|
}
|
|
if (columnStart > columnEnd) {
|
|
[columnStart, columnEnd] = [columnEnd, columnStart];
|
|
}
|
|
html.push(
|
|
<span key={index} style={{ color }}>
|
|
{sliceString(formula, start, end)}
|
|
</span>,
|
|
);
|
|
colorCount += 1;
|
|
|
|
activeRanges.push({
|
|
sheet: sheetIndex,
|
|
rowStart,
|
|
columnStart,
|
|
rowEnd,
|
|
columnEnd,
|
|
color,
|
|
});
|
|
} else {
|
|
html.push(<span key={index}>{sliceString(formula, start, end)}</span>);
|
|
}
|
|
}
|
|
html = [<span key="equals">=</span>].concat(html);
|
|
} else {
|
|
html = [<span key="single">{text}</span>];
|
|
}
|
|
return { html, activeRanges };
|
|
}
|
|
|
|
export default getFormulaHTML;
|