<commit_before>import { vec2, Panel } from './node_modules/SvgPanels/Panel.js';
import { PanePanel, TilingPanel, DropDownMenuPanel, ButtonPanel, ScrollViewPanel, IndexedListPanel } from './node_modules/SvgPanels/Containers.js';
import { LabelPanel, RectPanel, TextAreaPanel } from './node_modules/SvgPanels/Atoms.js';
import { Utils, SymbolInternals, SymbolMap, JavaScriptBackend } from './node_modules/SymatemJS/SymatemJS.mjs';
export const backend = new JavaScriptBackend();
const symbolObservers = SymbolMap.create();

function startObservingSymbol(observer, symbol) {
    SymbolMap.getOrInsert(symbolObservers, symbol, new Set()).add(observer);
}

function stopObservingSymbol(observer, symbol) {
    SymbolMap.get(symbolObservers, symbol).delete(observer);
}

function triggerObservedSymbol(symbol) {
    const observers = SymbolMap.get(symbolObservers, symbol);
    if(observers)
        for(const observer of observers)
            observer.backendUpdate();
}

export class SymbolThumbnailPanel extends LabelPanel {
    constructor(position, symbol, onActivate) {
        super(position);
        this._symbol = symbol;
        if(onActivate)
            this.addEventListener('action', onActivate);
        this.registerFocusEvent(this.node);
        this.registerDragEvent(() => {
            const panel = new SymbolThumbnailPanel(vec2.create(), this._symbol);
            panel.backendUpdate();
            return panel;
        });
        this.addEventListener('parentchange', (event) => {
            if(this.parent)
                startObservingSymbol(this, symbol);
            else
                stopObservingSymbol(this, symbol);
        });
    }

    get symbol() {
        return this._symbol;
    }

    set symbol(symbol) {
        stopObservingSymbol(this, this.symbol);
        startObservingSymbol(this, symbol);
        this._symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        const data = backend.getData(this.symbol);
        this.text = (data) ? Utils.encodeText(data) : '#'+this.symbol;
    }
};

export class BasicSymbolPanel extends PanePanel {
    constructor(position, symbol, onClick, acceptsDrop=(item) => item instanceof SymbolThumbnailPanel) {
        super(position, vec2.create());
        this.addEventListener('parentchange', (event) => {
            if(this.parent)
                startObservingSymbol(this, symbol);
            else
                stopObservingSymbol(this, symbol);
        });
        this.headerSplit = new TilingPanel(vec2.create(), vec2.create());
        this.headerSplit.padding = vec2.fromValues(5, 5);
        this.headerSplit.axis = 1;
        this.headerSplit.sizeAlongAxis = 1;
        this.headerSplit.otherAxisAlignment = 'stretch';
        this.headerSplit.otherAxisSizeStays = true;
        this.headerSplit.interChildSpacing = 5;
        this.headerPanel = new TilingPanel(vec2.create(), vec2.create());
        this.headerPanel.axis = 0;
        this.headerPanel.sizeAlongAxis = 0;
        this.headerSplit.insertChild(this.headerPanel);
        this.symbolPanel = new SymbolThumbnailPanel(vec2.create(), symbol, onClick);
        this.symbolPanel.registerDropEvent(acceptsDrop,
            (item) => {
                this.symbol = item.symbol;
            }
        );
        this.headerPanel.insertChild(this.symbolPanel);
        this.headerPanel.recalculateLayout();
        this.insertChild(this.headerSplit);
    }

    get symbol() {
        return this.symbolPanel.symbol;
    }

    set symbol(symbol) {
        stopObservingSymbol(this, this.symbolPanel.symbol);
        startObservingSymbol(this, symbol);
        this.symbolPanel.symbol = symbol;
    }

    backendUpdate() {
        this.symbolPanel.backendUpdate();
        this.headerPanel.recalculateLayout();
    }
};

const indexNames = ['EAV', 'AVE', 'VEA', 'EVA', 'AEV', 'VAE'],
      triplePermutation = [
    [0, 1, 2],
    [1, 2, 0],
    [2, 0, 1],
    [0, 2, 1],
    [1, 0, 2],
    [2, 1, 0]
];

export class TriplePanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.UTF8) {
        super(position, symbol, () => {
            this.symbolPanel.selected = !this.symbolPanel.selected;
            for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
                betaCollection.children[0].children[0].selected = this.symbolPanel.selected;
                for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                    gammaRow.children[0].selected = this.symbolPanel.selected;
            }
        });
        const indexOptions = [];
        indexNames.forEach((name, index) => {
            const indexOption = new ButtonPanel(vec2.create(), 'toolbarMenuButton');
            indexOption.addEventListener('action', (event) => {
                this.indexSelection.overlayPanel.root.closeModalOverlay(event);
                this.index = index;
            });
            indexOption.insertChild(new LabelPanel(vec2.create(), name));
            indexOptions.push(indexOption);
        });
        this.indexSelection = new DropDownMenuPanel(vec2.create(), new LabelPanel(vec2.create()), indexOptions);
        this.headerPanel.insertChild(this.indexSelection);
        this.indexedListPanel = new IndexedListPanel(vec2.create(), vec2.create());
        this.indexedListPanel.contentPanel.registerFocusNavigationEvent();
        this.headerSplit.insertChild(this.indexedListPanel);
        this.betaIndex = SymbolMap.create();
        this.index = 0;
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                case 'left':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'right':
                    this.indexSelection.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.indexedListPanel.contentPanel.dispatchEvent({'type': 'focusnavigation', 'direction': 'in'});
                    break;
            }
            return true;
        });
    }

    get index() {
        return this._index;
    }

    set index(_index) {
        this._index = _index;
        this.indexSelection.contentPanel.text = indexNames[this.index];
        this.indexSelection.recalculateLayout();
        this.backendUpdate();
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.index = 0;
    }

    backendUpdate() {
        super.backendUpdate();
        let query;
        switch(this.index) {
            case 0:
            case 3:
                query = backend.queryTriples(backend.queryMasks.MVV, [this.symbol, backend.symbolByName.Void, backend.symbolByName.Void]);
                break;
            case 1:
            case 4:
                query = backend.queryTriples(backend.queryMasks.VMV, [backend.symbolByName.Void, this.symbol, backend.symbolByName.Void]);
                break;
            case 2:
            case 5:
                query = backend.queryTriples(backend.queryMasks.VVM, [backend.symbolByName.Void, backend.symbolByName.Void, this.symbol]);
                break;
        }
        const makeRow = (betaCollection, symbol, isHeader) => {
            const rowPanel = new TilingPanel(vec2.create(), vec2.fromValues(0, 20));
            rowPanel.axis = 1;
            rowPanel.sizeAlongAxis = 'centering';
            rowPanel.padding[0] = 25;
            if(isHeader) {
                rowPanel.backgroundPanel = new RectPanel(vec2.create(), vec2.create());
                rowPanel.backgroundPanel.node.classList.add('indexedListHeader');
            }
            rowPanel.insertChild(new SymbolThumbnailPanel(vec2.create(), symbol, () => {
                rowPanel.children[0].selected = !rowPanel.children[0].selected;
                if(isHeader) {
                    const betaCollection = SymbolMap.get(this.betaIndex, rowPanel.children[0].symbol);
                    for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                        gammaRow.children[0].selected = rowPanel.children[0].selected;
                }
            }));
            rowPanel.children[0].backendUpdate();
            rowPanel.recalculateLayout();
            rowPanel.otherAxisSizeStays = true;
            betaCollection.insertChild(rowPanel);
            return rowPanel;
        }
        for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
            betaCollection.deletionFlag = true;
            for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                gammaRow.deletionFlag = true;
        }
        for(let triple of query) {
            triple = [triple[triplePermutation[this.index][0]], triple[triplePermutation[this.index][1]], triple[triplePermutation[this.index][2]]];
            let betaCollection = SymbolMap.get(this.betaIndex, triple[1]);
            if(!betaCollection) {
                betaCollection = new TilingPanel(vec2.create(), vec2.create());
                betaCollection.gammaSet = SymbolMap.create();
                this.indexedListPanel.contentPanel.insertChild(betaCollection);
                makeRow(betaCollection, triple[1], true);
                SymbolMap.set(this.betaIndex, triple[1], betaCollection);
                betaCollection.registerFocusEvent(betaCollection.children[0].backgroundPanel.node);
                betaCollection.registerFocusNavigationEvent(1);
            } else
                delete betaCollection.deletionFlag;
            let gammaRow = SymbolMap.get(betaCollection.gammaSet, triple[2]);
            if(!gammaRow) {
                gammaRow = makeRow(betaCollection, triple[2], false);
                SymbolMap.set(betaCollection.gammaSet, triple[2], gammaRow);
            } else
                delete gammaRow.deletionFlag;
        }
        for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
            if(betaCollection.deletionFlag) {
                this.indexedListPanel.contentPanel.removeChild(betaCollection);
                SymbolMap.remove(this.betaIndex, symbol);
            } else for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                if(gammaRow.deletionFlag) {
                    betaCollection.removeChild(gammaRow);
                    SymbolMap.remove(betaCollection.gammaSet, symbol);
                }
        }
        for(const betaCollection of this.indexedListPanel.contentPanel.children) {
            betaCollection.axis = 1;
            betaCollection.otherAxisAlignment = 'stretch';
            betaCollection.recalculateLayout();
            betaCollection.otherAxisSizeStays = true;
        }
        this.indexedListPanel.contentPanel.recalculateLayout();
        this.indexedListPanel.recalculateLayout();
    }
};

export class SymbolDataContentPanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.UTF8) {
        super(position, symbol);
        this.contentPanel = new TextAreaPanel(vec2.create(), vec2.create());
        this.contentPanel.recalculateLayout();
        this.contentPanel.addEventListener('change', (event) => {
            const data = Utils.decodeText(this.contentPanel.text);
            if(data && data != backend.getData(this.symbol)) {
                backend.setData(this.symbol, data);
                triggerObservedSymbol(this.symbol);
            }
        });
        this.headerSplit.insertChild(this.contentPanel);
        this.backendUpdate();
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                case 'up':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.contentPanel.dispatchEvent({'type': 'focus'});
                    break;
            }
            return true;
        });
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        super.backendUpdate();
        this.contentPanel.text = Utils.encodeText(backend.getData(this.symbol));
    }
};

export class NamespacePanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.Namespaces) {
        super(position, symbol, undefined, (item) => item instanceof SymbolThumbnailPanel && SymbolInternals.namespaceOfSymbol(item.symbol) == backend.metaNamespaceIdentity);
        this.listPanel = new TilingPanel(vec2.create(), vec2.create());
        this.listPanel.axis = 1;
        this.listPanel.registerFocusNavigationEvent();
        this.scrollViewPanel = new ScrollViewPanel(vec2.create(), vec2.create(), this.listPanel);
        this.headerSplit.insertChild(this.scrollViewPanel);
        this.betaIndex = SymbolMap.create();
        this.backendUpdate();
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.listPanel.dispatchEvent({'type': 'focusnavigation', 'direction': 'in'});
                    break;
            }
            return true;
        });
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        super.backendUpdate();
        const symbols = SymbolMap.create();
        for(const symbol of backend.querySymbols(SymbolInternals.identityOfSymbol(this.symbol))) {
            SymbolMap.set(symbols, symbol, true);
            let entry = SymbolMap.get(this.betaIndex, symbol);
            if(entry)
                continue;
            entry = new SymbolThumbnailPanel(vec2.create(), symbol, () => {
                entry.selected = !entry.selected;
            });
            this.listPanel.insertChild(entry);
            entry.backendUpdate();
            SymbolMap.set(this.betaIndex, symbol, entry);
        }
        for(const [symbol, entry] of SymbolMap.entries(this.betaIndex)) {
            if(!SymbolMap.get(symbols, symbol)) {
                this.listPanel.removeChild(entry);
                SymbolMap.remove(this.betaIndex, symbol);
            }
        }
        this.listPanel.recalculateLayout();
        this.scrollViewPanel.recalculateLayout();
    }
};
<commit_msg>Improves focus and selection in TriplePanel.<commit_after>import { vec2, Panel } from './node_modules/SvgPanels/Panel.js';
import { PanePanel, TilingPanel, DropDownMenuPanel, ButtonPanel, ScrollViewPanel, IndexedListPanel } from './node_modules/SvgPanels/Containers.js';
import { LabelPanel, RectPanel, TextAreaPanel } from './node_modules/SvgPanels/Atoms.js';
import { Utils, SymbolInternals, SymbolMap, JavaScriptBackend } from './node_modules/SymatemJS/SymatemJS.mjs';
export const backend = new JavaScriptBackend();
const symbolObservers = SymbolMap.create();

function startObservingSymbol(observer, symbol) {
    SymbolMap.getOrInsert(symbolObservers, symbol, new Set()).add(observer);
}

function stopObservingSymbol(observer, symbol) {
    SymbolMap.get(symbolObservers, symbol).delete(observer);
}

function triggerObservedSymbol(symbol) {
    const observers = SymbolMap.get(symbolObservers, symbol);
    if(observers)
        for(const observer of observers)
            observer.backendUpdate();
}

export class SymbolThumbnailPanel extends LabelPanel {
    constructor(position, symbol, onActivate) {
        super(position);
        this._symbol = symbol;
        if(onActivate)
            this.addEventListener('action', onActivate);
        this.registerFocusEvent(this.node);
        this.registerDragEvent(() => {
            const panel = new SymbolThumbnailPanel(vec2.create(), this._symbol);
            panel.backendUpdate();
            return panel;
        });
        this.addEventListener('parentchange', (event) => {
            if(this.parent)
                startObservingSymbol(this, symbol);
            else
                stopObservingSymbol(this, symbol);
        });
    }

    get symbol() {
        return this._symbol;
    }

    set symbol(symbol) {
        stopObservingSymbol(this, this.symbol);
        startObservingSymbol(this, symbol);
        this._symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        const data = backend.getData(this.symbol);
        this.text = (data) ? Utils.encodeText(data) : '#'+this.symbol;
    }
};

export class BasicSymbolPanel extends PanePanel {
    constructor(position, symbol, onClick, acceptsDrop=(item) => item instanceof SymbolThumbnailPanel) {
        super(position, vec2.create());
        this.addEventListener('parentchange', (event) => {
            if(this.parent)
                startObservingSymbol(this, symbol);
            else
                stopObservingSymbol(this, symbol);
        });
        this.headerSplit = new TilingPanel(vec2.create(), vec2.create());
        this.headerSplit.padding = vec2.fromValues(5, 5);
        this.headerSplit.axis = 1;
        this.headerSplit.sizeAlongAxis = 1;
        this.headerSplit.otherAxisAlignment = 'stretch';
        this.headerSplit.otherAxisSizeStays = true;
        this.headerSplit.interChildSpacing = 5;
        this.headerPanel = new TilingPanel(vec2.create(), vec2.create());
        this.headerPanel.axis = 0;
        this.headerPanel.sizeAlongAxis = 0;
        this.headerSplit.insertChild(this.headerPanel);
        this.symbolPanel = new SymbolThumbnailPanel(vec2.create(), symbol, onClick);
        this.symbolPanel.registerDropEvent(acceptsDrop,
            (item) => {
                this.symbol = item.symbol;
            }
        );
        this.headerPanel.insertChild(this.symbolPanel);
        this.headerPanel.recalculateLayout();
        this.insertChild(this.headerSplit);
    }

    get symbol() {
        return this.symbolPanel.symbol;
    }

    set symbol(symbol) {
        stopObservingSymbol(this, this.symbolPanel.symbol);
        startObservingSymbol(this, symbol);
        this.symbolPanel.symbol = symbol;
    }

    backendUpdate() {
        this.symbolPanel.backendUpdate();
        this.headerPanel.recalculateLayout();
    }
};

const indexNames = ['EAV', 'AVE', 'VEA', 'EVA', 'AEV', 'VAE'],
      triplePermutation = [
    [0, 1, 2],
    [1, 2, 0],
    [2, 0, 1],
    [0, 2, 1],
    [1, 0, 2],
    [2, 1, 0]
];

export class TriplePanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.UTF8) {
        super(position, symbol, () => {
            this.symbolPanel.selected = !this.symbolPanel.selected;
            for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
                betaCollection.children[0].children[0].selected = this.symbolPanel.selected;
                for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                    gammaRow.children[0].selected = this.symbolPanel.selected;
            }
        });
        const indexOptions = [];
        indexNames.forEach((name, index) => {
            const indexOption = new ButtonPanel(vec2.create(), 'toolbarMenuButton');
            indexOption.addEventListener('action', (event) => {
                this.indexSelection.overlayPanel.root.closeModalOverlay(event);
                this.index = index;
            });
            indexOption.insertChild(new LabelPanel(vec2.create(), name));
            indexOptions.push(indexOption);
        });
        this.indexSelection = new DropDownMenuPanel(vec2.create(), new LabelPanel(vec2.create()), indexOptions);
        this.headerPanel.insertChild(this.indexSelection);
        this.indexedListPanel = new IndexedListPanel(vec2.create(), vec2.create());
        this.indexedListPanel.contentPanel.registerFocusNavigationEvent();
        this.headerSplit.insertChild(this.indexedListPanel);
        this.betaIndex = SymbolMap.create();
        this.index = 0;
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                case 'left':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'right':
                    this.indexSelection.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.indexedListPanel.contentPanel.dispatchEvent({'type': 'focusnavigation', 'direction': 'in'});
                    break;
            }
            return true;
        });
    }

    get index() {
        return this._index;
    }

    set index(_index) {
        this._index = _index;
        this.indexSelection.contentPanel.text = indexNames[this.index];
        this.indexSelection.recalculateLayout();
        this.backendUpdate();
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.index = 0;
    }

    backendUpdate() {
        super.backendUpdate();
        let query;
        switch(this.index) {
            case 0:
            case 3:
                query = backend.queryTriples(backend.queryMasks.MVV, [this.symbol, backend.symbolByName.Void, backend.symbolByName.Void]);
                break;
            case 1:
            case 4:
                query = backend.queryTriples(backend.queryMasks.VMV, [backend.symbolByName.Void, this.symbol, backend.symbolByName.Void]);
                break;
            case 2:
            case 5:
                query = backend.queryTriples(backend.queryMasks.VVM, [backend.symbolByName.Void, backend.symbolByName.Void, this.symbol]);
                break;
        }
        const makeRow = (betaCollection, symbol, isHeader) => {
            const rowPanel = new TilingPanel(vec2.create(), vec2.fromValues(0, 20));
            rowPanel.axis = 1;
            rowPanel.sizeAlongAxis = 'centering';
            rowPanel.padding[0] = 25;
            if(isHeader) {
                rowPanel.backgroundPanel = new RectPanel(vec2.create(), vec2.create());
                rowPanel.backgroundPanel.node.classList.add('indexedListHeader');
            }
            rowPanel.insertChild(new SymbolThumbnailPanel(vec2.create(), symbol, () => {
                rowPanel.children[0].selected = !rowPanel.children[0].selected;
            }));
            rowPanel.children[0].backendUpdate();
            rowPanel.recalculateLayout();
            rowPanel.otherAxisSizeStays = true;
            betaCollection.insertChild(rowPanel);
            return rowPanel;
        }
        for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
            betaCollection.deletionFlag = true;
            for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                gammaRow.deletionFlag = true;
        }
        for(let triple of query) {
            triple = [triple[triplePermutation[this.index][0]], triple[triplePermutation[this.index][1]], triple[triplePermutation[this.index][2]]];
            let betaCollection = SymbolMap.get(this.betaIndex, triple[1]);
            if(!betaCollection) {
                betaCollection = new TilingPanel(vec2.create(), vec2.create());
                betaCollection.gammaSet = SymbolMap.create();
                this.indexedListPanel.contentPanel.insertChild(betaCollection);
                makeRow(betaCollection, triple[1], true);
                SymbolMap.set(this.betaIndex, triple[1], betaCollection);
                betaCollection.registerActionEvent(() => {
                    const header = betaCollection.children[0].children[0];
                    header.selected = !header.selected;
                    for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                        gammaRow.children[0].selected = header.selected;
                });
                betaCollection.registerFocusEvent(betaCollection.children[0].backgroundPanel.node);
                betaCollection.registerFocusNavigationEvent(1);
            } else
                delete betaCollection.deletionFlag;
            let gammaRow = SymbolMap.get(betaCollection.gammaSet, triple[2]);
            if(!gammaRow) {
                gammaRow = makeRow(betaCollection, triple[2], false);
                SymbolMap.set(betaCollection.gammaSet, triple[2], gammaRow);
            } else
                delete gammaRow.deletionFlag;
        }
        for(const [symbol, betaCollection] of SymbolMap.entries(this.betaIndex)) {
            if(betaCollection.deletionFlag) {
                this.indexedListPanel.contentPanel.removeChild(betaCollection);
                SymbolMap.remove(this.betaIndex, symbol);
            } else for(const [symbol, gammaRow] of SymbolMap.entries(betaCollection.gammaSet))
                if(gammaRow.deletionFlag) {
                    betaCollection.removeChild(gammaRow);
                    SymbolMap.remove(betaCollection.gammaSet, symbol);
                }
        }
        for(const betaCollection of this.indexedListPanel.contentPanel.children) {
            betaCollection.axis = 1;
            betaCollection.otherAxisAlignment = 'stretch';
            betaCollection.recalculateLayout();
            betaCollection.otherAxisSizeStays = true;
        }
        this.indexedListPanel.contentPanel.recalculateLayout();
        this.indexedListPanel.recalculateLayout();
    }
};

export class SymbolDataContentPanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.UTF8) {
        super(position, symbol);
        this.contentPanel = new TextAreaPanel(vec2.create(), vec2.create());
        this.contentPanel.recalculateLayout();
        this.contentPanel.addEventListener('change', (event) => {
            const data = Utils.decodeText(this.contentPanel.text);
            if(data && data != backend.getData(this.symbol)) {
                backend.setData(this.symbol, data);
                triggerObservedSymbol(this.symbol);
            }
        });
        this.headerSplit.insertChild(this.contentPanel);
        this.backendUpdate();
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                case 'up':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.contentPanel.dispatchEvent({'type': 'focus'});
                    break;
            }
            return true;
        });
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        super.backendUpdate();
        this.contentPanel.text = Utils.encodeText(backend.getData(this.symbol));
    }
};

export class NamespacePanel extends BasicSymbolPanel {
    constructor(position, symbol=backend.symbolByName.Namespaces) {
        super(position, symbol, undefined, (item) => item instanceof SymbolThumbnailPanel && SymbolInternals.namespaceOfSymbol(item.symbol) == backend.metaNamespaceIdentity);
        this.listPanel = new TilingPanel(vec2.create(), vec2.create());
        this.listPanel.axis = 1;
        this.listPanel.registerFocusNavigationEvent();
        this.scrollViewPanel = new ScrollViewPanel(vec2.create(), vec2.create(), this.listPanel);
        this.headerSplit.insertChild(this.scrollViewPanel);
        this.betaIndex = SymbolMap.create();
        this.backendUpdate();
        this.addEventListener('focusnavigation', (event) => {
            switch(event.direction) {
                case 'in':
                    this.symbolPanel.dispatchEvent({'type': 'focus'});
                    break;
                case 'down':
                    this.listPanel.dispatchEvent({'type': 'focusnavigation', 'direction': 'in'});
                    break;
            }
            return true;
        });
    }

    get symbol() {
        return super.symbol;
    }

    set symbol(symbol) {
        super.symbol = symbol;
        this.backendUpdate();
    }

    backendUpdate() {
        super.backendUpdate();
        const symbols = SymbolMap.create();
        for(const symbol of backend.querySymbols(SymbolInternals.identityOfSymbol(this.symbol))) {
            SymbolMap.set(symbols, symbol, true);
            let entry = SymbolMap.get(this.betaIndex, symbol);
            if(entry)
                continue;
            entry = new SymbolThumbnailPanel(vec2.create(), symbol, () => {
                entry.selected = !entry.selected;
            });
            this.listPanel.insertChild(entry);
            entry.backendUpdate();
            SymbolMap.set(this.betaIndex, symbol, entry);
        }
        for(const [symbol, entry] of SymbolMap.entries(this.betaIndex)) {
            if(!SymbolMap.get(symbols, symbol)) {
                this.listPanel.removeChild(entry);
                SymbolMap.remove(this.betaIndex, symbol);
            }
        }
        this.listPanel.recalculateLayout();
        this.scrollViewPanel.recalculateLayout();
    }
};
<|endoftext|>


<commit_before>// Client side code for the Problem Builder Dashboard XBlock
// So far, this code is only used to generate a downloadable report.
function ExportBase(runtime, element, initData) {
    "use strict";

    var reportTemplate = initData.reportTemplate;

    var generateDataUriFromImageURL = function(imgURL) {
        // Given the URL to an image, IF the image has already been cached by the browser,
        // returns a data: URI with the contents of the image (image will be converted to PNG)

        // Expand relative urls and urls without an explicit protocol into absolute urls
        var a = document.createElement('a');
        a.href = imgURL;
        imgURL = a.href;

        // If the image is from another domain, just return its URL. We can't
        // create a data URL from cross-domain images:
        // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-todataurl
        if (a.origin !== window.location.origin)
            return imgURL;

        var img = new Image();
        img.src = imgURL;
        if (!img.complete)
            return imgURL;

        // Create an in-memory canvas from which we can extract a data URL:
        var canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        // Draw the image onto our temporary canvas:
        canvas.getContext('2d').drawImage(img, 0, 0);
        return canvas.toDataURL("image/png");
    };

    var unicodeStringToBase64 = function(str) {
        // Convert string to base64. A bit weird in order to support unicode, per
        // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa
        return window.btoa(unescape(encodeURIComponent(str)));
    };

    var downloadReport = function(ev) {
        // Download Report:
        // Change the URL to a data: URI before continuing with the click event.
        if ($(this).attr('href').charAt(0) == '#') {
            var $report = $(initData.reportContentSelector, element).clone();
            // Convert all images in $report to data URIs:
            $report.find('image').each(function() {
                var origURL = $(this).attr('xlink:href');
                $(this).attr('xlink:href', generateDataUriFromImageURL(origURL));
            });
            // Take the resulting HTML and put it into the template we have:
            var mentoringTableContainer = $report.find('.mentoring-table-container');
            var cache_width = mentoringTableContainer.width();
            var wrapperHTML = reportTemplate.replace('REPORT_GOES_HERE', $report.html());

            // createPDF(mentoringTableContainer, cache_width);
            var doc = new jsPDF('p','pt','a4');
            console.log(mentoringTableContainer.context);
            // doc.addHTML(mentoringTableContainer.context ,function() {
            //     var string = doc.output('datauristring');
            //     doc.save('report.pdf');
            // });

            doc.fromHTML(mentoringTableContainer.context, 15, 15, {
                'width': 170,
            });
            doc.save('report.pdf');

            var dataURI = "data:text/html;base64," + unicodeStringToBase64(wrapperHTML);
            $(this).attr('href', dataURI);
        }
    };

    function createPDF(html, cache_width){
     getCanvas(html).then(function(canvas){
      var
      img = canvas.toDataURL("image/png"),
      doc = new jsPDF({
              unit:'px',
              format:'a4'
            });
            doc.addImage(img, 'JPEG', 20, 20);
            doc.save('report.pdf');
            html.width(cache_width);
     });
    }

    function getCanvas(html){
     var a4  =[ 595.28,  841.89];
     html.width((a4[0]*1.33333) -80).css('max-width','none');
     console.log(html.context);
     return html2canvas(html.context,{
         imageTimeout:2000,
         removeContainer:true
        });
    }

    var $downloadLink = $('.report-download-link', element);
    $downloadLink.on('click', downloadReport);
}

function PBDashboardBlock(runtime, element, initData) {
    new ExportBase(runtime, element, initData);
}

function MentoringTableBlock(runtime, element, initData) {
    // Display an excerpt for long answers, with a "more" link to display the full text

    var $element = $(element),
        $shareButton = $element.find('.mentoring-share-button'),
        $doShareButton = $element.find('.do-share-button'),
        $shareMenu = $element.find('.mentoring-share-with'),
        $displayDropdown = $element.find('.mentoring-display-dropdown'),
        $errorHolder = $element.find('.share-errors'),
        $deleteShareButton = $element.find('.remove-share'),
        $newShareContainer = $($element.find('.new-share-container')[0]),
        $addShareField = $($element.find('.add-share-field')[0]),
        $notification = $($element.find('.share-notification')),
        $closeNotification = $($element.find('.notification-close')),
        tableLoadURL = runtime.handlerUrl(element, 'table_render'),
        deleteShareUrl = runtime.handlerUrl(element, 'remove_share'),
        sharedListLoadUrl = runtime.handlerUrl(element, 'get_shared_list'),
        clearNotificationUrl = runtime.handlerUrl(element, 'clear_notification'),
        shareResultsUrl = runtime.handlerUrl(element, 'share_results');

    function loadTable(data) {
        $element.find('.mentoring-table-target').html(data['content']);
        $('.answer-table', element).shorten({
            moreText: 'more',
            lessText: 'less',
            showChars: '500'
        });
    }

    function errorMessage(event) {
        $errorHolder.text(JSON.parse(event.responseText)['error'])
    }

    function sharedRefresh(data) {
        $element.find('.shared-with-container').html(data['content']);
        $deleteShareButton = $($deleteShareButton.selector);
        $deleteShareButton.on('click', deleteShare);
    }

    function postShareRefresh(data) {
        sharedRefresh(data);
        $element.find(".new-share-container").each(function(index, container) {
            if (index === 0) {
                var $container = $(container);
                $container.find('.add-share-username').val('');
                $container.find('.add-share-field').show();
                return;
            }
            $(container).remove()
        });
        $errorHolder.html('');
    }

    function postShare() {
        $.ajax({
            type: "POST",
            url: sharedListLoadUrl,
            data: JSON.stringify({}),
            success: postShareRefresh,
            error: errorMessage
        });
    }

    function updateShare() {
        var usernames = [];
        $element.find('.add-share-username').each(function(index, username) {
            usernames.push($(username).val())
        });
        $.ajax({
            type: "POST",
            url: shareResultsUrl,
            data: JSON.stringify({'usernames': usernames}),
            success: postShare,
            error: errorMessage
        });
    }

    function menuHider(event) {
        if (!$(event.target).closest($shareMenu).length) {
            // We're clicking outside of the menu, so hide it.
            $shareMenu.hide();
            $(document).off('click.mentoring_share_menu_hide');
        }
    }

    $shareButton.on('click', function (event) {
        if (!$shareMenu.is(':visible')){
            event.stopPropagation();
            $(document).on('click.mentoring_share_menu_hide', menuHider);
            $shareMenu.show();
        }
    });
    $doShareButton.on('click', updateShare);

    function postLoad(data) {
        loadTable(data);
        new ExportBase(runtime, element, initData);
    }

    $.ajax({
        type: "POST",
        url: tableLoadURL,
        data: JSON.stringify({'target_username': $displayDropdown.val()}),
        success: postLoad
    });

    $.ajax({
        type: "POST",
        url: sharedListLoadUrl,
        data: JSON.stringify({}),
        success: sharedRefresh
    });

    $displayDropdown.on('change', function () {
        if ($displayDropdown[0].selectedIndex !== 0) {
            $shareButton.prop('disabled', true);
            $element.find('.report-download-container').hide();
        } else {
            $shareButton.prop('disabled', false);
            $element.find('.report-download-container').show();
        }
        $.ajax({
            type: "POST",
            url: tableLoadURL,
            data: JSON.stringify({'target_username': $displayDropdown.val()}),
            success: loadTable
        })
    });

    function addShare() {
        var container = $newShareContainer.clone();
        container.find('.add-share-username').val('');
        container.insertAfter($element.find('.new-share-container').last());
        container.find('.add-share-field').on('click', addShare);
        var buttons = $element.find('.new-share-container .add-share-field');
        buttons.hide();
        buttons.last().show();
    }

    function deleteShare(event) {
        event.preventDefault();
        $.ajax({
            type: "POST",
            url: deleteShareUrl,
            data: JSON.stringify({'username': $(event.target).parent().prev()[0].innerHTML}),
            success: function () {
                $(event.target).parent().parent().remove();
                $errorHolder.html('');
            },
            error: errorMessage
        });
    }

    $closeNotification.on('click', function () {
        // Don't need server approval to hide it.
        $notification.hide();
        $.ajax({
            type: "POST",
            url: clearNotificationUrl,
            data: JSON.stringify({'usernames': $notification.data('shared')})
        })
    });

    $addShareField.on('click', addShare);
}
<commit_msg>what is report?<commit_after>// Client side code for the Problem Builder Dashboard XBlock
// So far, this code is only used to generate a downloadable report.
function ExportBase(runtime, element, initData) {
    "use strict";

    var reportTemplate = initData.reportTemplate;

    var generateDataUriFromImageURL = function(imgURL) {
        // Given the URL to an image, IF the image has already been cached by the browser,
        // returns a data: URI with the contents of the image (image will be converted to PNG)

        // Expand relative urls and urls without an explicit protocol into absolute urls
        var a = document.createElement('a');
        a.href = imgURL;
        imgURL = a.href;

        // If the image is from another domain, just return its URL. We can't
        // create a data URL from cross-domain images:
        // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-todataurl
        if (a.origin !== window.location.origin)
            return imgURL;

        var img = new Image();
        img.src = imgURL;
        if (!img.complete)
            return imgURL;

        // Create an in-memory canvas from which we can extract a data URL:
        var canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        // Draw the image onto our temporary canvas:
        canvas.getContext('2d').drawImage(img, 0, 0);
        return canvas.toDataURL("image/png");
    };

    var unicodeStringToBase64 = function(str) {
        // Convert string to base64. A bit weird in order to support unicode, per
        // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa
        return window.btoa(unescape(encodeURIComponent(str)));
    };

    var downloadReport = function(ev) {
        // Download Report:
        // Change the URL to a data: URI before continuing with the click event.
        if ($(this).attr('href').charAt(0) == '#') {
            var $report = $(initData.reportContentSelector, element).clone();
            // Convert all images in $report to data URIs:
            $report.find('image').each(function() {
                var origURL = $(this).attr('xlink:href');
                $(this).attr('xlink:href', generateDataUriFromImageURL(origURL));
            });
            // Take the resulting HTML and put it into the template we have:
            var mentoringTableContainer = $report.find('.mentoring-table-container');
            var cache_width = mentoringTableContainer.width();
            var wrapperHTML = reportTemplate.replace('REPORT_GOES_HERE', $report.html());

            // createPDF(mentoringTableContainer, cache_width);
            var doc = new jsPDF('p','pt','a4');
            console.log($report);
            console.log($report.context);
            console.log(mentoringTableContainer.context);
            // doc.addHTML(mentoringTableContainer.context ,function() {
            //     var string = doc.output('datauristring');
            //     doc.save('report.pdf');
            // });

            doc.fromHTML(mentoringTableContainer.context, 15, 15, {
                'width': 170,
            });
            doc.save('report.pdf');

            var dataURI = "data:text/html;base64," + unicodeStringToBase64(wrapperHTML);
            $(this).attr('href', dataURI);
        }
    };

    function createPDF(html, cache_width){
     getCanvas(html).then(function(canvas){
      var
      img = canvas.toDataURL("image/png"),
      doc = new jsPDF({
              unit:'px',
              format:'a4'
            });
            doc.addImage(img, 'JPEG', 20, 20);
            doc.save('report.pdf');
            html.width(cache_width);
     });
    }

    function getCanvas(html){
     var a4  =[ 595.28,  841.89];
     html.width((a4[0]*1.33333) -80).css('max-width','none');
     console.log(html.context);
     return html2canvas(html.context,{
         imageTimeout:2000,
         removeContainer:true
        });
    }

    var $downloadLink = $('.report-download-link', element);
    $downloadLink.on('click', downloadReport);
}

function PBDashboardBlock(runtime, element, initData) {
    new ExportBase(runtime, element, initData);
}

function MentoringTableBlock(runtime, element, initData) {
    // Display an excerpt for long answers, with a "more" link to display the full text

    var $element = $(element),
        $shareButton = $element.find('.mentoring-share-button'),
        $doShareButton = $element.find('.do-share-button'),
        $shareMenu = $element.find('.mentoring-share-with'),
        $displayDropdown = $element.find('.mentoring-display-dropdown'),
        $errorHolder = $element.find('.share-errors'),
        $deleteShareButton = $element.find('.remove-share'),
        $newShareContainer = $($element.find('.new-share-container')[0]),
        $addShareField = $($element.find('.add-share-field')[0]),
        $notification = $($element.find('.share-notification')),
        $closeNotification = $($element.find('.notification-close')),
        tableLoadURL = runtime.handlerUrl(element, 'table_render'),
        deleteShareUrl = runtime.handlerUrl(element, 'remove_share'),
        sharedListLoadUrl = runtime.handlerUrl(element, 'get_shared_list'),
        clearNotificationUrl = runtime.handlerUrl(element, 'clear_notification'),
        shareResultsUrl = runtime.handlerUrl(element, 'share_results');

    function loadTable(data) {
        $element.find('.mentoring-table-target').html(data['content']);
        $('.answer-table', element).shorten({
            moreText: 'more',
            lessText: 'less',
            showChars: '500'
        });
    }

    function errorMessage(event) {
        $errorHolder.text(JSON.parse(event.responseText)['error'])
    }

    function sharedRefresh(data) {
        $element.find('.shared-with-container').html(data['content']);
        $deleteShareButton = $($deleteShareButton.selector);
        $deleteShareButton.on('click', deleteShare);
    }

    function postShareRefresh(data) {
        sharedRefresh(data);
        $element.find(".new-share-container").each(function(index, container) {
            if (index === 0) {
                var $container = $(container);
                $container.find('.add-share-username').val('');
                $container.find('.add-share-field').show();
                return;
            }
            $(container).remove()
        });
        $errorHolder.html('');
    }

    function postShare() {
        $.ajax({
            type: "POST",
            url: sharedListLoadUrl,
            data: JSON.stringify({}),
            success: postShareRefresh,
            error: errorMessage
        });
    }

    function updateShare() {
        var usernames = [];
        $element.find('.add-share-username').each(function(index, username) {
            usernames.push($(username).val())
        });
        $.ajax({
            type: "POST",
            url: shareResultsUrl,
            data: JSON.stringify({'usernames': usernames}),
            success: postShare,
            error: errorMessage
        });
    }

    function menuHider(event) {
        if (!$(event.target).closest($shareMenu).length) {
            // We're clicking outside of the menu, so hide it.
            $shareMenu.hide();
            $(document).off('click.mentoring_share_menu_hide');
        }
    }

    $shareButton.on('click', function (event) {
        if (!$shareMenu.is(':visible')){
            event.stopPropagation();
            $(document).on('click.mentoring_share_menu_hide', menuHider);
            $shareMenu.show();
        }
    });
    $doShareButton.on('click', updateShare);

    function postLoad(data) {
        loadTable(data);
        new ExportBase(runtime, element, initData);
    }

    $.ajax({
        type: "POST",
        url: tableLoadURL,
        data: JSON.stringify({'target_username': $displayDropdown.val()}),
        success: postLoad
    });

    $.ajax({
        type: "POST",
        url: sharedListLoadUrl,
        data: JSON.stringify({}),
        success: sharedRefresh
    });

    $displayDropdown.on('change', function () {
        if ($displayDropdown[0].selectedIndex !== 0) {
            $shareButton.prop('disabled', true);
            $element.find('.report-download-container').hide();
        } else {
            $shareButton.prop('disabled', false);
            $element.find('.report-download-container').show();
        }
        $.ajax({
            type: "POST",
            url: tableLoadURL,
            data: JSON.stringify({'target_username': $displayDropdown.val()}),
            success: loadTable
        })
    });

    function addShare() {
        var container = $newShareContainer.clone();
        container.find('.add-share-username').val('');
        container.insertAfter($element.find('.new-share-container').last());
        container.find('.add-share-field').on('click', addShare);
        var buttons = $element.find('.new-share-container .add-share-field');
        buttons.hide();
        buttons.last().show();
    }

    function deleteShare(event) {
        event.preventDefault();
        $.ajax({
            type: "POST",
            url: deleteShareUrl,
            data: JSON.stringify({'username': $(event.target).parent().prev()[0].innerHTML}),
            success: function () {
                $(event.target).parent().parent().remove();
                $errorHolder.html('');
            },
            error: errorMessage
        });
    }

    $closeNotification.on('click', function () {
        // Don't need server approval to hide it.
        $notification.hide();
        $.ajax({
            type: "POST",
            url: clearNotificationUrl,
            data: JSON.stringify({'usernames': $notification.data('shared')})
        })
    });

    $addShareField.on('click', addShare);
}
<|endoftext|>


<commit_before>/**
 * @namespace
 * @suppress {duplicate}
 */
var onefold = onefold || {};

/** @namespace */
onefold.dom = {};

/**
 * @template E
 * @param {Node} root
 * @param {Node} node
 * @returns {number}
 */
onefold.dom.determineDepth = function (root, node) {};

/**
 * @param {Node} container
 * @param {Node} node
 * @returns {boolean}
 */
onefold.dom.isOrContains = function (container, node) {};

/**
 * @param {Node} container
 * @param {Node} node
 * @returns {boolean}
 */
onefold.dom.strictlyContains = function (container, node) {};

/** @namespace */
onefold.dom.element = {};

/**
 * @param {Element} element
 * @param {string} selector
 * @returns {?Element}
 */
onefold.dom.element.closest = function (element, selector) {};

/**
 * @param {Element} element
 * @param {string} selector
 * @returns {boolean}
 */
onefold.dom.element.matches = function (element, selector) {};
<commit_msg>Moved APIs into the `de.benshu` namespace.<commit_after>/** @namespace */
de.benshu.onefold.dom = {};

/**
 * @template E
 * @param {Node} root
 * @param {Node} node
 * @returns {number}
 */
de.benshu.onefold.dom.determineDepth = function (root, node) {};

/**
 * @param {Node} container
 * @param {Node} node
 * @returns {boolean}
 */
de.benshu.onefold.dom.isOrContains = function (container, node) {};

/**
 * @param {Node} container
 * @param {Node} node
 * @returns {boolean}
 */
de.benshu.onefold.dom.strictlyContains = function (container, node) {};

/** @namespace */
de.benshu.onefold.dom.element = {};

/**
 * @param {Element} element
 * @param {string} selector
 * @returns {?Element}
 */
de.benshu.onefold.dom.element.closest = function (element, selector) {};

/**
 * @param {Element} element
 * @param {string} selector
 * @returns {boolean}
 */
de.benshu.onefold.dom.element.matches = function (element, selector) {};
<|endoftext|>


<commit_before>
function Schema(schema){

  this.contents = $.extend(true, [], schema);

  this.isEventsFile = function () {
    return !isNull(this.contents["EVENTS"]);
  }

  this.isLightCurveFile = function () {
    return !isNull(this.contents["RATE"]);
  }

  this.isGTIFile = function () {
    return !isNull(this.contents["GTI"])
          || !isNull(this.contents["STDGTI"])
          || !isNull(this.contents["STDGTI04"]);
  }

  this.isRMFFile = function () {
    return !isNull(this.contents["EBOUNDS"]);
  }

  this.hasTable = function () {
    return this.isEventsFile() || this.isLightCurveFile();
  }

  this.hasColumn = function (columnName) {
    return this.hasTable() && !isNull(this.getTable()[columnName]);
  }

  this.getTable = function () {
    if (this.isEventsFile()) {
        return this.contents["EVENTS"];
    } else if (this.isLightCurveFile()) {
        return this.contents["RATE"];
    } else if (this.isRMFFile()) {
        return this.contents["EBOUNDS"];
    } else if (this.isGTIFile()) {
        return this.contents[0];
    }

    return null;
  }

  this.hasHeader = function () {
    return this.hasTable() && !isNull(this.getTable()["HEADER"]);
  }

  this.getHeader = function () {
    if (this.hasHeader()) {
      return this.getTable()["HEADER"];
    }

    return null;
  }

  this.hasGTIs = function () {
    return !isNull(this.contents["GTI"])
            && !isNull(this.contents["GTI"]["START"])
            && !isNull(this.contents["GTI"]["STOP"]);
  }

  this.getGTIs = function () {
    if (this.hasGTIs()) {
      return this.contents["GTI"];
    }

    return null;
  }

  this.getTimeUnit = function () {
    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TIMEUNIT"])) {
        return tableHeader["TIMEUNIT"];
      }
    }

    return "";
  }

  this.getBackgroundSubstracted = function () {
    if (this.hasHeader()){
      return getBackgroundSubstracted (this.getHeader());
    }

    return false;
  }

  this.getTimeResolution = function () {
    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TIMEDEL"])) {
        return parseFloat(tableHeader["TIMEDEL"]);
      } else if (!isNull(tableHeader["FRMTIME"])) {
        return parseInt(tableHeader["FRMTIME"]) / 1000;
      }
    }

    /*var eventsCount = this.getEventsCount();
    if (eventsCount > 0) {
      return this.getTotalDuration() / eventsCount;
    } else {
      return 1.0;
    }*/

    return Math.pow(10, -CONFIG.MAX_TIME_RESOLUTION_DECIMALS);
  }

  this.getTotalDuration = function () {

    var start = 0;
    var stop = 0;

    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TSTART"])
          && !isNull(tableHeader["TSTOP"])) {

        start = parseInt(tableHeader["TSTART"]);
        stop = parseInt(tableHeader["TSTOP"]);

      } else if (!isNull(tableHeader["TSTARTI"])
                  && !isNull(tableHeader["TSTOPI"])
                  && !isNull(tableHeader["TSTARTF"])
                  && !isNull(tableHeader["TSTOPF"])) {

        start = parseInt(tableHeader["TSTARTI"]) + parseFloat(tableHeader["TSTARTF"]);
        stop = parseInt(tableHeader["TSTOPI"]) + parseFloat(tableHeader["TSTOPF"]);

      } else if (this.hasTable() && this.hasColumn(CONFIG.TIME_COLUMN)){

        var timeColumn = this.getTable()[CONFIG.TIME_COLUMN];
        start = timeColumn.min_value;
        stop = timeColumn.max_value;
      }
    }

    return stop - start;
  }

  this.getEventsCount = function () {
    if (this.hasTable() && this.hasColumn(CONFIG.TIME_COLUMN)){
      return this.getTable()[CONFIG.TIME_COLUMN].count;
    }

    return 0;
  }

  this.getAvgSegmentSize = function () {
    if (this.hasGTIs()) {
      var gtiTable = this.getGTIs();

      if (gtiTable["START"].count > 1) {

        //If there are more than one gti then get segmSize from avg gti size
        return gtiTable["STOP"].min_value - gtiTable["START"].min_value;
      } else {

        //Else set segmSize from splitting totalDuration by 30
        return this.getTotalDuration() / 30;
      }
    } else {

      return this.getTimeResolution();
    }
  }

  this.getMaxSegmentSize = function () {
    return this.getTotalDuration() * CONFIG.TIMERANGE_MULTIPLIER;
  }

  return this;
}

//STATIC HEADER METHODS:

//Returns if a lightcurve is background substracted
function getBackgroundSubstracted (rateTableHeader) {
  if (!isNull(rateTableHeader)){
    if (!isNull(rateTableHeader["BACKAPP"])) {
      return rateTableHeader["BACKAPP"].toLowerCase() == "true";
    }
  }
  return false;
}

//Extracts te text for the energy range used to create a lightcurve
function extractEnergyRangeTextFromHeader (header) {
  var tableName = "RATE";
  if (!isNull(header) && !isNull(header[tableName])) {
    return extractEnergyRangeTextRateTable(header[tableName]);
  }
  return "";
}

function extractEnergyRangeTextFromSchema (schema) {
  if (schema.hasHeader()) {
    return extractEnergyRangeTextRateTable(schema.getHeader());
  }
  return "";
}

function extractEnergyRangeTextRateTable (rateTable) {

  var filterColumn = "PI";
  var searchFieldPrefix = "DSTYP";
  var unitFieldPrefix = "DSUNI";
  var valueFieldPrefix = "DSVAL";

  for (i=0; i<20; i++) {
    //Looks for the searchField index
    var searchField = searchFieldPrefix + i;
    if (!isNull(rateTable[searchField]) && rateTable[searchField] == filterColumn){
      var unit = rateTable[unitFieldPrefix + i];
      var range = rateTable[valueFieldPrefix + i];

      if (!isNull(unit) && !isNull(range) && range.indexOf(":") > -1){
        var rangeVals = range.split(":");
        if (unit.toLowerCase() == "ev") {
          return fixedPrecision(rangeVals[0]/1000, 3) + " - " + fixedPrecision(rangeVals[1]/1000, 3) + " keV";
        } else {
          return rangeVals[0] + " - " + rangeVals[1] + " " + unit;
        }
      }
    }
  }

  return "";
}
<commit_msg>DAVE-335: Energy range for lightcurve shows NaN if N/A<commit_after>
function Schema(schema){

  this.contents = $.extend(true, [], schema);

  this.isEventsFile = function () {
    return !isNull(this.contents["EVENTS"]);
  }

  this.isLightCurveFile = function () {
    return !isNull(this.contents["RATE"]);
  }

  this.isGTIFile = function () {
    return !isNull(this.contents["GTI"])
          || !isNull(this.contents["STDGTI"])
          || !isNull(this.contents["STDGTI04"]);
  }

  this.isRMFFile = function () {
    return !isNull(this.contents["EBOUNDS"]);
  }

  this.hasTable = function () {
    return this.isEventsFile() || this.isLightCurveFile();
  }

  this.hasColumn = function (columnName) {
    return this.hasTable() && !isNull(this.getTable()[columnName]);
  }

  this.getTable = function () {
    if (this.isEventsFile()) {
        return this.contents["EVENTS"];
    } else if (this.isLightCurveFile()) {
        return this.contents["RATE"];
    } else if (this.isRMFFile()) {
        return this.contents["EBOUNDS"];
    } else if (this.isGTIFile()) {
        return this.contents[0];
    }

    return null;
  }

  this.hasHeader = function () {
    return this.hasTable() && !isNull(this.getTable()["HEADER"]);
  }

  this.getHeader = function () {
    if (this.hasHeader()) {
      return this.getTable()["HEADER"];
    }

    return null;
  }

  this.hasGTIs = function () {
    return !isNull(this.contents["GTI"])
            && !isNull(this.contents["GTI"]["START"])
            && !isNull(this.contents["GTI"]["STOP"]);
  }

  this.getGTIs = function () {
    if (this.hasGTIs()) {
      return this.contents["GTI"];
    }

    return null;
  }

  this.getTimeUnit = function () {
    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TIMEUNIT"])) {
        return tableHeader["TIMEUNIT"];
      }
    }

    return "";
  }

  this.getBackgroundSubstracted = function () {
    if (this.hasHeader()){
      return getBackgroundSubstracted (this.getHeader());
    }

    return false;
  }

  this.getTimeResolution = function () {
    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TIMEDEL"])) {
        return parseFloat(tableHeader["TIMEDEL"]);
      } else if (!isNull(tableHeader["FRMTIME"])) {
        return parseInt(tableHeader["FRMTIME"]) / 1000;
      }
    }

    /*var eventsCount = this.getEventsCount();
    if (eventsCount > 0) {
      return this.getTotalDuration() / eventsCount;
    } else {
      return 1.0;
    }*/

    return Math.pow(10, -CONFIG.MAX_TIME_RESOLUTION_DECIMALS);
  }

  this.getTotalDuration = function () {

    var start = 0;
    var stop = 0;

    if (this.hasHeader()){
      var tableHeader = this.getHeader();

      if (!isNull(tableHeader["TSTART"])
          && !isNull(tableHeader["TSTOP"])) {

        start = parseInt(tableHeader["TSTART"]);
        stop = parseInt(tableHeader["TSTOP"]);

      } else if (!isNull(tableHeader["TSTARTI"])
                  && !isNull(tableHeader["TSTOPI"])
                  && !isNull(tableHeader["TSTARTF"])
                  && !isNull(tableHeader["TSTOPF"])) {

        start = parseInt(tableHeader["TSTARTI"]) + parseFloat(tableHeader["TSTARTF"]);
        stop = parseInt(tableHeader["TSTOPI"]) + parseFloat(tableHeader["TSTOPF"]);

      } else if (this.hasTable() && this.hasColumn(CONFIG.TIME_COLUMN)){

        var timeColumn = this.getTable()[CONFIG.TIME_COLUMN];
        start = timeColumn.min_value;
        stop = timeColumn.max_value;
      }
    }

    return stop - start;
  }

  this.getEventsCount = function () {
    if (this.hasTable() && this.hasColumn(CONFIG.TIME_COLUMN)){
      return this.getTable()[CONFIG.TIME_COLUMN].count;
    }

    return 0;
  }

  this.getAvgSegmentSize = function () {
    if (this.hasGTIs()) {
      var gtiTable = this.getGTIs();

      if (gtiTable["START"].count > 1) {

        //If there are more than one gti then get segmSize from avg gti size
        return gtiTable["STOP"].min_value - gtiTable["START"].min_value;
      } else {

        //Else set segmSize from splitting totalDuration by 30
        return this.getTotalDuration() / 30;
      }
    } else {

      return this.getTimeResolution();
    }
  }

  this.getMaxSegmentSize = function () {
    return this.getTotalDuration() * CONFIG.TIMERANGE_MULTIPLIER;
  }

  return this;
}

//STATIC HEADER METHODS:

//Returns if a lightcurve is background substracted
function getBackgroundSubstracted (rateTableHeader) {
  if (!isNull(rateTableHeader)){
    if (!isNull(rateTableHeader["BACKAPP"])) {
      return rateTableHeader["BACKAPP"].toLowerCase() == "true";
    }
  }
  return false;
}

//Extracts te text for the energy range used to create a lightcurve
function extractEnergyRangeTextFromHeader (header) {
  var tableName = "RATE";
  if (!isNull(header) && !isNull(header[tableName])) {
    return extractEnergyRangeTextRateTable(header[tableName]);
  }
  return "";
}

function extractEnergyRangeTextFromSchema (schema) {
  if (schema.hasHeader()) {
    return extractEnergyRangeTextRateTable(schema.getHeader());
  }
  return "";
}

function extractEnergyRangeTextRateTable (rateTable) {

  var filterColumn = "PI";
  var searchFieldPrefix = "DSTYP";
  var unitFieldPrefix = "DSUNI";
  var valueFieldPrefix = "DSVAL";

  for (i=0; i<20; i++) {
    //Looks for the searchField index
    var searchField = searchFieldPrefix + i;
    if (!isNull(rateTable[searchField]) && rateTable[searchField] == filterColumn){
      var unit = rateTable[unitFieldPrefix + i];
      var range = rateTable[valueFieldPrefix + i];

      if (!isNull(unit) && !isNull(range) && range.indexOf(":") > -1){
        var rangeVals = range.split(":");
        var rangeStart = rangeVals[0].replace(/[^\d.-]/g, '');
        var rangeEnd = rangeVals[1].replace(/[^\d.-]/g, '');
        if (jQuery.isNumeric(rangeStart) && jQuery.isNumeric(rangeEnd)){
          if (unit.toLowerCase() == "ev") {
            return fixedPrecision(rangeStart/1000, 3) + " - " + fixedPrecision(rangeEnd/1000, 3) + " keV";
          } else {
            return rangeStart + " - " + rangeEnd + " " + unit;
          }
        }
      }
    }
  }

  return "";
}
<|endoftext|>


<commit_before>// Processing step class
// Describes the process of official role (like revision, processing or front desk)

'use strict';

var Map                   = require('es6-map')
  , memoize               = require('memoizee/plain')
  , ensureDb              = require('dbjs/valid-dbjs')
  , defineStringLine      = require('dbjs-ext/string/string-line')
  , defineCreateEnum      = require('dbjs-ext/create-enum')
  , _                     = require('mano').i18n.bind('Model')
  , defineInstitution     = require('./institution')
  , defineFormSectionBase = require('./form-section-base');

module.exports = memoize(function (db) {
	var StringLine = defineStringLine(ensureDb(db))
	  , Institution = defineInstitution(db)
	  , FormSectionBase = defineFormSectionBase(db);

	defineCreateEnum(db);

	// Enum for processing step status
	var ProcessingStepStatus = StringLine.createEnum('ProcessingStepStatus', new Map([
		['paused', { label: _("Paused") }],
		['sentBack', { label: _("Sent back") }],
		['rejected', { label: _("Rejected") }],
		['approved', { label: _("Approved") }]
	]));

	return db.Object.extend('ProcessingStep', {
		// Label (name) of processing step
		label: { type: StringLine },
		// If step is processed by single institution
		// then instution should be exposed here
		institution: { type: Institution },

		// Whether given step applies at all
		isApplicable: { type: db.Boolean, value: true },
		// Whether business process is at given step or have passed it
		isReady: { type: db.Boolean, value: function (_observe) {
			return Boolean(_observe(this.master._isSubmitted) && this.isApplicable);
		} },
		// Processing step form fields section (applies only to approved status)
		dataForm: { type: FormSectionBase, nested: true },
		// Eventual reason of file been sent back
		sentBackReason: { type: db.String, required: true  },
		// Eventual reason of rejection
		rejectReason: { type: db.String, required: true  },
		// Status of step
		status: { type: ProcessingStepStatus, required: true },

		// Whether process is pending at step
		isPending: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not pending
			if (!this.isReady) return false;
			// If status not decided then clearly pending
			if (!this.status) return true;
			// If approved, but form data is complete, it's still pending
			if (this.status === 'approved') return (_observe(this.dataForm._status) !== 1);
			// If sent back, but no reason provided, it's still pending
			if (this.status === 'sentBack') return !this.sentBackReason;
			// If rejected, but no reason provided, it's still pending
			if (this.status === 'rejected') return !this.rejectReason;
			// 'paused' is the only option left, that's not pending
			return false;
		} },

		// Whether process is paused at step
		isPaused: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not paused
			if (!this.isReady) return false;
			return (this.status === 'paused');
		} },

		// Whether process was sent back from this step
		isSentBack: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not sent back
			if (!this.isReady) return false;
			// No sentBack status, means no sent back
			if (this.status !== 'sentBack') return false;
			// Provided reason confirms complete sent back
			return Boolean(this.sentBackReason);
		} },

		// Whether process was rejected at this step
		isRejected: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not rejected
			if (!this.isReady) return false;
			// No rejected status, means no it's not rejected
			if (this.status !== 'rejected') return false;
			// Provided reason confirms complete rejection
			return Boolean(this.rejectReason);
		} },

		// Whether process successfully passed this step
		isApproved: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not approved
			if (!this.isReady) return false;
			// No approved status, means no it's not approved
			if (this.status !== 'approved') return false;
			// Completed form confirms step completion
			return (_observe(this.dataForm._status) === 1);
		} },

		// Whether processing of this step has ended
		isClosed: { type: db.Boolean, value: function (_observe) {
			return this.isApproved || this.isRejected;
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<commit_msg>Define processor property for processingStep<commit_after>// Processing step class
// Describes the process of official role (like revision, processing or front desk)

'use strict';

var Map                   = require('es6-map')
  , memoize               = require('memoizee/plain')
  , ensureDb              = require('dbjs/valid-dbjs')
  , defineStringLine      = require('dbjs-ext/string/string-line')
  , defineCreateEnum      = require('dbjs-ext/create-enum')
  , _                     = require('mano').i18n.bind('Model')
  , defineUser            = require('./user')
  , defineInstitution     = require('./institution')
  , defineFormSectionBase = require('./form-section-base');

module.exports = memoize(function (db) {
	var StringLine = defineStringLine(ensureDb(db))
	  , Institution = defineInstitution(db)
	  , User = defineUser(db)
	  , FormSectionBase = defineFormSectionBase(db);

	defineCreateEnum(db);

	// Enum for processing step status
	var ProcessingStepStatus = StringLine.createEnum('ProcessingStepStatus', new Map([
		['paused', { label: _("Paused") }],
		['sentBack', { label: _("Sent back") }],
		['rejected', { label: _("Rejected") }],
		['approved', { label: _("Approved") }]
	]));

	return db.Object.extend('ProcessingStep', {
		// Label (name) of processing step
		label: { type: StringLine },
		// If step is processed by single institution
		// then instution should be exposed here
		institution: { type: Institution },
		// Official that processed request at given processing step
		processor: { type: User },

		// Whether given step applies at all
		isApplicable: { type: db.Boolean, value: true },
		// Whether business process is at given step or have passed it
		isReady: { type: db.Boolean, value: function (_observe) {
			return Boolean(_observe(this.master._isSubmitted) && this.isApplicable);
		} },
		// Processing step form fields section (applies only to approved status)
		dataForm: { type: FormSectionBase, nested: true },
		// Eventual reason of file been sent back
		sentBackReason: { type: db.String, required: true  },
		// Eventual reason of rejection
		rejectReason: { type: db.String, required: true  },
		// Status of step
		status: { type: ProcessingStepStatus, required: true },

		// Whether process is pending at step
		isPending: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not pending
			if (!this.isReady) return false;
			// If status not decided then clearly pending
			if (!this.status) return true;
			// If approved, but form data is complete, it's still pending
			if (this.status === 'approved') return (_observe(this.dataForm._status) !== 1);
			// If sent back, but no reason provided, it's still pending
			if (this.status === 'sentBack') return !this.sentBackReason;
			// If rejected, but no reason provided, it's still pending
			if (this.status === 'rejected') return !this.rejectReason;
			// 'paused' is the only option left, that's not pending
			return false;
		} },

		// Whether process is paused at step
		isPaused: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not paused
			if (!this.isReady) return false;
			return (this.status === 'paused');
		} },

		// Whether process was sent back from this step
		isSentBack: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not sent back
			if (!this.isReady) return false;
			// No sentBack status, means no sent back
			if (this.status !== 'sentBack') return false;
			// Provided reason confirms complete sent back
			return Boolean(this.sentBackReason);
		} },

		// Whether process was rejected at this step
		isRejected: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not rejected
			if (!this.isReady) return false;
			// No rejected status, means no it's not rejected
			if (this.status !== 'rejected') return false;
			// Provided reason confirms complete rejection
			return Boolean(this.rejectReason);
		} },

		// Whether process successfully passed this step
		isApproved: { type: db.Boolean, value: function (_observe) {
			// If not ready, then obviously not approved
			if (!this.isReady) return false;
			// No approved status, means no it's not approved
			if (this.status !== 'approved') return false;
			// Completed form confirms step completion
			return (_observe(this.dataForm._status) === 1);
		} },

		// Whether processing of this step has ended
		isClosed: { type: db.Boolean, value: function (_observe) {
			return this.isApproved || this.isRejected;
		} }
	});
}, { normalizer: require('memoizee/normalizers/get-1')() });
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const path = require('path');
const glob = Promise.promisify(require('glob'));
const fs = Promise.promisifyAll(require('fs-extra'));

const TRANSLATION_INVOKE_PATTERN = /\{t\('(.*)'\)/g;

const DEFINED_KEYS_EN = Object.keys(require('../client/app/assets/i18n/en.json'));
const DEFINED_KEYS_DE = Object.keys(require('../client/app/assets/i18n/de.json'));

check().catch((err) => {
  console.error(err);
  process.exit(1);
});

const findTranslationKeysInFileContent = (fileName, content) => {
  const fileMatches = {
    fileName,
    translationKeys: []
  };
  let match;
  while ((match = TRANSLATION_INVOKE_PATTERN.exec(content)) !== null) {
    fileMatches.translationKeys.push(match[1]);
  }
  return fileMatches;
};

const groupByKey = (fileResults) =>
  Object.values(
    fileResults
      .filter((r) => r.translationKeys.length)
      .reduce(
        (groupedByKey, currentFile) =>
          currentFile.translationKeys.reduce((innerGroupedByKey, translationKey) => {
            if (!innerGroupedByKey[translationKey]) {
              innerGroupedByKey[translationKey] = {
                files: [currentFile.fileName],
                key: translationKey
              };
            } else {
              innerGroupedByKey[translationKey].files.push(currentFile.fileName);
            }
            return innerGroupedByKey;
          }, groupedByKey),
        {}
      )
  );

async function check() {
  const files = await glob('../client/app/**/*.js', {cwd: __dirname});
  console.log(`Checking ${files.length} js files for translation key use...`);
  const filePromises = files.map(async (fileName) => {
    const content = await fs.readFile(path.join(__dirname, fileName), 'utf-8');
    return findTranslationKeysInFileContent(fileName, content);
  });
  const fileResults = await Promise.all(filePromises);
  const keys = groupByKey(fileResults);
  keys.sort((kA, kB) => kA.key.localeCompare(kB.key));

  const missingTranslations = keys
    .map((key) => {
      key.isTranslatedDE = DEFINED_KEYS_DE.includes(key.key);
      key.isTranslatedEN = DEFINED_KEYS_EN.includes(key.key);
      return key;
    })
    .filter((res) => !res.isTranslatedDE || !res.isTranslatedEN);

  missingTranslations.forEach((res) => {
    console.error(
      `Key "${
        res.key
      }" is missing at least one translation. Used in File(s) ${res.files.join()}: isTranslatedDE=${
        res.isTranslatedDE
      }  isTranslatedEN=${res.isTranslatedEN}`
    );
  });

  if (missingTranslations.length > 0) {
    throw new Error('Check failed');
  }

  const usedKeyStrings = keys.map((k) => k.key);

  const unusedDE = DEFINED_KEYS_DE.filter((deKey) => !usedKeyStrings.includes(deKey));
  const unusedEN = DEFINED_KEYS_EN.filter((enKey) => !usedKeyStrings.includes(enKey));

  unusedDE.forEach((unusedDeKey) => {
    console.warn(`Defined DE key "${unusedDeKey}" is not used..`);
  });
  unusedEN.forEach((unusedEnKey) => {
    console.warn(`Defined EN key "${unusedEnKey}" is not used..`);
  });

  if (unusedDE.length > 0 || unusedEN.length) {
    throw new Error('Check failed');
  }

  console.log(`checked ${usedKeyStrings.length} translation keys... all good.`);
}
<commit_msg>refactoring<commit_after>const Promise = require('bluebird');
const path = require('path');
const glob = Promise.promisify(require('glob'));
const fs = Promise.promisifyAll(require('fs-extra'));

const TRANSLATION_INVOKE_PATTERN = /\{t\('(.*)'\)/g;

const DEFINED_KEYS_EN = Object.keys(require('../client/app/assets/i18n/en.json'));
const DEFINED_KEYS_DE = Object.keys(require('../client/app/assets/i18n/de.json'));

check().catch((err) => {
  console.error(err);
  process.exit(1);
});

const findTranslationKeysInFileContent = (fileName, content) => {
  const fileMatches = {
    fileName,
    translationKeys: []
  };
  let match;
  while ((match = TRANSLATION_INVOKE_PATTERN.exec(content)) !== null) {
    fileMatches.translationKeys.push(match[1]);
  }
  return fileMatches;
};

const groupByKey = (fileResults) =>
  Object.values(
    fileResults
      .filter((r) => r.translationKeys.length)
      .reduce(
        (groupedByKey, currentFile) =>
          currentFile.translationKeys.reduce((innerGroupedByKey, translationKey) => {
            if (!innerGroupedByKey[translationKey]) {
              innerGroupedByKey[translationKey] = {
                files: [currentFile.fileName],
                key: translationKey
              };
            } else {
              innerGroupedByKey[translationKey].files.push(currentFile.fileName);
            }
            return innerGroupedByKey;
          }, groupedByKey),
        {}
      )
  );

async function getUsedKeysInAppFiles() {
  const files = await glob('../client/app/**/*.js', {cwd: __dirname});
  console.log(`Checking ${files.length} js files for translation key use...`);
  const filePromises = files.map(async (fileName) => {
    const content = await fs.readFile(path.join(__dirname, fileName), 'utf-8');
    return findTranslationKeysInFileContent(fileName, content);
  });
  const fileResults = await Promise.all(filePromises);
  const keys = groupByKey(fileResults);
  keys.sort((kA, kB) => kA.key.localeCompare(kB.key));

  return keys;
}

/**
 * check whether for all used keys, there exists a DE and EN translation
 */
function checkMissing(keys) {
  const missingTranslations = keys
    .map((key) => {
      key.isTranslatedDE = DEFINED_KEYS_DE.includes(key.key);
      key.isTranslatedEN = DEFINED_KEYS_EN.includes(key.key);
      return key;
    })
    .filter((res) => !res.isTranslatedDE || !res.isTranslatedEN);

  missingTranslations.forEach((res) => {
    console.error(
      `Key "${
        res.key
      }" is missing at least one translation. Used in File(s) ${res.files.join()}: isTranslatedDE=${
        res.isTranslatedDE
      }  isTranslatedEN=${res.isTranslatedEN}`
    );
  });

  if (missingTranslations.length > 0) {
    throw new Error('Check failed');
  }
}

function checkUnused(keys) {
  const usedKeyStrings = keys.map((k) => k.key);

  const unusedDE = DEFINED_KEYS_DE.filter((deKey) => !usedKeyStrings.includes(deKey));
  const unusedEN = DEFINED_KEYS_EN.filter((enKey) => !usedKeyStrings.includes(enKey));

  unusedDE.forEach((unusedDeKey) => {
    console.warn(`Defined DE key "${unusedDeKey}" is not used..`);
  });
  unusedEN.forEach((unusedEnKey) => {
    console.warn(`Defined EN key "${unusedEnKey}" is not used..`);
  });

  if (unusedDE.length > 0 || unusedEN.length) {
    throw new Error('Check failed');
  }
}

async function check() {

  const keys = await getUsedKeysInAppFiles();

  checkMissing(keys);

  checkUnused(keys);

  console.log(`checked ${keys.length} translation keys... all good.`);
}

<|endoftext|>