All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dashboard.dashboard.js Maven / Gradle / Ivy

The newest version!
let sheetsMap = new Map();

async function DecompressBlob(blob) {
    const ds = new DecompressionStream("gzip");
    const decompressedStream = blob.stream().pipeThrough(ds);
    return await new Response(decompressedStream).blob();
}

/**
 * https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
 * Convert a base64 string to a Blob object.
 * @param base64Data {string}
 * @param contentType {string}
 * @returns {Blob}
 */
function base64toBlob(base64Data, contentType = '') {
    contentType = contentType || '';
    const sliceSize = 1024;
    const byteCharacters = atob(base64Data);
    const bytesLength = byteCharacters.length;
    const slicesCount = Math.ceil(bytesLength / sliceSize);
    const byteArrays = new Array(slicesCount);

    for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        const begin = sliceIndex * sliceSize;
        const end = Math.min(begin + sliceSize, bytesLength);

        const bytes = new Array(end - begin);
        for (let offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, {type: contentType});
}

Element.prototype.isVisible = function (percentX, percentY) {
    let tolerance = 0.01; /* needed because the rects returned by getBoundingClientRect provides the position up to 10 decimals */
    /*if (percentX == null) {
        percentX = 100;
    }*/
    if (percentY == null) {
        percentY = 100;
    }

    let elementRect = this.getBoundingClientRect();
    let parentRects = [];
    let element = this;

    while (element.parentElement != null) {
        parentRects.push(element.parentElement.getBoundingClientRect());
        element = element.parentElement;
    }

    return parentRects.every(function (parentRect) {
        /*let visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
        let visiblePercentageX = visiblePixelX / elementRect.width * 100;*/
        let visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
        let visiblePercentageY = visiblePixelY / elementRect.height * 100;
        /* this would check if both x and y-wise the component is visible, but checking only y is enough for this case
         * return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY; */
        return visiblePercentageY + tolerance > percentY;
    });
};

let doNotAddToHistory = false;
let isFirstSheet = false;
let lastSheet = '';
let sheetRecursionCheck = '';
/* sheet id --> { decompressedSheet: string, decompressionTime: number } */
let decompressedSheetsCache = new Map();

function showSheet(sheetID) {
    if (sheetID == null) return;
    if (!sheetsMap.has(sheetID)) {
        if (sheetRecursionCheck === sheetID) {
            sheetRecursionCheck = null;
            return;
        }
        sheetRecursionCheck = defaultSheet;
        showSheet(defaultSheet);
        return;
    }
    if (isFirstSheet && lastSheet.length > 0) {
        isFirstSheet = false;
        return;
    }
    if (!doNotAddToHistory && lastSheet.length > 0) {
        addToHistory(lastSheet);
    }
    if (lastSheet.length > 0) {
        document.getElementById("nav-row-" + lastSheet).classList.remove("active");
        document.getElementById("navigation-tile-" + lastSheet).classList.remove("active");
        document.getElementById("nav-row-" + sheetID).classList.add("active");
        document.getElementById("navigation-tile-" + sheetID).classList.add("active");
    }
    lastSheet = sheetID;

    accessContentSheet(sheetID)
        .then(decompressedSheetData => {
            let sheetContainer = document.getElementById('sheet-container');
            sheetContainer.innerHTML = '';
            let element = document.createElement('span');

            const decompressedSheet = decompressedSheetData.decompressedSheet;
            if (decompressedSheet == null) {
                console.error('Failed to access and decompress sheet data: ', e);
                return;
            }

            element.innerHTML = decompressedSheet;
            sheetContainer.appendChild(element.firstChild);
            hideRequiredSheetParagraphs(false);
            nodeScriptReplace(sheetContainer);

            if (!document.getElementById("navigation-tile-" + sheetID).isVisible()) {
                let headerOffset = 200;
                let elementPosition = document.getElementById('navigation-tile-' + sheetID).getBoundingClientRect().top;
                let offsetPosition = elementPosition - headerOffset + document.getElementById('navigation-table').scrollTop;
                document.getElementById('navigation-table').scrollTo({
                    top: offsetPosition,
                    behavior: "smooth"
                });
            }
            doNotAddToHistory = false;
            updateGoBackArrow();
            document.title = sheetID + ' | ' + document.title.replace(/^.* \| /, '');

            updateURL();

            if (currentDisplayMode === 0) {
                setDisplayMode(1);
            }
            setContentSheetWidth();

            let keys = Array.from(sheetsMap.keys());
            let currentIndex = keys.indexOf(sheetID);
            let beforeIndex = currentIndex - 1;
            let nextIndex = currentIndex + 1;
            if (beforeIndex >= 0) {
                accessContentSheet(keys[beforeIndex]);
            }
            if (nextIndex < keys.length) {
                accessContentSheet(keys[nextIndex]);
            }
        })
        .catch(e => {
            console.error('Failed to access and decompress sheet data: ', e);
        });
}

function accessContentSheet(sheetID) {
    return new Promise((resolve, reject) => {
        if (decompressedSheetsCache.has(sheetID)) {
            // console.log('sheet cache hit:', sheetID);
            resolve(decompressedSheetsCache.get(sheetID));
        } else {
            const sheetCompressedContent = sheetsMap.get(sheetID);
            const startTimestamp = performance.now();
            DecompressBlob(base64toBlob(sheetCompressedContent))
                .then(e => {
                    e.text().then(decompressedSheetData => {
                        decompressedSheetsCache.set(sheetID, {
                            decompressedSheet: decompressedSheetData,
                            decompressionTime: performance.now()
                        });
                        console.log('sheet cache miss:', sheetID, 'in', performance.now() - startTimestamp, 'ms');
                        cleanupContentSheetCache();
                        resolve(decompressedSheetsCache.get(sheetID));
                    });
                })
                .catch(e => {
                    console.error('Failed to access and decompress sheet data: ', e);
                    reject(e);
                });
        }
    });
}

function cleanupContentSheetCache() {
    const maxCacheSize = 20;
    while (decompressedSheetsCache.size > maxCacheSize) {
        let oldestSheet = '';
        let oldestDecompressionTime = Number.MAX_VALUE;
        decompressedSheetsCache.forEach((value, key) => {
            if (value.decompressionTime < oldestDecompressionTime) {
                oldestSheet = key;
                oldestDecompressionTime = value.decompressionTime;
            }
        });
        decompressedSheetsCache.delete(oldestSheet);
    }
}

function nodeScriptReplace(node) {
    if (node != null) {
        if (node.tagName === 'SCRIPT') {
            node.parentNode.replaceChild(nodeScriptClone(node), node);
        } else {
            let i = -1, children = node.childNodes;
            while (++i < children.length) {
                nodeScriptReplace(children[i]);
            }
        }
    }

    return node;
}

function nodeScriptClone(node) {
    let script = document.createElement("script");
    script.text = node.innerHTML;

    let i = -1, attrs = node.attributes, attr;
    while (++i < attrs.length) {
        script.setAttribute((attr = attrs[i]).name, attr.value);
    }
    return script;
}

let historyStack = [];

function addToHistory(sheet) {
    if (historyStack.length === 0 || historyStack[historyStack.length - 1] !== sheet) {
        historyStack.push(sheet);
        adjustTopRightOverflowItems();
    }
    if (historyStack.length > 30) {
        historyStack.shift();
    }
}

function historyBackwards() {
    if (historyStack.length > 0) {
        doNotAddToHistory = true;
        showSheet(historyStack.pop());
        adjustTopRightOverflowItems();
    }
}

let goBackArrowIsVisible = false;

function updateGoBackArrow() {
    if (document.getElementsByClassName('previous-sheet-arrow').length > 0) {
        /* only show the go-back-arrow if there are entries to go back to and if there is only one make sure it is not the currently displayed one */
        if (historyStack.length > 0 && !(historyStack.length === 1 && historyStack[0] === lastSheet)) {
            document.getElementsByClassName('previous-sheet-arrow')[0].classList.remove('hidden');
            goBackArrowIsVisible = true;
        } else {
            document.getElementsByClassName('previous-sheet-arrow')[0].classList.add('hidden');
            goBackArrowIsVisible = false;
        }
    }
}

function hideRequiredSheetParagraphs(changeSliders) {
    let hideSheetDataGET = findGetParameter('hide');
    if (hideSheetDataGET != null) {
        let hideData = hideSheetDataGET.split("_");
        for (let i = 0; i < hideData.length; i++) {
            if (hideData[i].length > 0) {
                toggleDataSheetContentVisibility(hideData[i], changeSliders);
            }
        }
    }
}

function toggleDataSheetContentVisibility(contentType, toggleSliders) {
    let allContentTypeElements = document.getElementsByClassName('data-sheet-' + contentType);
    let dataSheetContentToggle = document.getElementById('data-sheet-content-toggle-' + contentType);
    for (let i = 0, max = allContentTypeElements.length; i < max; i++) {
        if (allContentTypeElements[i].classList.contains('hidden'))
            allContentTypeElements[i].classList.remove('hidden');
        else allContentTypeElements[i].classList.add('hidden');
    }

    if (toggleSliders && dataSheetContentToggle != null) {
        if (dataSheetContentToggle.hasAttribute('checked'))
            dataSheetContentToggle.removeAttribute('checked');
        else dataSheetContentToggle.setAttribute('checked', '');
        updateURL();
    }
}

let updateUrl = false;

function updateURL() {
    if (updateUrl) {
        let queryParams = new URLSearchParams(window.location.search);
        let hiddenData = '';
        for (let i = 0; i < visibilityToggleDataIdentifiers.length; i++) {
            if (document.getElementById(visibilityToggleDataIdentifiers[i]) == null) continue;
            if (!document.getElementById(visibilityToggleDataIdentifiers[i]).hasAttribute('checked')) {
                if (hiddenData.length > 0) hiddenData = hiddenData + '_';
                hiddenData = hiddenData + visibilityToggleDataIdentifiers[i].replace('data-sheet-content-toggle-', '');
            }
        }

        if (hiddenData.length > 0) queryParams.set('hide', trimCharacterFromString(hiddenData, '_'));
        else queryParams.delete('hide');

        if (lastSheet.length > 0) {
            queryParams.set('sheet', lastSheet.replaceAll('-', '_'));
        }

        let filters = [];
        for (let i = 0; i < navigationFilters.length; i++) {
            let filter = navigationFilters[i];
            filters.push(filter.column + ';' + filter.operation + ';' + filter.value.replace(',', 'REP00'));
        }
        if (filters.length > 0) {
            queryParams.set('f', filters.join(',').replaceAll('#', 'REP01').replaceAll(' ', 'REP02'));
        } else {
            queryParams.delete('f');
        }

        if (bookmarkedDataSheets.length > 0) {
            queryParams.set('bkm', bookmarkedDataSheets.join(','));
        } else {
            queryParams.delete('bkm');
        }


        if (tableIsSortedBy !== -1 && tableIsSortedBy != null) {
            queryParams.set('sort', tableIsSortedBy);
            queryParams.set('sortDir', tableIsSortedDirection);
        }

        history.replaceState(null, null, '?' + queryParams.toString());
    }
}

function findGetParameter(parameterName) {
    let result = null, tmp = [];
    let items = location.search.substr(1).split('&');
    for (let index = 0; index < items.length; index++) {
        tmp = items[index].split('=');
        if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]).replaceAll('REP01', '#').replaceAll('REP02', ' ');
    }
    return result;
}

let tableIsSortedBy = -1;
let tableIsSortedDirection = true;

function checkForTableRowsSortedInit() {
    tableIsSortedBy = findGetParameter('sort');
    if (tableIsSortedBy != null && findGetParameter('sortDir') != null) {
        tableIsSortedDirection = findGetParameter('sortDir') === 'true';
        let tableElement = document.getElementById('navigation-table-table');
        if (tableElement != null) {
            sortTableRowsByColumn(tableElement, tableIsSortedBy, tableIsSortedDirection);
            document.getElementById('header-table-id-' + tableIsSortedBy).dataset['sort'] = tableIsSortedDirection ? 'asc' : 'desc';
        }
    }
}

function sortTableRowsByColumn(table, columnIndex, ascending) {
    if (table == null) return;

    const rows = Array.from(table.getElementsByTagName('tbody')[0].getElementsByTagName('tr'));

    rows.sort((x, y) => {
        let xValue = x.cells[columnIndex].textContent;
        let yValue = y.cells[columnIndex].textContent;

        if (!isNaN(xValue) && !isNaN(yValue)) {
            xValue = parseFloat(xValue);
            yValue = parseFloat(yValue);
        } else if (xValue != null && yValue != null) {
            let xValueTmp = xValue.toString().replace(/^ *(\d+).*$/, '$1');
            let yValueTmp = yValue.toString().replace(/^ *(\d+).*$/, '$1');
            if (!isNaN(xValueTmp) && !isNaN(yValueTmp)) {
                xValue = parseFloat(xValueTmp);
                yValue = parseFloat(yValueTmp);
            }
        }

        if (xValue === yValue) {
            const identifierX = x.cells[0].textContent;
            const identifierY = y.cells[0].textContent;
            if (ascending) {
                return identifierX > identifierY ? 1 : -1;
            } else {
                return identifierX < identifierY ? 1 : -1;
            }
        }
        if (ascending) {
            if (xValue === "N/A")
                return -1;
            if (yValue === "N/A")
                return 1;
            return xValue > yValue ? 1 : -1;
        } else {
            if (xValue === "N/A")
                return 1;
            if (yValue === "N/A")
                return -1;
            return xValue < yValue ? 1 : -1;
        }
    });

    for (let row of rows) {
        table.tBodies[0].appendChild(row);
    }

    let sortedIndicatorElements = document.getElementsByClassName('header-table-sort');
    for (let i = 0, max = sortedIndicatorElements.length; i < max; i++) {
        sortedIndicatorElements[i].classList.remove('btn-primary');
        sortedIndicatorElements[i].classList.remove('btn-success');
        sortedIndicatorElements[i].classList.add('btn-secondary');
    }

    sortedIndicatorElements = document.getElementsByClassName('navigation-header-tile');
    for (let i = 0, max = sortedIndicatorElements.length; i < max; i++) {
        sortedIndicatorElements[i].classList.remove('descending');
        sortedIndicatorElements[i].classList.remove('ascending');
    }
    let sortArrow = document.getElementById('navigation-table-sort-dir-arrow');
    if (sortArrow !== null) {
        sortArrow.remove();
    }

    let sortedIndicatorElement = document.getElementById('header-table-sort-' + columnIndex);
    let sortedIndicatorTableElement = document.getElementById('header-table-id-' + columnIndex);
    sortedIndicatorElement.classList.remove('btn-secondary');
    if (ascending) {
        sortedIndicatorElement.classList.add('btn-success');
        sortedIndicatorTableElement.classList.add('ascending');
        sortArrow = document.createElement('span');
        sortArrow.innerHTML = getSvg('arrow-down', 14, 'var(--strong-dark-green)');
    } else {
        sortedIndicatorElement.classList.add('btn-primary');
        sortedIndicatorTableElement.classList.add('descending');
        sortArrow = document.createElement('span');
        sortArrow.innerHTML = getSvg('arrow-up', 14, 'var(--strong-dark-blue)');
    }
    sortArrow.id = 'navigation-table-sort-dir-arrow';
    sortArrow.style.marginLeft = '3px';
    sortedIndicatorTableElement.appendChild(sortArrow);

    tableIsSortedBy = columnIndex;
    tableIsSortedDirection = ascending;
    updateURL();
}

function onColumnHeaderClicked(th) {
    const table = th.parentElement.parentElement.parentElement;
    const thIndex = Array.from(th.parentElement.children).indexOf(th);
    const descending = !('sort' in th.dataset) || th.dataset.sort !== 'desc';

    const start = performance.now();
    sortTableRowsByColumn(table, thIndex, !descending);
    const end = performance.now();
    console.log("Sorted table rows in %d ms.", end - start);

    const allTh = table.querySelectorAll(':scope > thead > tr > th');
    for (let th2 of allTh) {
        delete th2.dataset['sort'];
    }

    th.dataset['sort'] = descending ? 'desc' : 'asc';
}

function onColumnHeaderRightClicked(th) {
    addFilter(th.innerText, '', '');
    openModal('settingsModal');
}

function setNavigationVisible(visible) {
    let navItem = document.getElementById('navigation-table');
    if (visible) navItem.classList.remove('hidden');
    else navItem.classList.add('hidden');
}

function setSheetsVisible(visible) {
    let sheet = document.getElementsByClassName('right-sheet');
    for (let i = 0, max = sheet.length; i < max; i++) {
        if (visible) sheet[i].classList.remove('hidden');
        else sheet[i].classList.add('hidden');
    }
}

let navigationFullScreenStoredWidth = '0px';

function setNavigationFullScreen(active) {
    let navItem = document.getElementById('navigation-table');
    if (active) {
        navItem.classList.add('full-screen');
        navItem.classList.remove('horizontal-resize');
        navigationFullScreenStoredWidth = navItem.style.width;
        navItem.style.removeProperty('width');
    } else {
        navItem.classList.remove('full-screen');
        navItem.classList.add('horizontal-resize');
        let widthDiff = window.innerWidth - navigationFullScreenStoredWidth.replace('px', '');
        if (navigationFullScreenStoredWidth !== '0px' && widthDiff > 130) {
            navItem.style.width = navigationFullScreenStoredWidth;
        }
    }
}

let currentDisplayMode = 0;

function nextDisplayMode() {
    currentDisplayMode = Math.max(0, currentDisplayMode - 1);
    setDisplayMode(currentDisplayMode);
}

function previousDisplayMode(allowOverflow) {
    if (allowOverflow) currentDisplayMode = (currentDisplayMode + 1) % 3;
    else currentDisplayMode = Math.min(2, currentDisplayMode + 1);
    setDisplayMode(currentDisplayMode);
}

function setDisplayMode(displayMode) {
    currentDisplayMode = displayMode;
    console.log('Setting display mode to ' + currentDisplayMode);
    if (displayMode === 0) {
        setNavigationVisible(true);
        setSheetsVisible(false);
        setNavigationFullScreen(true);
    } else if (displayMode === 1) {
        setNavigationVisible(true);
        setSheetsVisible(true);
        setNavigationFullScreen(false);
        if (lastSheet === '') {
            showSheet(defaultSheet);
        } else {
            showSheet(lastSheet);
        }
    } else if (displayMode === 2) {
        setNavigationVisible(false);
        setSheetsVisible(true);
        setNavigationFullScreen(false);
        showSheet(lastSheet);
    }
}

function isAnyModalVisible() {
    let modals = document.getElementsByClassName('modal');
    if (modals == null) return false;
    for (let i = 0; i < modals.length; i++) {
        if (modals[i].style.display !== 'none') return true;
    }
    return false;
}

function isModalVisible(modalId) {
    let current = document.getElementById(modalId).style.display;
    return current !== 'none' && current !== '';
}

function toggleModal(modalId) {
    if (isModalVisible(modalId)) closeModal(modalId);
    else openModal(modalId);
}

function closeAllModals() {
    let modals = document.getElementsByClassName('modal');
    if (modals == null) return;
    for (let i = 0; i < modals.length; i++) {
        modals[i].style.display = 'none';
    }
    overwriteFilterUid = -1;
}

function openModal(modalId) {
    closeAllModals();
    let modal = document.getElementById(modalId);
    if (modal == null) return;
    modal.style.display = 'block';

    if (onOpenModalScripts && onOpenModalScripts[modalId]) {
        onOpenModalScripts[modalId]();
    }
}

function closeModal(modalId) {
    let modal = document.getElementById(modalId);
    if (modal == null) return;
    modal.style.display = 'none';
    overwriteFilterUid = -1;
}

/* new search and filter */

function getAllColumnsNames() {
    let columns = [];
    let navigationHeaders = document.getElementsByClassName('navigation-header-tile');
    for (let k = 0; k < navigationHeaders.length; k++) {
        columns.push(normalizeHeader(navigationHeaders[k].innerText));
    }
    return columns;
}

function capitalizeWords(str) {
    return str.replace(/\w\S*/g, function (txt) {
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
}

function updateFiltersInSettings() {
    let displayFilterListing = document.getElementById('display-filter-listing');
    let displayFilterClearAll = document.getElementById('display-filter-clear-filters');

    displayFilterListing.innerHTML = '';

    if (navigationFilters.length === 0) {
        displayFilterListing.classList.add('hidden');
        displayFilterClearAll.classList.add('hidden');
    } else {
        displayFilterListing.classList.remove('hidden');
        displayFilterClearAll.classList.remove('hidden');
        for (let i = 0; i < navigationFilters.length; i++) {
            let filter = navigationFilters[i];
            displayFilterListing.appendChild(createFilterListingEntry(filter));
        }
    }

    let filterIndicator = document.getElementsByClassName('navigation-table-filter-indicator');
    for (let i = filterIndicator.length - 1; i >= 0; i--) {
        filterIndicator[i].parentNode.removeChild(filterIndicator[i]);
    }
    let columnsWithIndicator = [];
    for (let i = 0; i < navigationFilters.length; i++) {
        filterIndicator = document.createElement('span');
        filterIndicator.innerHTML = getSvg('filter', 16, 'var(--strong-yellow)');
        filterIndicator.classList.add('navigation-table-filter-indicator');
        filterIndicator.style.marginLeft = '3px';
        let filter = navigationFilters[i];
        let columnIndex = findNavigationTableColumnIndex(filter.column);
        if (columnIndex !== -1 && columnsWithIndicator.indexOf(columnIndex) === -1) {
            let tableElement = document.getElementById('header-table-id-' + columnIndex);
            tableElement.appendChild(filterIndicator);
            columnsWithIndicator.push(columnIndex);
        }
    }
}

function createFilterListingEntry(filter) {
    let container = document.createElement('span');

    let remove = document.createElement('span');
    remove.classList.add('badge');
    remove.classList.add('badge-danger');
    remove.innerHTML = getSvg('trash3', 10);
    remove.style.cursor = 'pointer';
    remove.onclick = function () {
        removeFilterByUID(filter.uid);
    }

    let filterElement = document.createElement('span');
    filterElement.style.marginLeft = '4px';

    let selectColumnElement = document.createElement('select');
    selectColumnElement.classList.add('select-input-field');
    let columns = getAllColumnsNames();
    columns.push('sheet content');
    for (let i = 0; i < columns.length; i++) {
        let option = document.createElement('option');
        option.value = columns[i];
        option.innerText = capitalizeWords(columns[i]);
        selectColumnElement.appendChild(option);
        if (compareNormalizedString(filter.column, columns[i])) {
            selectColumnElement.value = columns[i];
        }
    }
    selectColumnElement.onchange = function () {
        filter.column = selectColumnElement.value;
        updateFiltersInSettings();
        applyFilters();
    }
    filterElement.appendChild(selectColumnElement);

    let selectOperationElement = document.createElement('select');
    selectOperationElement.classList.add('select-input-field');
    let operations = FILTER_OPERATIONS;
    for (let i = 0; i < operations.length; i++) {
        let option = document.createElement('option');
        option.value = operations[i];
        option.innerText = capitalizeWords(operations[i]);
        selectOperationElement.appendChild(option);
        if (filter.operation === operations[i]) {
            selectOperationElement.value = operations[i];
        }
    }
    selectOperationElement.onchange = function () {
        filter.operation = selectOperationElement.value;
        updateFiltersInSettings();
        applyFilters();
    }
    filterElement.appendChild(selectOperationElement);

    let inputValueElement = document.createElement('input');
    inputValueElement.classList.add('text-input-field');
    inputValueElement.type = 'text';
    inputValueElement.value = filter.value;
    inputValueElement.onchange = function () {
        filter.value = inputValueElement.value;
        updateFiltersInSettings();
        applyFilters();
    }
    filterElement.appendChild(inputValueElement);

    container.appendChild(remove);
    container.appendChild(filterElement);

    container.appendChild(document.createElement('br'));

    return container;
}

function addFilter(column, operation, value) {
    if (column === undefined || column === null || column === '') column = getAllColumnsNames()[0];
    if (operation === undefined || operation === null || operation === '') operation = FILTER_OPERATIONS[0];
    if (value === undefined || value === null) value = '';
    let columns = getAllColumnsNames();
    for (let i = 0; i < columns.length; i++) {
        if (compareNormalizedString(column, columns[i])) {
            column = columns[i];
            break;
        }
    }
    console.log('Created filter for ' + column);
    navigationFilters.push(new Filter(column, operation, value));
    updateFiltersInSettings();
    applyFilters();
}

function normalizeHeader(header) {
    return header
        .replaceAll('\n', ' ')
        .replaceAll('- ', '')
        .replaceAll('-', '')
        .toLowerCase();
}

function compareNormalizedString(c1, c2) {
    return normalizeHeader(c1) === normalizeHeader(c2);
}

const navigationFilters = [];
let filteredElementCount = sheetsMap.size;

class Filter {
    constructor(column, operation, value) {
        this.column = column;
        this.operation = operation;
        this.value = value;
        this.uid = Math.floor(Math.random() * 100000);
    }
}

Filter.prototype.toString = function () {
    let middle;
    switch (this.operation) {
        case 'not contains':
            middle = 'does not contain';
            break;
        case 'equal':
        case 'not equal':
            middle = 'is ' + this.operation + ' to';
            break;
        case 'larger':
        case 'smaller':
            middle = 'is ' + this.operation + ' than';
            break;
        case 'larger/equal':
        case 'smaller/equal':
            middle = 'is ' + this.operation.replace('/', ' or ') + ' to';
            break;
        default:
            middle = this.operation;
    }
    return this.column + ' ' + middle + ' ' + this.value;
}

function clearAllFilters() {
    navigationFilters.length = 0;
    updateFiltersInSettings();
    applyFilters();
}

function removeFilterByUID(uid) {
    for (let i = 0; i < navigationFilters.length; i++) {
        if (navigationFilters[i].uid === uid) {
            navigationFilters.splice(i, 1);
        }
    }
    updateFiltersInSettings();
    applyFilters();
}

function applyFilters() {
    filteredElementCount = 0;
    let navigationListElements = document.getElementsByClassName('navigation-entry');
    for (let i = 0, max = navigationListElements.length; i < max; i++) {
        let shouldBeVisible = true;
        for (let j = 0; j < navigationFilters.length; j++) {
            let filter = navigationFilters[j];

            let checkContent = '';
            if (compareNormalizedString(filter.column, 'sheet content')) {
                checkContent = sheetsMap.get(navigationListElements[i].id.replace('nav-row-', ''));
            } else {
                let headerIndex = findNavigationTableColumnIndex(filter.column);
                if (headerIndex !== -1) {
                    checkContent = navigationListElements[i].childNodes[headerIndex].innerText;
                }
            }

            checkContent = checkContent.toLowerCase();
            let compareValue = filter.value.toLowerCase();

            switch (filter.operation) {
                case 'contains':
                    shouldBeVisible &= checkContent.indexOf(compareValue) !== -1;
                    break;
                case 'not contains':
                    shouldBeVisible &= checkContent.indexOf(compareValue) === -1;
                    break;
                case 'equal':
                    shouldBeVisible &= checkContent === compareValue;
                    break;
                case 'not equal':
                    shouldBeVisible &= checkContent !== compareValue;
                    break;
                case 'larger':
                    shouldBeVisible &= getNumberFromString(checkContent) > getNumberFromString(compareValue);
                    break;
                case 'larger/equal':
                    shouldBeVisible &= getNumberFromString(checkContent) >= getNumberFromString(compareValue);
                    break;
                case 'smaller/equal':
                    shouldBeVisible &= getNumberFromString(checkContent) <= getNumberFromString(compareValue);
                    break;
                case 'smaller':
                    shouldBeVisible &= getNumberFromString(checkContent) < getNumberFromString(compareValue);
                    break;
                default:
                    break;
            }
            if (!shouldBeVisible) break;
        }

        if (shouldBeVisible) {
            navigationListElements[i].classList.remove('hidden');
            filteredElementCount++;
        } else {
            navigationListElements[i].classList.add('hidden');
        }
    }

    updateURL();
    try {
        onFilterApplied();
    } catch (e) {
    }
}

function getNumberFromString(str) {
    let extracted = str.replace(/(?:[\s\S]*?)(-?\d+\.?\d*)(?:[\s\S]*)/gi, '$1');
    if (extracted.length > 0) {
        return parseFloat(extracted);
    }
    return NaN;
}

function findNavigationTableColumnIndex(columnName) {
    let navigationHeaders = document.getElementsByClassName('navigation-header-tile');
    columnName = columnName.replaceAll('-', '').replaceAll('\n', '').replaceAll(' ', '').toLowerCase();
    for (let k = 0; k < navigationHeaders.length; k++) {
        let compareHeader = navigationHeaders[k].innerText.replaceAll('-', '').replaceAll('\n', '').replaceAll(' ', '').toLowerCase();
        if (compareHeader === columnName) {
            return k;
        }
    }
    return -1;
}

function loadFiltersFromGetValues() {
    let filters = findGetParameter('f');
    if (filters !== undefined && filters !== null && filters.length > 0) {
        createFilterFromGETValue(filters);
    }
}

function createFilterFromGETValue(getValue) {
    let split = getValue.split(',');
    for (let i = 0; i < split.length; i++) {
        let filterAttributes = split[i].split(';');
        if (filterAttributes.length === 3) {
            let column = filterAttributes[0];
            let operation = filterAttributes[1];
            let value = filterAttributes[2].replace('REP00', ',');
            navigationFilters.push(new Filter(column, operation, value));
        }
    }
    updateFiltersInSettings();
    applyFilters();
}

/* search and filter over */

function getSvg(id, size, color = 'currentColor') {
    switch (id) {
        case "pencil":
            return '' +
                '' +
                '';
        case "trash3":
            return '' +
                '' +
                '';
        case "arrow-up":
            return '' +
                '' +
                '';
        case "arrow-down":
            return '' +
                '' +
                '';
        case "filter":
            return '' +
                '' +
                '';
    }
}

function nextVulnerability() {
    if (!canUseNavigationBarShortcuts()) return;
    let current = getCurrentNavigationElement();
    let tableBody = current.parentElement;
    let index = Array.prototype.indexOf.call(tableBody.children, current);

    for (let i = 0; i < 300; i++) {
        index = (index + 1) % tableBody.children.length;
        if (!tableBody.children[index].classList.contains('hidden')) {
            break;
        }
    }

    eventFire(tableBody.children[index], 'click');
}

function previousVulnerability() {
    if (!canUseNavigationBarShortcuts()) return;
    let current = getCurrentNavigationElement();
    let tableBody = current.parentElement;
    let index = Array.prototype.indexOf.call(tableBody.children, current);


    for (let i = 0; i < 300; i++) {
        index -= 1;
        if (index < 0) index = tableBody.children.length - 1;
        if (!tableBody.children[index].classList.contains('hidden')) {
            break;
        }
    }

    eventFire(tableBody.children[index], 'click');
}

let timeoutForNavigationStart = Date.now();

function canUseNavigationBarShortcuts() {
    if (Date.now() - timeoutForNavigationStart > 140) {
        timeoutForNavigationStart = Date.now();
        return true;
    }
    return false;
}

function getCurrentNavigationElement() {
    return document.getElementById('navigation-tile-' + lastSheet).parentElement.parentElement;
}

function eventFire(el, etype) {
    if (el.fireEvent) {
        el.fireEvent('on' + etype);
    } else {
        let evObj = document.createEvent('Events');
        evObj.initEvent(etype, true, false);
        el.dispatchEvent(evObj);
    }
}

document.addEventListener('keyup', (e) => {
    if (isAnyModalVisible()) {
        if (e.code === 'Escape') {
            closeAllModals();
        } else if (e.code === 'Enter') {
            if (doesUserNotSelectInputElement()) {
                closeAllModals();
            }
        }
    }
});

document.addEventListener('keydown', (e) => {
    if (doesUserNotSelectInputElement()) {
        let actionFound = false;
        if (e.altKey && e.code === 'ArrowLeft') {
            previousDisplayMode(false);
            actionFound = true;
        } else if (e.altKey && e.code === 'ArrowRight') {
            nextDisplayMode();
            actionFound = true;
        } else if (e.altKey && e.code === 'ArrowDown') {
            nextVulnerability(false);
            actionFound = true;
        } else if (e.altKey && e.code === 'ArrowUp') {
            previousVulnerability();
            actionFound = true;
        } else if (e.altKey && e.code === 'KeyS') {
            openModal('settingsModal');
            findOrCreateSheetContentContainsFilter();
            actionFound = true;
        } else if (e.code === 'KeyS') {
            toggleModal('settingsModal');
            actionFound = true;
        } else if (e.code === 'Backspace') {
            historyBackwards();
            actionFound = true;
        }
        if (actionFound) {
            e.preventDefault();
        }
    }
});

function doesUserNotSelectInputElement() {
    return document.activeElement === undefined || document.activeElement === null || (document.activeElement.tagName.toLowerCase() !== 'button' && document.activeElement.tagName.toLowerCase() !== 'input' && document.activeElement.tagName.toLowerCase() !== 'textarea');
}

function findOrCreateSheetContentContainsFilter() {
    if (!navigationFilters
        .filter(filter => filter.column === 'sheet content' && filter.operation === 'contains')
        .length > 0) {
        addFilter('sheet content', 'contains', '');
    }

    let filterContainer = document.getElementById('display-filter-listing');
    for (let i = 0; i < filterContainer.children.length; i++) {
        let filter = filterContainer.children[i];
        let filterColumn = filter.getElementsByClassName('select-input-field')[0];
        let filterOperation = filter.getElementsByClassName('select-input-field')[1];
        let filterValue = filter.getElementsByClassName('text-input-field')[0];
        if (filterColumn.value === 'sheet content' && filterOperation.value === 'contains') {
            filterValue.focus();
        }
    }
}

/* saving data-sheet entries */
let bookmarkedDataSheets = [];

function loadDataSheetBookmark() {
    let saved = findGetParameter('bkm');

    if (saved !== undefined && saved !== null && saved.length > 0) {
        let split = saved.split(',');
        for (let i = 0; i < split.length; i++) {
            bookmarkedDataSheets.push(split[i]);
        }
    }

    updateBookmarkedDataSheets(null);
}

function toggleDataSheetBookmark(sheetID) {
    if (!sheetsMap.has(sheetID)) {
        return;
    }

    let index = bookmarkedDataSheets.indexOf(sheetID);
    if (index === -1) {
        bookmarkedDataSheets.push(sheetID);
    } else {
        bookmarkedDataSheets.splice(index, 1);
    }

    updateBookmarkedDataSheets(sheetID);
    updateURL();
}

function updateBookmarkedDataSheets(currentSheetID) {
    if (currentSheetID !== undefined && currentSheetID !== null) {
        document.getElementById("navigation-tile-" + currentSheetID).classList.remove("special-highlighted");
    }

    for (let i = 0; i < bookmarkedDataSheets.length; i++) {
        document.getElementById("navigation-tile-" + bookmarkedDataSheets[i]).classList.add("special-highlighted");
    }
}

/* saving data-sheet entries over*/

function getDashboardTitleWidth() {
    /* (title width + subtitle width) + margin left + gap */
    return document.getElementById('dashboard-full-title').offsetWidth + 30 + 8;
}

function topRightBadgesOverhangToggle() {
    let overhangElement = document.getElementById("top-right-badges-overhang");
    if (overhangElement) {
        overhangElement.classList.toggle('hidden');
    }
}

function setTopRightBadgesOverhangVisible(visible) {
    if (isMobile()) return;
    let overhangElement = document.getElementById("top-right-badges-overhang");
    if (overhangElement) {
        if (visible && overhangElement.classList.contains('hidden')) {
            overhangElement.classList.remove('hidden');
        } else if (!visible && !overhangElement.classList.contains('hidden')) {
            overhangElement.classList.add('hidden');
        }
    }
}

function isMobile() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}

let topRightIconSize = 22;

function adjustTopRightOverflowItems() {
    let width = window.innerWidth;
    let mainContainer = document.getElementById('top-right-badges');
    let overflowContainer = document.getElementById('top-right-badges-overhang');
    let storageContainer = document.getElementById('top-right-badges-storage');

    if (mainContainer && overflowContainer && storageContainer) {
        mainContainer.innerHTML = '';
        mainContainer.classList.add('hidden');
        overflowContainer.innerHTML = '';
        overflowContainer.classList.add('hidden');

        let storedItems = storageContainer.childNodes;
        let amountItems = storedItems.length - 1;
        let elementWidth = 14 + topRightIconSize; /* flex-gap + own width */
        let requiredWidth = amountItems * elementWidth + 30 + 20 + elementWidth * 2; /* items * width + container box margin right + container box padding + two at least item widths space */
        let availableSpace = width - getDashboardTitleWidth(); /* width - title width */
        let overlappingSpace = availableSpace - requiredWidth;
        let overlapItems = Math.abs(Math.min(0, Math.ceil(overlappingSpace / elementWidth))); /* figure out how many items overlap the reserved space */
        let amountItemsAdded = 0;

        let goBackArrowIsInOverflow = false;
        for (let i = 0; i < storedItems.length; i++) {
            let storedItem = storedItems[i];
            if (storedItem.getAttribute('id') === 'top-right-items-show-more-storage') {
                continue;
            }
            if (amountItemsAdded >= overlapItems) {
                mainContainer.appendChild(storedItem.cloneNode(true));
            } else {
                overflowContainer.appendChild(storedItem.cloneNode(true));
                if (!goBackArrowIsInOverflow && storedItem.classList.contains('previous-sheet-arrow')) {
                    goBackArrowIsInOverflow = true;
                }
            }
            amountItemsAdded++;
        }

        updateGoBackArrow();

        if (overflowContainer.childNodes.length > 0 && !(!goBackArrowIsVisible && goBackArrowIsInOverflow && overflowContainer.childNodes.length === 1)) {
            let threeDots = document.getElementById('top-right-items-show-more-storage');
            if (threeDots) {
                threeDots = threeDots.cloneNode(true);
                threeDots.setAttribute('id', 'top-right-items-show-more');
                mainContainer.appendChild(threeDots);
            }
        }

        mainContainer.classList.remove('hidden');
    } else if (mainContainer) {
        mainContainer.classList.add('hidden');
    }
}

function trimCharacterFromString(s, c) {
    return s.replace(new RegExp(
        '^[' + c + ']+|[' + c + ']+$', 'g'
    ), '');
}

function copyToClipboard(text) {
    try {
        navigator.clipboard.writeText(text);
        return true;
    } catch (e) {
        window.prompt("Copy to clipboard: Ctrl+C, Enter", text);
    }
}

/* checks up to three layers upwards whether there is a node that has the tooltip attribute */
function getTooltipAttributeFromParent(e) {
    let tooltipText = null;
    try {
        tooltipText = e.target.getAttribute('tooltip');
        if (!tooltipText && e.target.parentNode !== undefined) {
            tooltipText = e.target.parentNode.getAttribute('tooltip');
            if (!tooltipText && e.target.parentNode.parentNode !== undefined) {
                tooltipText = e.target.parentNode.parentNode.getAttribute('tooltip');
                if (!tooltipText && e.target.parentNode.parentNode.parentNode !== undefined) {
                    tooltipText = e.target.parentNode.parentNode.parentNode.getAttribute('tooltip');
                }
            }
        }
    } catch (e) {
    }
    return tooltipText;
}

/* a listener whether the screen size has been adjusted */
window.addEventListener('resize', function (e) {
    adjustTopRightOverflowItems();
    setContentSheetWidth();
}, true);

function setContentSheetWidth() {
    let contentSheets = document.getElementsByClassName("content-sheet");


    if (contentSheets.length > 0) {
        let contentSheet = contentSheets[0];
        let sheetContainerRect = contentSheet.getBoundingClientRect();
        let left = sheetContainerRect.left;
        let screenWidth = window.innerWidth;

        contentSheet.style.width = (screenWidth - left - 31) + "px";
    }
}

const TOOLTIP_OFFSET = 7;

/* a global hover listener for all elements */
document.addEventListener('mousemove', function (e) {
    handleTooltipHover(e);

    if (e.target === undefined || e.target === null || e.target.getAttribute === undefined) {
        return;
    }
    const topRightBadgesOverhang = document.getElementById('top-right-badges-overhang');

    /* check if user hovers over the show more icon in the top right menu bar */
    if (e.target.getAttribute('id') === 'top-right-items-show-more'
        || (isDomElement(e.target.parentNode) && e.target.parentNode.getAttribute('id') === 'top-right-items-show-more')) {
        setTopRightBadgesOverhangVisible(true);
    } else if (topRightBadgesOverhang && !topRightBadgesOverhang.classList.contains('hidden')) {
        let threeDots = document.getElementById('top-right-items-show-more');
        if (threeDots && distanceFromNodeToPosition(threeDots, e.clientX, e.clientY) > 300) {
            setTopRightBadgesOverhangVisible(false);
        }
    }
});

function handleTooltipHover(e) {
    let tooltip = document.getElementById('tooltip');

    /* do not change the tooltip if the alt key is pressed */
    if (tooltip && e.altKey) {
        return;
    }

    /* check if user hovers an element with a tooltip attribute */
    let tooltipContent = getTooltipAttributeFromParent(e);

    if (tooltipContent) {
        let tooltipHasToBeAdded = !tooltip;
        if (tooltipHasToBeAdded) {
            tooltip = document.createElement('div');
            tooltip.setAttribute('id', 'tooltip');
            tooltip.style.visibility = 'hidden';
        }
        tooltip.innerHTML = tooltipContent;
        /* respect the bounding box of the tooltip and the screen
           give it an offset of 10px */
        let tooltipWidth = tooltip.offsetWidth + TOOLTIP_OFFSET;
        let tooltipHeight = tooltip.offsetHeight + TOOLTIP_OFFSET;
        let tooltipX = e.clientX + TOOLTIP_OFFSET;
        let tooltipY = e.clientY + TOOLTIP_OFFSET;
        /* if the tooltip would be off the screen, move it to the left */
        if (tooltipX + tooltipWidth > window.innerWidth) {
            tooltipX = e.clientX - tooltipWidth - TOOLTIP_OFFSET + 3;
        }
        /* if the tooltip would be off the screen, move it to the top */
        if (tooltipY + tooltipHeight > window.innerHeight) {
            /* if this would lead to the tooltip being out of the top, do not perform this action
               but rather make the tooltip wider */
            if (e.clientY - tooltipHeight - TOOLTIP_OFFSET < 0) {
                tooltip.style.maxWidth = '900px';
            } else {
                tooltipY = e.clientY - tooltipHeight - TOOLTIP_OFFSET;
            }
        }
        tooltip.style.left = tooltipX + 'px';
        tooltip.style.top = tooltipY + 'px';

        if (tooltipHasToBeAdded) {
            document.body.appendChild(tooltip);
            handleTooltipHover(e);
        } else {
            tooltip.style.visibility = 'visible';
        }
    } else if (tooltip) {
        tooltip.remove();
    }
}

function isDomElement(obj) {
    try {
        return obj instanceof HTMLElement;
    } catch (e) {
        return (typeof obj === "object") &&
            (obj.nodeType === 1) && (typeof obj.style === "object") &&
            (typeof obj.ownerDocument === "object");
    }
}

function distanceFromNodeToPosition(node, x, y) {
    let viewportOffset = node.getBoundingClientRect();
    return Math.sqrt(Math.pow(x - viewportOffset.left, 2) + Math.pow(y - viewportOffset.top, 2));
}

/* observe the width of the navigation table */
function addNavigationResizeListener() {
    let navigation = document.getElementById('navigation-table');
    let lastWidth = navigation.style.width.replace('px', '');
    setInterval(function () {
        let newWidth = navigation.style.width.replace('px', '');
        if (lastWidth !== newWidth) {
            navigationResized(newWidth);
            lastWidth = newWidth;
        }
    }, 500);
}

function navigationResized(newWidth) {
    let widthDiff = window.innerWidth - newWidth;
    /* Check if the navigation is full screen, change to full screen navigation layout for this */
    if (widthDiff < 120 && currentDisplayMode !== 0) {
        setDisplayMode(0);
    }
    setContentSheetWidth();
}

/* DARK MODE START */

let manualDarkModeState = localStorage.getItem('ae-vad-manual-dark-mode'); // null, true, false
let currentDarkModeState = null;
let darkModeSwitchTimeout = false;

if (manualDarkModeState === null) {
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
        const newColorScheme = event.matches ? "dark" : "light";
        setDarkMode(newColorScheme === "dark");
    });
} else {
    setDarkMode(manualDarkModeState === "true" || manualDarkModeState === true);
}

function setDarkMode(darkMode) {
    darkModeSwitchTimeout = setTimeout(function () {
        console.log("Dark mode:", darkMode);
        if (darkMode) {
            try {
                Chart.defaults.backgroundColor = '#00000000';
                Chart.defaults.borderColor = 'rgba(170, 170, 170, 0.2)';
                Chart.defaults.color = '#FFF';
                Chart.defaults.scale.ticks.backdropColor = "#00000000";
            } catch (e) {
            }
        } else {
            try {
                Chart.defaults.backgroundColor = '#00000000';
                Chart.defaults.borderColor = 'rgba(0, 0, 0, 0.2)';
                Chart.defaults.color = '#000';
                Chart.defaults.scale.ticks.backdropColor = "rgba(255, 255, 255, 0.75)";
            } catch (e) {
            }
        }

        const switchElement = document.getElementById("dark-mode-switch");
        if (switchElement) {
            let key = darkMode ? 'svgDarkMode' : 'svgLightMode';
            switchElement.innerHTML = switchElement.dataset[key];
            if (currentDarkModeState === null && darkMode) {
                setDarkMode(darkMode);
            }
        }

        if (darkMode) {
            document.documentElement.classList.remove("ae-light");
            document.documentElement.classList.add("ae-dark");
        } else if (!darkMode) {
            document.documentElement.classList.remove("ae-dark");
            document.documentElement.classList.add("ae-light");
        }

        if (lastSheet !== null && lastSheet !== "") {
            showSheet(lastSheet);
        }

        currentDarkModeState = darkMode;

        try {
            onThemeChanged(darkMode);
        } catch (e) {
        }
    }, 100);
}

function toggleManualDarkMode() {
    currentDarkModeState = !currentDarkModeState;
    manualDarkModeState = currentDarkModeState;
    localStorage.setItem('ae-vad-manual-dark-mode', manualDarkModeState);
    setDarkMode(manualDarkModeState);
}

/* DARK MODE END */

/* COPY DATA START */

function copyContentSheetNames() {
    const navigationListElements = document.getElementsByClassName('navigation-entry');
    const names = Array.from(navigationListElements)
        .filter(e => !e.classList.contains('hidden'))
        .map(e => e.childNodes[0])
        .map(e => e.innerText);
    const csv = names.join(', ');
    copyTextToClipboard(csv);
}

function copyTextToClipboard(text) {
    if (navigator.clipboard && window.isSecureContext) {
        navigator.clipboard.writeText(text).then(() => {
        }).catch(err => {
            console.error("Could not copy text: ", err);
            fallbackCopyTextToClipboard(text);
        });
    } else {
        fallbackCopyTextToClipboard(text);
    }
}

function fallbackCopyTextToClipboard(text) {
    prompt('Copy to clipboard: Ctrl+C, Enter', text);
}

/* COPY DATA END */

function checkForIdExistsValidation(id, elementName) {
    if (document.getElementById(id) === null) {
        console.error(elementName, ' (id: ' + id + ') not found');
    }
}

function onLoad() {
    console.log('Starting loading process for VAD');

    console.log('Running validation checks');
    checkForIdExistsValidation('navigation-table', 'navigation table');
    checkForIdExistsValidation('dashboard-full-title', 'dashboard title');
    checkForIdExistsValidation('top-right-badges', 'top right badges');
    checkForIdExistsValidation('top-right-badges-overhang', 'top right badges overhang');
    checkForIdExistsValidation('top-right-badges-storage', 'top right badges storage');
    try {
        if (defaultSheet === undefined) {
            console.error('defaultSheet is undefined');
        }
    } catch (e) {
        console.error('defaultSheet is not defined', e);
    }

    addNavigationResizeListener();
    console.log('1. Added navigation resize listener');
    try {
        adjustTopRightOverflowItems();
        console.log('2. Adjusted top right overflow items');
    } catch (e) {
        console.error('2. Failed to adjust top right overflow items', e);
    }
    setContentSheetWidth();
    console.log('3. Set content sheet width');
    loadFiltersFromGetValues();
    console.log('4. Loaded filters from get values');
    loadDataSheetBookmark();
    console.log('5. Loaded data sheet bookmark');
    applyFilters();
    console.log('6. Applied filters');

    try {
        if (manualDarkModeState == null) {
            if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
                setDarkMode(true);
            } else {
                setDarkMode(false);
            }
        }
        console.log('7. Set dark mode');
    } catch (e) {
        console.error('7. Failed to set dark mode', e);
    }

    console.log('Finished loading process for VAD');
}

window.onload = onLoad;




© 2015 - 2025 Weber Informatics LLC | Privacy Policy