<commit_before>import {DRAG_NONE} from './constants';
import Emitter from './Emitter';
import {getCellID, getCellPos} from './utils/cellID';
import {initEvents} from './WebSheet.events';
import {listen} from './utils/events';
import parseExpression from './exprCompiler';
import {parseNumMaybe} from './exprCompiler/functions';


const DEFAULT_COLUMN_WIDTH = 100; // px
const DEFAULT_BORDER_WIDTH = 1; // px

const defaultParams = {
    width: 6,
    height: 6,
};


const WINDOW_MOUSEUP = Symbol('window.onmouseup');

export default class WebSheet {
    constructor(elem, params = {}) {
        this.elem = elem;
        this.elem.className = 'websheet';

        Object.assign(params, defaultParams);
        this.height = params.height;
        this.width = params.width;

        this.columnWidths = [];
        for (let i = 0; i < params.width; i++) {
            this.columnWidths[i] = DEFAULT_COLUMN_WIDTH;
        }

        this.data = [];
        this.calculated = [];
        this.formatting = [];

        this.depUpdateQueue = null;
        this.dependencies = {}; // Map of cell ID to array of dependant cell IDs
        this.dependants = {}; // Map of cell ID to array of dependencies

        this.cellCache = {};

        this.dragType = DRAG_NONE;
        this.dragSource = null;

        listen(window, 'mouseup', this[WINDOW_MOUSEUP] = e => {
            if (this.dragType === DRAG_NONE) {
                return;
            }
            this.dragType = DRAG_NONE;
            this.dragSource = null;
            this.elem.className = 'websheet';
        });

        this.valueUpdates = new Emitter();
        this.calculatedUpdates = new Emitter();

        this.context = params.context || null;
        this.name = null;
    }

    addColumn() {
        this.width += 1;
        this.columnWidths.push(DEFAULT_COLUMN_WIDTH);
        this.forceRerender();
    }
    addRow() {
        this.height += 1;
        this.data.push(new Array(this.width));
        this.calculated.push(new Array(this.width));
        this.formatting.push(new Array(this.width));
        this.forceRerender();
    }

    calculateValueAtPosition(row, col, expression) {
        if (!expression) return;
        var cellID = getCellID(row, col);

        // Parse the expression
        var parsed = parseExpression(expression);

        // Evaluate the expression to find a value
        var value;
        try {
            value = parsed.run(this);
            if (isNaN(value)) value = '#VALUE!';
        } catch (e) {
            throw e;
            console.error(e);
            value = '#ERROR!';
            parsed = null;
        }

        // Set the calculated value in the calculated cache
        this.calculated[row] = this.calculated[row] || [];

        var wasUpdated = this.calculated[row][col] !== value;
        if (wasUpdated) {
            this.calculated[row][col] = value;
        }

        // Set the dependants
        var dependants = [];
        if (parsed) {
            // Bind intra-sheet dependencies
            parsed.findCellDependencies(dep => {
                if (dependants.indexOf(dep) !== -1) return;
                dependants.push(dep);
                var deps;
                if (!(dep in this.dependencies)) {
                    this.dependencies[dep] = [cellID];
                } else if ((deps = this.dependencies[dep]) && deps.indexOf(cellID) === -1) {
                    deps.push(cellID);
                }
            });

            // Bind inter-sheet dependencies if a sheet context exists
            if (this.context) {
                this.context.clearDependencies(this, cellID);
                let sheetDeps = [];
                parsed.findSheetDependencies((sheet, dep) => {
                    if (!this.context.sheets[sheet.toUpperCase()]) return;
                    var depName = `${sheet}!${dep}`;
                    if (sheetDeps.indexOf(depName) !== -1) return;
                    sheetDeps.push(depName);

                    this.context.setDependency(this, cellID, sheet, dep, () => {
                        this.calculateValueAtPosition(row, col, expression);
                    });

                });
            }

        }
        this.dependants[cellID] = dependants;

        // Set the value of the element
        this.getCell(cellID).value = value;

        if (wasUpdated) {
            this.updateDependencies(cellID);
            this.calculatedUpdates.fire(cellID, value);
        }
    }

    clearCell(row, col) {
        var cellID = getCellID(row, col);
        var elem = this.getCell(cellID);
        if (!elem) return;

        elem.value = '';
        if (row in this.data) delete this.data[row][col];
        if (row in this.calculated) delete this.calculated[row][col];
        this.clearDependants(cellID);
        this.dependants[cellID] = [];
    }

    clearDependants(id) {
        var deps = this.dependants[id];
        if (!deps) return;

        for (var i = 0; i < deps.length; i++) {
            let remDeps = this.dependencies[deps[i]];
            if (!remDeps) continue;
            let idx = remDeps.indexOf(id);
            if (idx !== -1) remDeps.splice(idx, 1);
        }

        if (!this.context) return;
        this.context.clearDependencies(this, id);
    }

    forceRerender() {
        // First, update the element to be the correct dimensions.
        var width = this.columnWidths.reduce((a, b) => a + b); // Get the width of each column
        width += DEFAULT_BORDER_WIDTH;
        // width -= this.width * DEFAULT_BORDER_WIDTH; // Account for border widths
        this.elem.style.width = width + 'px';

        while (this.elem.childNodes.length) {
            this.elem.removeChild(this.elem.firstChild);
        }
        this.cellCache = {};

        var workQueue = [];

        // Create each row and cell
        for (var i = 0; i < this.height; i++) {
            let row = document.createElement('div');
            row.style.width = width + 'px';
            row.className = 'websheet-row';
            if (i < 1) {
                row.className += ' websheet-row-sticky';
            }
            this.elem.appendChild(row);

            let rowDataCache = this.data[i] || [];
            let rowCalculatedCache = this.calculated[i] || [];
            let rowFormattingCache = this.formatting[i] || [];

            for (var j = 0; j < this.width; j++) {
                let cell = document.createElement('input');
                cell.className = 'websheet-cell';

                let cellWrapper = document.createElement('div');
                cellWrapper.className = 'websheet-cell-wrapper';
                cellWrapper.style.width = (this.columnWidths[j] - 1) + 'px';
                cellWrapper.appendChild(cell);

                row.appendChild(cellWrapper);

                cell.value = rowCalculatedCache[j] || rowDataCache[j] || '';
                cell.setAttribute('data-id', cell.title = getCellID(i, j));
                cell.setAttribute('data-id-prev-col', getCellID(i, j - 1));
                cell.setAttribute('data-id-prev-row', getCellID(i - 1, j));
                cell.setAttribute('data-id-next-col', getCellID(i, j + 1));
                cell.setAttribute('data-id-next-row', getCellID(i + 1, j));
                cell.setAttribute('data-row', i);
                cell.setAttribute('data-col', j);

                if (cell.value[0] === '=') {
                    workQueue.push(() => this.setValueAtPosition(i, j, cell.value, true));<commit_msg>Fix for equations being rendered in the wrong location<commit_after>import {DRAG_NONE} from './constants';
import Emitter from './Emitter';
import {getCellID, getCellPos} from './utils/cellID';
import {initEvents} from './WebSheet.events';
import {listen} from './utils/events';
import parseExpression from './exprCompiler';
import {parseNumMaybe} from './exprCompiler/functions';


const DEFAULT_COLUMN_WIDTH = 100; // px
const DEFAULT_BORDER_WIDTH = 1; // px

const defaultParams = {
    width: 6,
    height: 6,
};


const WINDOW_MOUSEUP = Symbol('window.onmouseup');

export default class WebSheet {
    constructor(elem, params = {}) {
        this.elem = elem;
        this.elem.className = 'websheet';

        Object.assign(params, defaultParams);
        this.height = params.height;
        this.width = params.width;

        this.columnWidths = [];
        for (let i = 0; i < params.width; i++) {
            this.columnWidths[i] = DEFAULT_COLUMN_WIDTH;
        }

        this.data = [];
        this.calculated = [];
        this.formatting = [];

        this.depUpdateQueue = null;
        this.dependencies = {}; // Map of cell ID to array of dependant cell IDs
        this.dependants = {}; // Map of cell ID to array of dependencies

        this.cellCache = {};

        this.dragType = DRAG_NONE;
        this.dragSource = null;

        listen(window, 'mouseup', this[WINDOW_MOUSEUP] = e => {
            if (this.dragType === DRAG_NONE) {
                return;
            }
            this.dragType = DRAG_NONE;
            this.dragSource = null;
            this.elem.className = 'websheet';
        });

        this.valueUpdates = new Emitter();
        this.calculatedUpdates = new Emitter();

        this.context = params.context || null;
        this.name = null;
    }

    addColumn() {
        this.width += 1;
        this.columnWidths.push(DEFAULT_COLUMN_WIDTH);
        this.forceRerender();
    }
    addRow() {
        this.height += 1;
        this.data.push(new Array(this.width));
        this.calculated.push(new Array(this.width));
        this.formatting.push(new Array(this.width));
        this.forceRerender();
    }

    calculateValueAtPosition(row, col, expression) {
        if (!expression) return;
        var cellID = getCellID(row, col);

        // Parse the expression
        var parsed = parseExpression(expression);

        // Evaluate the expression to find a value
        var value;
        try {
            value = parsed.run(this);
            if (isNaN(value)) value = '#VALUE!';
        } catch (e) {
            throw e;
            console.error(e);
            value = '#ERROR!';
            parsed = null;
        }

        // Set the calculated value in the calculated cache
        this.calculated[row] = this.calculated[row] || [];

        var wasUpdated = this.calculated[row][col] !== value;
        if (wasUpdated) {
            this.calculated[row][col] = value;
        }

        // Set the dependants
        var dependants = [];
        if (parsed) {
            // Bind intra-sheet dependencies
            parsed.findCellDependencies(dep => {
                if (dependants.indexOf(dep) !== -1) return;
                dependants.push(dep);
                var deps;
                if (!(dep in this.dependencies)) {
                    this.dependencies[dep] = [cellID];
                } else if ((deps = this.dependencies[dep]) && deps.indexOf(cellID) === -1) {
                    deps.push(cellID);
                }
            });

            // Bind inter-sheet dependencies if a sheet context exists
            if (this.context) {
                this.context.clearDependencies(this, cellID);
                let sheetDeps = [];
                parsed.findSheetDependencies((sheet, dep) => {
                    if (!this.context.sheets[sheet.toUpperCase()]) return;
                    var depName = `${sheet}!${dep}`;
                    if (sheetDeps.indexOf(depName) !== -1) return;
                    sheetDeps.push(depName);

                    this.context.setDependency(this, cellID, sheet, dep, () => {
                        this.calculateValueAtPosition(row, col, expression);
                    });

                });
            }

        }
        this.dependants[cellID] = dependants;

        // Set the value of the element
        this.getCell(cellID).value = value;

        if (wasUpdated) {
            this.updateDependencies(cellID);
            this.calculatedUpdates.fire(cellID, value);
        }
    }

    clearCell(row, col) {
        var cellID = getCellID(row, col);
        var elem = this.getCell(cellID);
        if (!elem) return;

        elem.value = '';
        if (row in this.data) delete this.data[row][col];
        if (row in this.calculated) delete this.calculated[row][col];
        this.clearDependants(cellID);
        this.dependants[cellID] = [];
    }

    clearDependants(id) {
        var deps = this.dependants[id];
        if (!deps) return;

        for (var i = 0; i < deps.length; i++) {
            let remDeps = this.dependencies[deps[i]];
            if (!remDeps) continue;
            let idx = remDeps.indexOf(id);
            if (idx !== -1) remDeps.splice(idx, 1);
        }

        if (!this.context) return;
        this.context.clearDependencies(this, id);
    }

    forceRerender() {
        // First, update the element to be the correct dimensions.
        var width = this.columnWidths.reduce((a, b) => a + b); // Get the width of each column
        width += DEFAULT_BORDER_WIDTH;
        // width -= this.width * DEFAULT_BORDER_WIDTH; // Account for border widths
        this.elem.style.width = width + 'px';

        while (this.elem.childNodes.length) {
            this.elem.removeChild(this.elem.firstChild);
        }
        this.cellCache = {};

        var workQueue = [];

        // Create each row and cell
        for (var i = 0; i < this.height; i++) {
            let row = document.createElement('div');
            row.style.width = width + 'px';
            row.className = 'websheet-row';
            if (i < 1) {
                row.className += ' websheet-row-sticky';
            }
            this.elem.appendChild(row);

            let rowDataCache = this.data[i] || [];
            let rowCalculatedCache = this.calculated[i] || [];
            let rowFormattingCache = this.formatting[i] || [];

            for (var j = 0; j < this.width; j++) {
                let cell = document.createElement('input');
                cell.className = 'websheet-cell';

                let cellWrapper = document.createElement('div');
                cellWrapper.className = 'websheet-cell-wrapper';
                cellWrapper.style.width = (this.columnWidths[j] - 1) + 'px';
                cellWrapper.appendChild(cell);

                row.appendChild(cellWrapper);

                cell.value = rowCalculatedCache[j] || rowDataCache[j] || '';
                cell.setAttribute('data-id', cell.title = getCellID(i, j));
                cell.setAttribute('data-id-prev-col', getCellID(i, j - 1));
                cell.setAttribute('data-id-prev-row', getCellID(i - 1, j));
                cell.setAttribute('data-id-next-col', getCellID(i, j + 1));
                cell.setAttribute('data-id-next-row', getCellID(i + 1, j));
                cell.setAttribute('data-row', i);
                cell.setAttribute('data-col', j);

                if (cell.value[0] === '=') {
                    workQueue.push(
                        this.setValueAtPosition.bind(this, i, j, cell.value, true)
                    );
{'fraction_non_alphanumeric': 0.13150250858199103, 'fraction_numerical': 0.0031687351465540005, 'mean_word_length': 1.7578736573821228, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5474646', 'n_tokens_mistral': 4445, 'n_tokens_neox': 4224, 'n_words': 954}