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 = {}; 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( {sliceString(formula, start, end)} , ); 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( {sliceString(formula, start, end)} , ); colorCount += 1; activeRanges.push({ sheet: sheetIndex, rowStart, columnStart, rowEnd, columnEnd, color, }); } else { html.push({sliceString(formula, start, end)}); } } html = [=].concat(html); } else { html = [{text}]; } return { html, activeRanges }; } export default getFormulaHTML;