<commit_before>/*
 * Copyright 2015-2016 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import "../../commons/opencga-active-filters.js";
import "../../commons/forms/select-field-filter.js";
import "../../loading-spinner.js";
import "../../commons/tool-header.js";
import "./rga-gene-view.js";
import "./rga-filter.js";
import "./rga-individual-view.js";
import "./rga-variant-view.js";

export default class RgaBrowser extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            query: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = "rga" + UtilsNew.randomString(6);
        this.query = {};
        this.preparedQuery = {};
        this.executedQuery = {};
        this.activeFilterAlias = {
        };
        this.activeTab = {"gene-tab": true};
        this.detail = {};
        this.resource = "RGA";

        // LoF as defaultQuery
        this.defaultQuery = {
            /* consequenceType: "transcript_ablation,splice_acceptor_variant,splice_donor_variant,stop_gained,frameshift_variant,stop_lost,start_lost,transcript_amplification,inframe_insertion,inframe_deletion",
            geneName: ["GRIK5", "ACTN3", "COMT", "TTN", "ABCA12", "ALMS1", "ALOX12B", "ATP8A2", "BLM",
                "CCNO", "CEP290", "CNGB3", "CUL7", "DNAAF1", "DOCK6", "EIF2B5", "ERCC6", "FLG", "HADA",
                "INPP5K", "MANIB1", "MERTK", "MUTYH", "NDUFAF5", "NDUFS7", "OTOG", "PAH", "PDZD7", "PHYH",
                "PKHD1", "PMM2", "RARS2", "SACS", "SGCA", "SIGMAR1", "SPG7", "TTN", "TYR", "USH2A", "WFS1"].join(","),
            // individualId: "118000206" // 118000208 is not a proband, 118000206 is a proband
            individualId: "210010274", // no case
            // knockoutType: "COMP_HET"*/
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    firstUpdated(_changedProperties) {
        $(".bootstrap-select", this).selectpicker();
        UtilsNew.initTooltip(this);
    }

    updated(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.opencgaSessionObserver();
        }
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
    }

    opencgaSessionObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            this.query = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            // this.requestUpdate().then(() => $(".bootstrap-select", this).selectpicker());
        }
    }

    queryObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            // NOTE UtilsNew.objectCompare avoid repeating remote requests.
            if (this.forceQuery || !UtilsNew.objectCompare(this.query, this._query)) {
                this._query = this.query;
                if (this.query) {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                } else {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                }
                // onServerFilterChange() in opencga-active-filters drops a filterchange event when the Filter dropdown is used
                this.dispatchEvent(new CustomEvent("queryChange", {
                    detail: this.preparedQuery
                }
                ));
                this.detail = {};
            } else {
                // console.error("same queries")
            }
            this.forceQuery = false;
            this.requestUpdate();
        }
    }

    notifySearch(query) {
        this.dispatchEvent(new CustomEvent("querySearch", {
            detail: {
                query: query
            },
            bubbles: true,
            composed: true
        }));
    }

    async onRun() {
        // NOTE notifySearch() triggers this chain: notifySearch -> onQueryFilterSearch() on iva-app.js -> this.queries updated -> queryObserver() here in rga-browser
        // queryObserver() here stops the repetition of the remote request by checking if it has changed
        this.query = {...this.preparedQuery};
        // forceQuery is meant to force a new search on click on Search, even if the query hasn't changed
        this.forceQuery = true;
        // updates this.queries in iva-app
        this.notifySearch(this.preparedQuery);
    }

    onClickPill(e) {
        this._changeView(e.currentTarget.dataset.tabId);
    }

    _changeView(tabId) {
        $(".content-pills", this).removeClass("active");
        $(".content-tab", this).removeClass("active");
        for (const tab in this.activeTab) {
            if (Object.prototype.hasOwnProperty.call(this.activeTab, tab)) {
                this.activeTab[tab] = false;
            }
        }
        $(`button.content-pills[data-id=${tabId}]`, this).addClass("active");
        $("#" + tabId, this).addClass("active");
        this.activeTab[tabId] = true;
        this.requestUpdate();
    }

    onFilterChange(e) {
        this.query = e.detail;
    }

    onQueryFilterChange(e) {
        this.preparedQuery = e.detail.query;
        this.requestUpdate();
    }

    onActiveFilterChange(e) {
        this.preparedQuery = {study: this.opencgaSession.study.fqn, ...e.detail};
        this.query = {study: this.opencgaSession.study.fqn, ...e.detail};
    }

    onActiveFilterClear() {
        console.log("onActiveFilterClear");
        this.query = {study: this.opencgaSession.study.fqn};
        this.preparedQuery = {...this.query};
    }

    getDefaultConfig() {
        // return BrowserConf.config;
        return {
            title: "Recessive Variant Browser",
            icon: "fas fa-dna",
            active: false,
            searchButtonText: "Search",
            consequenceTypes: [
                "frameshift_variant",
                "incomplete_terminal_codon_variant",
                "inframe_deletion",
                "inframe_insertion",
                "missense_variant",
                "start_lost",
                "stop_gained",
                "stop_lost",
                "splice_acceptor_variant",
                "splice_donor_variant",
                "feature_truncation",
                "transcript_ablation"
            ],
            views: [
                {
                    id: "gene-tab",
                    name: "Genes",
                    icon: "fa fa-table",
                    active: true
                },
                {
                    id: "individual-tab",
                    name: "Individuals",
                    icon: "fas fa-table"
                },
                {
                    id: "variant-tab",
                    name: "Variants",
                    icon: "fas fa-table"
                }
            ],
            filter: {
                title: "Filter",
                activeFilters: {
                    alias: {
                        // Example:
                        // "region": "Region",
                        // "gene": "Gene",
                        "ct": "Consequence Types"
                    },
                    complexFields: [],
                    hiddenFields: []
                },
                sections: [ // sections and subsections, structure and order is respected
                    {
                        title: "Gene",
                        collapsed: false,
                        fields: [
                            {
                                id: "geneName",
                                name: "Gene",
                                description: "Gene selection"
                            },
                            {
                                id: "knockoutType",
                                name: "Knockout Type",
                                allowedValues: ["COMP_HET", "HOM_ALT"]
                            }
                        ]
                    },
                    {
                        title: "Individual",
                        collapsed: false,
                        fields: [
                            {
                                id: "individualId",
                                name: "Individual",
                                description: "Individual selection"
                            }
                        ]
                    },
                    {
                        title: "Confidence",
                        fields: [
                            {
                                id: "numParents",
                                name: "Include families with",
                                description: "Confidence selection",
                                type: "checkbox",
                                allowedValues: [{id: 0, name: "No parents"}, {id: 1, name: "One parent"}, {id: 2, name: "Two parents"}]
                            }
                            /* {
                                id: "probandOnly",
                                name: "Affected individuals (proband) only",
                                type: "boolean",
                                defaultValue: "no",
                                tooltip: "other info here"
                                // allowedValues: ["father", "mother"]
                            }*/
                        ]
                    },
                    {
                        title: "Variants",
                        fields: [
                            /* {
                                id: "cohort",
                                name: "Cohort",
                                description: "Cohort selection"
                            },*/
                            {
                                id: "variants",
                                name: "Variant Id",
                                placeholder: "13:32906644:-:T,13:32912753:AC:-"
                            },
                            /* {
                                id: "region",
                                name: "Genomic Location",
                                tooltip: tooltips.region
                            },*/
                            {
                                id: "populationFrequency",
                                name: "Select Population Frequency",
                                onlyPopFreqAll: true
                            },
                            {
                                id: "type",
                                name: "Variant types",
                                types: ["SNV", "INDEL", "INSERTION", "DELETION"],
                                tooltip: tooltips.type
                                // layout: "horizontal"
                            },
                            {
                                id: "consequenceType",
                                name: "Consequence type",
                                tooltip: tooltips.consequenceTypeSelect,
                                value: CONSEQUENCE_TYPES.lof
                            },
                            {
                                id: "clinicalSignificance",
                                name: "Clinical Significance",
                            }
                        ]
                    }
                ],
                examples: [
                    {
                        id: "BRCA2 missense variants",
                        active: false,
                        query: {
                            geneName: "BRCA2",
                            ct: "missense_variant"
                        }
                    }
                ],
                result: {
                    grid: {}
                },
                detail: {
                    title: "Selected Variant",
                    views: [
                        {
                            id: "annotationSummary",
                            title: "Summary",
                            active: true
                        }
                    ]
                }
            }
        };
    }

    render() {

        if (!this?.opencgaSession?.study?.fqn) {
            return html`

No public projects available to browse. Please login to continue.

`;
        }

        if (this.opencgaSession.study?.attributes?.RGA !== "INDEXED") {
            return html`

Study ${this?.opencgaSession?.study.name} is not enabled to Recessive Variant Analysis.

`;
        }

        return html`

            <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        <button type="button" class="btn btn-primary ripple" @click="${this.onRun}">
                             ${this._config.searchButtonText || "Run"}
                        </button>

                            Filters

                            <rga-filter
                                    .opencgaSession="${this.opencgaSession}"
                                    .cellbaseClient="${this.cellbaseClient}"
                                    .config="${this._config.filter}"
                                    .query="${this.query}"
                                    .searchButton="${false}"
                                    @queryChange="${this.onQueryFilterChange}">
                            </rga-filter>

                            ${this._config.views && this._config.views.length ? this._config.views.map(tab => html`
                                <button type="button" class="btn btn-success ripple content-pills ${this.activeTab[tab.id] ? "active" : ""}" ?disabled=${tab.disabled} @click="${this.onClickPill}" data-tab-id="${tab.id}">
                                     ${tab.name}
                                </button>
                            `) : html`No view has been configured`}

                        <opencga-active-filters .resource="${this.resource}"
                                                .opencgaSession="${this.opencgaSession}"
                                                .defaultStudy="${this.opencgaSession?.study?.fqn}"
                                                .query="${this.preparedQuery}"
                                                .refresh="${this.executedQuery}"
                                                .alias="${this.activeFilterAlias}"
                                                .config="${this._config.activeFilters}"
                                                .filters="${this._config.filter.examples}"
                                                @activeFilterChange="${this.onActiveFilterChange}"
                                                @activeFilterClear="${this.onActiveFilterClear}">
                        </opencga-active-filters>

                                <rga-gene-view .query=${this.executedQuery} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["gene-tab"]}"></rga-gene-view>

                                <rga-individual-view .query=${this.executedQuery} .config=${this._config} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["individual-tab"]}"></rga-individual-view>

                                <rga-variant-view .query=${this.executedQuery}
                                                  .config=${this._config}
                                                  .opencgaSession="${this.opencgaSession}"
                                                  .cellbaseClient=${this.cellbaseClient}
                                                  .active="${this.activeTab["variant-tab"]}">
                                </rga-variant-view>

        `;
    }

}

customElements.define("rga-browser", RgaBrowser);
<commit_msg>rga-browser.js active-filter warning message fixed<commit_after>/*
 * Copyright 2015-2016 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import "../../commons/opencga-active-filters.js";
import "../../commons/forms/select-field-filter.js";
import "../../loading-spinner.js";
import "../../commons/tool-header.js";
import "./rga-gene-view.js";
import "./rga-filter.js";
import "./rga-individual-view.js";
import "./rga-variant-view.js";

export default class RgaBrowser extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cellbaseClient: {
                type: Object
            },
            query: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = "rga" + UtilsNew.randomString(6);
        this.query = {};
        this.preparedQuery = {};
        this.executedQuery = {};
        this.activeFilterAlias = {
        };
        this.activeTab = {"gene-tab": true};
        this.detail = {};
        this.resource = "RGA";

        // LoF as defaultQuery
        this.defaultQuery = {
            /* consequenceType: "transcript_ablation,splice_acceptor_variant,splice_donor_variant,stop_gained,frameshift_variant,stop_lost,start_lost,transcript_amplification,inframe_insertion,inframe_deletion",
            geneName: ["GRIK5", "ACTN3", "COMT", "TTN", "ABCA12", "ALMS1", "ALOX12B", "ATP8A2", "BLM",
                "CCNO", "CEP290", "CNGB3", "CUL7", "DNAAF1", "DOCK6", "EIF2B5", "ERCC6", "FLG", "HADA",
                "INPP5K", "MANIB1", "MERTK", "MUTYH", "NDUFAF5", "NDUFS7", "OTOG", "PAH", "PDZD7", "PHYH",
                "PKHD1", "PMM2", "RARS2", "SACS", "SGCA", "SIGMAR1", "SPG7", "TTN", "TYR", "USH2A", "WFS1"].join(","),
            // individualId: "118000206" // 118000208 is not a proband, 118000206 is a proband
            individualId: "210010274", // no case
            // knockoutType: "COMP_HET"*/
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = {...this.getDefaultConfig(), ...this.config};
    }

    firstUpdated(_changedProperties) {
        $(".bootstrap-select", this).selectpicker();
        UtilsNew.initTooltip(this);
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession")) {
            this.opencgaSessionObserver();
        }
        if (changedProperties.has("query")) {
            this.queryObserver();
        }
        super.update(changedProperties);
    }

    opencgaSessionObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            this.query = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
            // this.requestUpdate().then(() => $(".bootstrap-select", this).selectpicker());
        }
    }

    queryObserver() {
        if (this?.opencgaSession?.study?.fqn) {
            // NOTE UtilsNew.objectCompare avoid repeating remote requests.
            if (this.forceQuery || !UtilsNew.objectCompare(this.query, this._query)) {
                this._query = this.query;
                if (this.query) {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.query};
                } else {
                    this.preparedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                    this.executedQuery = {study: this.opencgaSession.study.fqn, ...this.defaultQuery};
                }
                // onServerFilterChange() in opencga-active-filters fires an activeFilterChange event when the Filter dropdown is used
                this.dispatchEvent(new CustomEvent("queryChange", {
                    detail: this.preparedQuery
                }
                ));
                this.detail = {};
            } else {
                // console.error("same queries")
            }
            this.forceQuery = false;
            // this.requestUpdate();
        }
    }

    notifySearch(query) {
        this.dispatchEvent(new CustomEvent("querySearch", {
            detail: {
                query: query
            },
            bubbles: true,
            composed: true
        }));
    }

    async onRun() {
        // NOTE notifySearch() triggers this chain: notifySearch -> onQueryFilterSearch() on iva-app.js -> this.queries updated -> queryObserver() here in rga-browser
        // queryObserver() here stops the repetition of the remote request by checking if it has changed
        this.query = {...this.preparedQuery};
        // forceQuery is meant to force a new search on click on Search, even if the query hasn't changed
        this.forceQuery = true;
        // updates this.queries in iva-app
        this.notifySearch(this.preparedQuery);
    }

    onClickPill(e) {
        this._changeView(e.currentTarget.dataset.tabId);
    }

    _changeView(tabId) {
        $(".content-pills", this).removeClass("active");
        $(".content-tab", this).removeClass("active");
        for (const tab in this.activeTab) {
            if (Object.prototype.hasOwnProperty.call(this.activeTab, tab)) {
                this.activeTab[tab] = false;
            }
        }
        $(`button.content-pills[data-id=${tabId}]`, this).addClass("active");
        $("#" + tabId, this).addClass("active");
        this.activeTab[tabId] = true;
        this.requestUpdate();
    }

    onFilterChange(e) {
        this.query = e.detail;
    }

    onQueryFilterChange(e) {
        this.preparedQuery = e.detail.query;
        this.requestUpdate();
    }

    onActiveFilterChange(e) {
        this.preparedQuery = {study: this.opencgaSession.study.fqn, ...e.detail};
        this.query = {study: this.opencgaSession.study.fqn, ...e.detail};
    }

    onActiveFilterClear() {
        console.log("onActiveFilterClear");
        this.query = {study: this.opencgaSession.study.fqn};
        this.preparedQuery = {...this.query};
    }

    getDefaultConfig() {
        // return BrowserConf.config;
        return {
            title: "Recessive Variant Browser",
            icon: "fas fa-dna",
            active: false,
            searchButtonText: "Search",
            consequenceTypes: [
                "frameshift_variant",
                "incomplete_terminal_codon_variant",
                "inframe_deletion",
                "inframe_insertion",
                "missense_variant",
                "start_lost",
                "stop_gained",
                "stop_lost",
                "splice_acceptor_variant",
                "splice_donor_variant",
                "feature_truncation",
                "transcript_ablation"
            ],
            views: [
                {
                    id: "gene-tab",
                    name: "Genes",
                    icon: "fa fa-table",
                    active: true
                },
                {
                    id: "individual-tab",
                    name: "Individuals",
                    icon: "fas fa-table"
                },
                {
                    id: "variant-tab",
                    name: "Variants",
                    icon: "fas fa-table"
                }
            ],
            filter: {
                title: "Filter",
                activeFilters: {
                    alias: {
                        // Example:
                        // "region": "Region",
                        // "gene": "Gene",
                        "ct": "Consequence Types"
                    },
                    complexFields: [],
                    hiddenFields: []
                },
                sections: [ // sections and subsections, structure and order is respected
                    {
                        title: "Gene",
                        collapsed: false,
                        fields: [
                            {
                                id: "geneName",
                                name: "Gene",
                                description: "Gene selection"
                            },
                            {
                                id: "knockoutType",
                                name: "Knockout Type",
                                allowedValues: ["COMP_HET", "HOM_ALT"]
                            }
                        ]
                    },
                    {
                        title: "Individual",
                        collapsed: false,
                        fields: [
                            {
                                id: "individualId",
                                name: "Individual",
                                description: "Individual selection"
                            }
                        ]
                    },
                    {
                        title: "Confidence",
                        fields: [
                            {
                                id: "numParents",
                                name: "Include families with",
                                description: "Confidence selection",
                                type: "checkbox",
                                allowedValues: [{id: 0, name: "No parents"}, {id: 1, name: "One parent"}, {id: 2, name: "Two parents"}]
                            }
                            /* {
                                id: "probandOnly",
                                name: "Affected individuals (proband) only",
                                type: "boolean",
                                defaultValue: "no",
                                tooltip: "other info here"
                                // allowedValues: ["father", "mother"]
                            }*/
                        ]
                    },
                    {
                        title: "Variants",
                        fields: [
                            /* {
                                id: "cohort",
                                name: "Cohort",
                                description: "Cohort selection"
                            },*/
                            {
                                id: "variants",
                                name: "Variant Id",
                                placeholder: "13:32906644:-:T,13:32912753:AC:-"
                            },
                            /* {
                                id: "region",
                                name: "Genomic Location",
                                tooltip: tooltips.region
                            },*/
                            {
                                id: "populationFrequency",
                                name: "Select Population Frequency",
                                onlyPopFreqAll: true
                            },
                            {
                                id: "type",
                                name: "Variant types",
                                types: ["SNV", "INDEL", "INSERTION", "DELETION"],
                                tooltip: tooltips.type
                                // layout: "horizontal"
                            },
                            {
                                id: "consequenceType",
                                name: "Consequence type",
                                tooltip: tooltips.consequenceTypeSelect,
                                value: CONSEQUENCE_TYPES.lof
                            },
                            {
                                id: "clinicalSignificance",
                                name: "Clinical Significance",
                            }
                        ]
                    }
                ],
                examples: [
                    {
                        id: "BRCA2 missense variants",
                        active: false,
                        query: {
                            geneName: "BRCA2",
                            ct: "missense_variant"
                        }
                    }
                ],
                result: {
                    grid: {}
                },
                detail: {
                    title: "Selected Variant",
                    views: [
                        {
                            id: "annotationSummary",
                            title: "Summary",
                            active: true
                        }
                    ]
                }
            }
        };
    }

    render() {

        if (!this?.opencgaSession?.study?.fqn) {
            return html`

No public projects available to browse. Please login to continue.

`;
        }

        if (this.opencgaSession.study?.attributes?.RGA !== "INDEXED") {
            return html`

Study ${this?.opencgaSession?.study.name} is not enabled to Recessive Variant Analysis.

`;
        }

        return html`

            <tool-header title="${this._config.title}" icon="${this._config.icon}"></tool-header>

                        <button type="button" class="btn btn-primary ripple" @click="${this.onRun}">
                             ${this._config.searchButtonText || "Run"}
                        </button>

                            Filters

                            <rga-filter
                                    .opencgaSession="${this.opencgaSession}"
                                    .cellbaseClient="${this.cellbaseClient}"
                                    .config="${this._config.filter}"
                                    .query="${this.query}"
                                    .searchButton="${false}"
                                    @queryChange="${this.onQueryFilterChange}">
                            </rga-filter>

                            ${this._config.views && this._config.views.length ? this._config.views.map(tab => html`
                                <button type="button" class="btn btn-success ripple content-pills ${this.activeTab[tab.id] ? "active" : ""}" ?disabled=${tab.disabled} @click="${this.onClickPill}" data-tab-id="${tab.id}">
                                     ${tab.name}
                                </button>
                            `) : html`No view has been configured`}

                        <opencga-active-filters .resource="${this.resource}"
                                                .opencgaSession="${this.opencgaSession}"
                                                .defaultStudy="${this.opencgaSession?.study?.fqn}"
                                                .query="${this.preparedQuery}"
                                                .refresh="${this.executedQuery}"
                                                .alias="${this.activeFilterAlias}"
                                                .config="${this._config.activeFilters}"
                                                .filters="${this._config.filter.examples}"
                                                @activeFilterChange="${this.onActiveFilterChange}"
                                                @activeFilterClear="${this.onActiveFilterClear}">
                        </opencga-active-filters>

                                <rga-gene-view .query=${this.executedQuery} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["gene-tab"]}"></rga-gene-view>

                                <rga-individual-view .query=${this.executedQuery} .config=${this._config} .opencgaSession="${this.opencgaSession}" .active="${this.activeTab["individual-tab"]}"></rga-individual-view>

                                <rga-variant-view .query=${this.executedQuery}
                                                  .config=${this._config}
                                                  .opencgaSession="${this.opencgaSession}"
                                                  .cellbaseClient=${this.cellbaseClient}
                                                  .active="${this.activeTab["variant-tab"]}">
                                </rga-variant-view>

        `;
    }

}

customElements.define("rga-browser", RgaBrowser);
<|endoftext|>


<commit_before><commit_msg>Add some building safeguards<commit_after><|endoftext|>


<commit_before>var Substance = require("substance");
var $$ = React.createElement;

// Entity types
var Prison = require("./entity_types/prison");
var Toponym = require("./entity_types/toponym");
var Person = require("./entity_types/person");
var Definition = require("./entity_types/definition");

var _ = require("substance/helpers");

var EntitiesPanel = React.createClass({
  displayName: "Entities",

  contextTypes: {
    backend: React.PropTypes.object.isRequired
  },

  getReferencedEntityIds: function() {
    var doc = this.props.writerCtrl.doc;
    var entityReferences = doc.entityReferencesIndex.get();
    return _.map(entityReferences, function(entityRef, key) {
      return entityRef.target;
    });
  },

  // Data loading methods
  // ------------

  loadEntities: function() {
    var self = this;

    var backend = this.context.backend;
    var entityIds = self.getReferencedEntityIds();

    backend.getEntities(entityIds, function(err, entities) {
      window.cache.entities = entities;

      // Finished simulated loading of entities
      self.setState({
        entities: entities
      });
    });
  },

  restoreEntitiesFromCache: function() {
    // 1. check if cache is valid
    if (window.cache && window.cache.entities) {
      return window.cache.entities;
    }
  },

  cacheInvalid: function() {
    // Sneak into cache
    if (window.cache.entities) {
      var currentEntities = _.uniq(this.getReferencedEntityIds());
      var cachedEntities = _.uniq(_.pluck(window.cache.entities, 'id'));

      if (currentEntities.length !== cachedEntities.length) return true;
      var overlappingEntities = _.intersection(currentEntities, cachedEntities);
      // Cache condition met when there's a full overlap
      if (overlappingEntities.length === currentEntities.length) return false;
    }

    return true;
  },

  // State relevant things
  // ------------

  getInitialState: function() {
    return {
      entities: this.restoreEntitiesFromCache() || []
    };
  },

  // Events
  // ------------

  componentDidMount: function() {
    if (this.cacheInvalid()) {
      this.loadEntities();
    }
  },

  componentDidUpdate: function() {
    if (this.cacheInvalid()) {
      this.loadEntities();
    }
  },

  handleToggle: function(entityId) {
    var writerCtrl = this.props.writerCtrl;

    if (writerCtrl.state.entityId === entityId) {
      writerCtrl.replaceState({
        contextId: "entities"
      });
    } else {
      writerCtrl.replaceState({
        contextId: "entities",
        entityId: entityId
      });
    }
  },

  // Rendering
  // -------------------

  getEntityElement: function(entity) {
    entity.handleToggle = this.handleToggle;

    if (entity.type === "prison") {
      return $$(Prison, entity);
    } else if (entity.type === "toponym") {
      return $$(Toponym, entity);
    } else if (entity.type === "person") {
      return $$(Person, entity);
    } else if (entity.type === "definition") {
      return $$(Definition, entity);
    }
    throw new Error('No view component for '+ entity.type);
  },

  render: function() {
    var state = this.state;
    var props = this.props;

    var getElem = this.getEntityElement;
    var entityNodes = state.entities.map(function(entity, index) {
      // Dynamically assign active state
      entity.active = entity.id === props.entityId;
      entity.key = entity.id;
      return getElem(entity);
    });

    return $$("div", {className: "panel entities-panel-component"},
      $$('div', {className: 'panel-content'},
        $$('div', {className: 'entities'},
          entityNodes
        )
      )
    );
  }
});

EntitiesPanel.contextId = "entities";
EntitiesPanel.icon = "fa-bullseye";

module.exports = EntitiesPanel;
<commit_msg>Remove caching logic from entities panel.<commit_after>var Substance = require("substance");
var $$ = React.createElement;

// Entity types
var Prison = require("./entity_types/prison");
var Toponym = require("./entity_types/toponym");
var Person = require("./entity_types/person");
var Definition = require("./entity_types/definition");

var _ = require("substance/helpers");

var EntitiesPanel = React.createClass({
  displayName: "Entities",

  contextTypes: {
    backend: React.PropTypes.object.isRequired
  },

  getReferencedEntityIds: function() {
    var doc = this.props.writerCtrl.doc;
    var entityReferences = doc.entityReferencesIndex.get();
    return _.map(entityReferences, function(entityRef, key) {
      return entityRef.target;
    });
  },

  // Data loading methods
  // ------------

  loadEntities: function() {
    var self = this;

    var backend = this.context.backend;
    var entityIds = self.getReferencedEntityIds();

    backend.getEntities(entityIds, function(err, entities) {
      // window.cache.entities = entities;

      // Finished simulated loading of entities
      self.setState({
        entities: entities
      });
    });
  },

  // restoreEntitiesFromCache: function() {
  //   // 1. check if cache is valid
  //   if (window.cache && window.cache.entities) {
  //     return window.cache.entities;
  //   }
  // },

  // cacheInvalid: function() {
  //   // Sneak into cache
  //   if (window.cache.entities) {
  //     var currentEntities = _.uniq(this.getReferencedEntityIds());
  //     var cachedEntities = _.uniq(_.pluck(window.cache.entities, 'id'));

  //     if (currentEntities.length !== cachedEntities.length) return true;
  //     var overlappingEntities = _.intersection(currentEntities, cachedEntities);
  //     // Cache condition met when there's a full overlap
  //     if (overlappingEntities.length === currentEntities.length) return false;
  //   }

  //   return true;
  // },

  // State relevant things
  // ------------

  getInitialState: function() {
    return {
      entities: []
    };
  },

  // Events
  // ------------

  componentDidMount: function() {
    this.loadEntities();
  },

  componentWillReceiveProps: function() {
    this.loadEntities();
  },

  handleToggle: function(entityId) {
    var writerCtrl = this.props.writerCtrl;

    if (writerCtrl.state.entityId === entityId) {
      writerCtrl.replaceState({
        contextId: "entities"
      });
    } else {
      writerCtrl.replaceState({
        contextId: "entities",
        entityId: entityId
      });
    }
  },

  // Rendering
  // -------------------

  getEntityElement: function(entity) {
    entity.handleToggle = this.handleToggle;

    if (entity.type === "prison") {
      return $$(Prison, entity);
    } else if (entity.type === "toponym") {
      return $$(Toponym, entity);
    } else if (entity.type === "person") {
      return $$(Person, entity);
    } else if (entity.type === "definition") {
      return $$(Definition, entity);
    }
    throw new Error('No view component for '+ entity.type);
  },

  render: function() {
    var state = this.state;
    var props = this.props;

    var getElem = this.getEntityElement;
    var entityNodes = state.entities.map(function(entity, index) {
      // Dynamically assign active state
      entity.active = entity.id === props.entityId;
      entity.key = entity.id;
      return getElem(entity);
    });

    return $$("div", {className: "panel entities-panel-component"},
      $$('div', {className: 'panel-content'},
        $$('div', {className: 'entities'},
          entityNodes
        )
      )
    );
  }
});

EntitiesPanel.contextId = "entities";
EntitiesPanel.icon = "fa-bullseye";

module.exports = EntitiesPanel;
<|endoftext|>


<commit_before>//= require libs/jquery/plugins/jquery.base64
//= require vendor/polyfills/bind
//= require analytics
//= require start-modules
//= require govuk/selection-buttons
//= require govuk/shim-links-with-button-role
//= require govuk_publishing_components/components/header
//= require core
<commit_msg>Use correct component references<commit_after>//= require libs/jquery/plugins/jquery.base64
//= require vendor/polyfills/bind
//= require analytics
//= require start-modules
//= require govuk/selection-buttons
//= require govuk/shim-links-with-button-role
//= require govuk_publishing_components/components/layout-header
//= require core
<|endoftext|>


<commit_before>import { Icon, ListItem } from 'react-native-elements';
import { Image, Linking, Platform, TouchableHighlight } from 'react-native';
import { NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import React from 'react';
import * as StoreReview from 'react-native-store-review';

import { styles } from './styles';
import BlankContainer from '../../components/library/BlankContainer';
import SectionHeading from '../../components/library/SectionHeading';
import I18n from '../../i18n';

/*
      <SectionHeading style={{ paddingTop: 0 }}>
        {I18n.t('account')}
      </SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title="Login"
        leftIcon={{ name: 'login-variant', type: 'material-community' }}
        onPress={() => console.log('login')}
      />
      <ListItem
        containerStyle={[styles.listItemContainer]}
        title="Register"
        leftIcon={{ name: 'account-plus', type: 'material-community' }}
        onPress={() => console.log('register')}
      />
*/

const ProfileStartScreen = ({
  navigateToAboutApp,
  navigateToAboutFOIMaster,
  navigateToFAQ,
  navigateToContact,
  navigateToDataUsePolicy,
  navigateToFeedback,
  navigateToAcknowledgements,
  navigateToIntroVideo,
  navigateToTermsOfUse,
}) => {
  return (
    <BlankContainer innerStyle={{ paddingHorizontal: 0 }}>
      <SectionHeading>{I18n.t('information')}</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.video')}
        leftIcon={{ name: 'ondemand-video' }}
        onPress={navigateToIntroVideo}
      />
      <ListItem
        containerStyle={[styles.listItemContainer]}
        title={I18n.t('moreScreen.aboutFDS')}
        leftIcon={{ name: 'info' }}
        onPress={navigateToAboutApp}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.aboutFOI')}
        leftIcon={{ name: 'info' }}
        onPress={navigateToAboutFOIMaster}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.faq')}
        leftIcon={{ name: 'question-answer' }}
        onPress={navigateToFAQ}
      />
      <SectionHeading>{I18n.t('miscellaneous')}</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.contact')}
        leftIcon={{ name: 'mail' }}
        onPress={navigateToContact}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.dataUse')}
        leftIcon={{ name: 'scale-balance', type: 'material-community' }}
        onPress={navigateToDataUsePolicy}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.tos')}
        leftIcon={{ name: 'scale-balance', type: 'material-community' }}
        onPress={navigateToTermsOfUse}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.acknowledgements')}
        leftIcon={{ name: 'copyright' }}
        onPress={navigateToAcknowledgements}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.feedback')}
        leftIcon={{ name: 'feedback' }}
        onPress={navigateToFeedback}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.rate')}
        leftIcon={{ name: 'star' }}
        onPress={() => {
          if (Platform.OS === 'ios' && StoreReview.isAvailable) {
            StoreReview.requestReview();
          }
          if (Platform.OS === 'android') {
            // TODO: Link to app in playstore
          }
        }}
        hideChevron
      />
      <SectionHeading>Links</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.donate')}
        leftIcon={{ name: 'euro-symbol' }}
        rightIcon={{ name: 'link' }}
        onPress={() =>
          Linking.openURL('https://fragdenstaat.de/hilfe/spenden/')}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.blog')}
        leftIcon={{ name: 'library-books', type: 'material-community' }}
        rightIcon={{ name: 'link' }}
        onPress={() => Linking.openURL('http://blog.fragdenstaat.de')}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        leftIcon={{ name: 'mic' }}
        title={I18n.t('moreScreen.press')}
        rightIcon={{ name: 'link' }}
        onPress={() =>
          Linking.openURL('https://fragdenstaat.de/hilfe/ueber/presse/')}
      />
      <Icon
        name="twitter-box"
        type="material-community"
        color="#1DA1F2"
        onPress={() => Linking.openURL('https://twitter.com/fragdenstaat')}
        size={50}
      />
      <Icon
        name="facebook-box"
        type="material-community"
        color="#3B5998"
        onPress={() =>
          Linking.openURL('https://www.facebook.com/fragdenstaat.de/')}
        size={50}
      />
      <TouchableHighlight
        style={{ alignSelf: 'center' }}
        onPress={() => Linking.openURL('https://fragdenstaat.de')}
      >
        <Image
          style={{ width: 50, height: 50 }}
          source={require('../../../assets/logo/original_fds_logo_small.jpg')}
        />
      </TouchableHighlight>
    </BlankContainer>
  );
};

ProfileStartScreen.navigationOptions = {
  title: I18n.t('more'),
};

const mapStateToProps = state => {
  return {};
};

const mapDispatchToProps = dispatch => {
  return {
    navigateToAboutApp: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileAboutApp' })),
    navigateToIntroVideo: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileIntroVideo' })),
    navigateToAboutFOIMaster: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileAboutFOI' })),
    navigateToContact: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileContact' })),
    navigateToDataUsePolicy: () =>
      dispatch(
        NavigationActions.navigate({ routeName: 'ProfileDataUsePolicy' })
      ),
    navigateToTermsOfUse: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileTermsOfUse' })),
    navigateToFAQ: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileFAQMaster' })),
    navigateToFeedback: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileFeedback' })),
    navigateToAcknowledgements: () =>
      dispatch(
        NavigationActions.navigate({ routeName: 'ProfileAcknowledgements' })
      ),
  };
};
export default connect(mapStateToProps, mapDispatchToProps)(ProfileStartScreen);
<commit_msg>fix unaligned list item<commit_after>import { Icon, ListItem } from 'react-native-elements';
import { Image, Linking, Platform, TouchableHighlight } from 'react-native';
import { NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import React from 'react';
import * as StoreReview from 'react-native-store-review';

import { styles } from './styles';
import BlankContainer from '../../components/library/BlankContainer';
import SectionHeading from '../../components/library/SectionHeading';
import I18n from '../../i18n';

/*
      <SectionHeading style={{ paddingTop: 0 }}>
        {I18n.t('account')}
      </SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title="Login"
        leftIcon={{ name: 'login-variant', type: 'material-community' }}
        onPress={() => console.log('login')}
      />
      <ListItem
        containerStyle={[styles.listItemContainer]}
        title="Register"
        leftIcon={{ name: 'account-plus', type: 'material-community' }}
        onPress={() => console.log('register')}
      />
*/

const ProfileStartScreen = ({
  navigateToAboutApp,
  navigateToAboutFOIMaster,
  navigateToFAQ,
  navigateToContact,
  navigateToDataUsePolicy,
  navigateToFeedback,
  navigateToAcknowledgements,
  navigateToIntroVideo,
  navigateToTermsOfUse,
}) => {
  return (
    <BlankContainer innerStyle={{ paddingHorizontal: 0 }}>
      <SectionHeading>{I18n.t('information')}</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.video')}
        leftIcon={{ name: 'ondemand-video' }}
        onPress={navigateToIntroVideo}
      />
      <ListItem
        containerStyle={[styles.listItemContainer]}
        title={I18n.t('moreScreen.aboutFDS')}
        leftIcon={{ name: 'info' }}
        onPress={navigateToAboutApp}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.aboutFOI')}
        leftIcon={{ name: 'info' }}
        onPress={navigateToAboutFOIMaster}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.faq')}
        leftIcon={{ name: 'question-answer' }}
        onPress={navigateToFAQ}
      />
      <SectionHeading>{I18n.t('miscellaneous')}</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.contact')}
        leftIcon={{ name: 'mail' }}
        onPress={navigateToContact}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.dataUse')}
        leftIcon={{ name: 'scale-balance', type: 'material-community' }}
        onPress={navigateToDataUsePolicy}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.tos')}
        leftIcon={{ name: 'scale-balance', type: 'material-community' }}
        onPress={navigateToTermsOfUse}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.acknowledgements')}
        leftIcon={{ name: 'copyright' }}
        onPress={navigateToAcknowledgements}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.feedback')}
        leftIcon={{ name: 'feedback' }}
        onPress={navigateToFeedback}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.rate')}
        leftIcon={{ name: 'star' }}
        onPress={() => {
          if (Platform.OS === 'ios' && StoreReview.isAvailable) {
            StoreReview.requestReview();
          }
          if (Platform.OS === 'android') {
            // TODO: Link to app in playstore
          }
        }}
        rightIcon={{ name: 'chevron-right', color: 'white' }} // chevronHide shitfs the label to the left
      />
      <SectionHeading>Links</SectionHeading>
      <ListItem
        containerStyle={[styles.listItemContainer, styles.firstItemContainer]}
        title={I18n.t('moreScreen.donate')}
        leftIcon={{ name: 'euro-symbol' }}
        rightIcon={{ name: 'link' }}
        onPress={() =>
          Linking.openURL('https://fragdenstaat.de/hilfe/spenden/')}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        title={I18n.t('moreScreen.blog')}
        leftIcon={{ name: 'library-books', type: 'material-community' }}
        rightIcon={{ name: 'link' }}
        onPress={() => Linking.openURL('http://blog.fragdenstaat.de')}
      />
      <ListItem
        containerStyle={styles.listItemContainer}
        leftIcon={{ name: 'mic' }}
        title={I18n.t('moreScreen.press')}
        rightIcon={{ name: 'link' }}
        onPress={() =>
          Linking.openURL('https://fragdenstaat.de/hilfe/ueber/presse/')}
      />
      <Icon
        name="twitter-box"
        type="material-community"
        color="#1DA1F2"
        onPress={() => Linking.openURL('https://twitter.com/fragdenstaat')}
        size={50}
      />
      <Icon
        name="facebook-box"
        type="material-community"
        color="#3B5998"
        onPress={() =>
          Linking.openURL('https://www.facebook.com/fragdenstaat.de/')}
        size={50}
      />
      <TouchableHighlight
        style={{ alignSelf: 'center' }}
        onPress={() => Linking.openURL('https://fragdenstaat.de')}
      >
        <Image
          style={{ width: 50, height: 50 }}
          source={require('../../../assets/logo/original_fds_logo_small.jpg')}
        />
      </TouchableHighlight>
    </BlankContainer>
  );
};

ProfileStartScreen.navigationOptions = {
  title: I18n.t('more'),
};

const mapStateToProps = state => {
  return {};
};

const mapDispatchToProps = dispatch => {
  return {
    navigateToAboutApp: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileAboutApp' })),
    navigateToIntroVideo: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileIntroVideo' })),
    navigateToAboutFOIMaster: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileAboutFOI' })),
    navigateToContact: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileContact' })),
    navigateToDataUsePolicy: () =>
      dispatch(
        NavigationActions.navigate({ routeName: 'ProfileDataUsePolicy' })
      ),
    navigateToTermsOfUse: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileTermsOfUse' })),
    navigateToFAQ: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileFAQMaster' })),
    navigateToFeedback: () =>
      dispatch(NavigationActions.navigate({ routeName: 'ProfileFeedback' })),
    navigateToAcknowledgements: () =>
      dispatch(
        NavigationActions.navigate({ routeName: 'ProfileAcknowledgements' })
      ),
  };
};
export default connect(mapStateToProps, mapDispatchToProps)(ProfileStartScreen);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
    View,
    Text,
    Linking
} from 'react-native';
import { connect } from 'react-redux';
import _ from 'lodash';
import I18n from 'react-native-i18n';
import Config from '../../constants/Config';
import { answerSet,
         hideModal,
         showModal,
         setFreetextAvailable,
         solveMission,
        showAchievements,
        resetSolutionSent } from '../../actions/AnswerSelectionActions';
import { showMission, downloadMissions } from '../../actions/MissionActions';
import { forceViewUpdateAchievements,
         forceViewUpdateHighscore,
         forceViewUpdateProfile } from '../../actions/NavigationActions';
import AnswerSelection from './AnswerSelection';
import { Input,
        Button,
        KortCoin,
        Popup,
        ModalSpinner,
        ButtonWithImage } from '../common';
import OpeningHours from './types/OpeningHours';
import AchievementPopup from '../achievements/AchievementPopup';
import CoordinateCalculations from '../../geolocation/CoordinateCalculations';

class MissionSolver extends Component {

    componentDidMount() {
        this.props.answerSet('', '');
        this.props.setFreetextAvailable('');
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.activeMission.inputType.options.length === 0 ||
            (nextProps.activeMission.inputType.options.length > 0 &&
                nextProps.activeMission.inputType.name === 'select')) {
            this.props.setFreetextAvailable(this.props.activeMission.inputType.name);
        }

        if (nextProps.solutionSent) {
            this.props.forceViewUpdateProfile(true);
            this.props.forceViewUpdateHighscore(true);
            if (nextProps.newAchievements.length > 0) {
                this.props.forceViewUpdateAchievements(true);
            }
            this.props.resetSolutionSent();
        }
    }

    onModalConfirm() {
        if (this.props.modalType === 'unsolvable') {
            const mission = this.props.activeMission;
            this.props.solveMission(this.props.user.id, mission, '', '', false, 0);
        } else if (this.props.modalType !== 'notLoggedIn') {
            this.props.hideModal(true);

            if (this.props.newAchievements.length > 0) {
                this.props.showAchievements(0);
            } else {
                this.hideMission();
            }
        } else {
            this.props.hideModal(true);
        }
    }

    onModalDecline() {
        this.props.hideModal(true);
    }

    hideMission() {
        this.props.showMission(false);
        this.props.downloadMissions(this.props.centerCoordinates,
        Config.RADIUS_IN_M_FOR_MISSION_FETCHING, true, this.props.user.id);
    }

    /*
        true if mission is within 5km perimeter of location or user has at least 20 missions solved
    */
    checkIfMissionInsidePerimeter() {
        const latitude = this.props.activeMission.annotationCoordinate[0];
        const longitude = this.props.activeMission.annotationCoordinate[1];
        if (this.props.user.mission_count >= Config.NO_OF_MISSIONS_FOR_PERIMETER_CHECK) {
            return true;
        } else if (this.props.currentLocation && CoordinateCalculations.calculateDistance(
                { latitude, longitude }, this.props.currentLocation.coords)
                <= Config.RADIUS_IN_M_FOR_MISSION_SOLVING) {
                    return true;
            }
        return false;
    }

    solveMission() {
        const mission = this.props.activeMission;
        const validationMessage = this.validateInput() ? '' :
            mission.inputType.constraints.description;
        if (!this.props.user.loggedIn) {
             this.props.showModal(false, I18n.t('not_logged_in_error'), 'notLoggedIn');
        } else if (!this.checkIfMissionInsidePerimeter()) {
            this.props.showModal(false, I18n.t('mission_message_not_within_perimeter',
                { missionCount: Config.NO_OF_MISSIONS_FOR_PERIMETER_CHECK,
                  distance: Config.RADIUS_IN_M_FOR_MISSION_SOLVING }));
        } else if (this.props.answer !== '' && validationMessage === '') {
            this.props.solveMission(
                this.props.user.id, mission, this.props.answer, this.props.selectedOption, true,
                this.props.stats ? Config.ADDITIONAL_KOINS_FOR_STATS : 0);
        } else {
            this.props.showModal(false, I18n.t('mission_message_valid_answer',
                { validationMessage }), 'validation');
        }
    }

    confirmUnsolvable() {
        this.props.showModal(true, I18n.t('mission_message_unsolvable'), 'unsolvable');
    }

    validateInput() {
        const { re, upperBound, lowerBound } = this.props.activeMission.inputType.constraints;
            if (re) {
                const regex = new RegExp(re);
                return regex.test(this.props.answer);
            } else if (upperBound && lowerBound) {
                return lowerBound <= parseFloat(this.props.answer) &&
                    parseFloat(this.props.answer) <= upperBound;
            }
        return true;
    }

    createListItem(index, item) {
        return { value: index, name: item };
    }

    determineKeyboardType() {
        switch (this.props.freetextType) {
            case 'text':
                return 'default';
            case 'number':
                return 'numeric';
            default:
                return '';
        }
    }

    showNextAchievementOrClose() {
        if (this.props.currentAchievementIndex < this.props.newAchievements.length - 1) {
            this.props.showAchievements(this.props.currentAchievementIndex + 1);
        } else {
            this.props.showAchievements(-1);
            this.hideMission();
        }
    }

    openOSM() {
        const { osmId, osmType } = this.props.activeMission;
        const url = `https://www.openstreetmap.org/${osmType}/${osmId}`;
        Linking.openURL(url);
    }

    renderAnswerSelection() {
        //TODO handle different types of mission in a nicer way
        console.log('type', this.props.activeMission);
        if (this.props.activeMission.error_type === 'opening_hours') {
            return (<OpeningHours
                setAnswer={value => this.props.answerSet(value, '')}
                answer={this.props.answer}
            />);
        }

        if (!_.isEmpty(this.props.activeMission.inputType.options)) {
            const options = _.map(this.props.activeMission.inputType.options,
                (item, index) => { return { value: index, name: item }; });
            const sortedOptions = _.sortBy(options, 'name');
            if (this.props.activeMission.inputType.name !== 'select') {
                sortedOptions.unshift({ value: -1, name: I18n.t('mission_answer_options_other') });
            }
            return <AnswerSelection options={sortedOptions} />;
        }
        return null;
    }

    renderAnswerFreetext() {
        const keyboardType = this.determineKeyboardType();
        if (keyboardType !== '') {
            return (
                <Input
                    placeHolder={I18n.t('mission_answer_placeholder')}
                    keyboardType={keyboardType}
                    value={this.props.answer}
                    onChangeText={value => this.props.answerSet(value, '')}
                />
            );
        }
        return null;
    }

    renderModalHeader() {
        if (this.props.modalType === 'win') {
            return <KortCoin animationStyle='win'>{this.props.activeMission.koinReward}</KortCoin>;
        }
    }

    renderModal() {
                return (
                <Popup
                    onAccept={this.onModalConfirm.bind(this)}
                    visible={this.props.modalVisible}
                    confirm={this.props.modalConfirm}
                    onDecline={this.onModalDecline.bind(this)}
                    message={this.props.modalText}
                >
                     {this.renderModalHeader()}
                </Popup>
            );
    }

    renderAchievementsModal() {
        if (this.props.currentAchievementIndex > -1 &&
            this.validateInput() !== '' &&
            this.checkIfMissionInsidePerimeter()) {
        const currentAchievement = this.props.newAchievements[this.props.currentAchievementIndex];
             return (
                    <AchievementPopup
                        visible
                        onAccept={() => this.showNextAchievementOrClose()}
                        achievement={currentAchievement}
                        animateIndef
                    />
            );
        }
       return null;
    }

    renderSending() {
        return (
            <View>
                <ModalSpinner
                    visible={this.props.sending}
                />
                <Popup
                    visible={this.props.errorMsg !== null}
                    onAccept={() => this.props.hideModal(true)}
                    message={I18n.t('error_message_bad_connectivity')}
                />
            </View>
        );
    }

    render() {
        const { bgColor, missionTextStyle, headerStyle, additionalButtonsStyle,
            containerStyle, unsolvableButtonStyle, completeMissionButtonStyle } = styles;
        return (
            <View style={[bgColor, { height: this.props.missionViewHeight }]}>
                <View style={headerStyle}>
                    <KortCoin animationStyle='normal'>
                         {this.props.activeMission.koinReward}
                    </KortCoin>
                    <Text style={missionTextStyle}>{this.props.activeMission.question}</Text>
                </View>
                <View style={containerStyle} >
                    {this.renderAnswerSelection()}
                    {this.renderAnswerFreetext()}
                        <Button
                            onPress={this.solveMission.bind(this)}
                            style={completeMissionButtonStyle}
                        >Complete Mission
                        </Button>
                        <View style={additionalButtonsStyle}>
                        <ButtonWithImage
                            style={{ borderWidth: 1, paddingRight: 20 }}
                            onPress={() => this.openOSM()}
                            imgSource={'osm'}
                        >OSM
                        </ButtonWithImage>
                        <Button
                            onPress={this.confirmUnsolvable.bind(this)}
                            style={unsolvableButtonStyle}
                        >
                            Unable to solve
                        </Button>
                        </View>
               </View>
               {this.renderModal()}
               {this.renderAchievementsModal()}
               {this.renderSending()}
            </View>
        );
    }
}

const styles = {
    headerStyle: {
        height: 80,
        flexDirection: 'row',
        alignItems: 'center',
        marginLeft: 10,
        paddingRight: 30,
        paddingBottom: 20,
        backgroundColor: 'transparent'

    },
    missionTextStyle: {
        color: 'white',
        marginLeft: 10,
        marginRight: 40,
    },
    containerStyle: {
        flex: 1,
        alignSelf: 'stretch',
        alignItems: 'center',
        backgroundColor: 'transparent',
        justifyContent: 'space-between',
        marginBottom: 40,
    },
    bgColor: {
        backgroundColor: '#395971',
        alignItems: 'center',
        height: 300,
    },
    completeMissionButtonStyle: {
        alignSelf: 'center',
        justifyContent: 'center',
        backgroundColor: '#fff',
        borderRadius: 5,
        borderWidth: 1,
        borderColor: '#657C8E',
        marginLeft: 5,
        marginRight: 5,
        height: 50,
        width: 300,
    },
    unsolvableButtonStyle: {
        alignSelf: 'center',
        width: 150,
    },
    additionalButtonsStyle: {
        flexDirection: 'row',
    }
};

const mapStateToProps = ({ answerReducer, missionReducer,
    authReducer, mapReducer, settingsReducer, navigationReducer }) => {
    const { freetextType, answer, selectedOption, modalVisible,
        modalConfirm, modalText, modalType, sending,
        newAchievements, currentAchievementIndex, errorMsg, solutionSent } = answerReducer;
    const { activeMission, missionViewHeight } = missionReducer;
    const { user } = authReducer;
    const { updateHighscoreView, updateProfileView, updateAchievementsView } = navigationReducer;
    const { centerCoordinates, currentLocation } = mapReducer;
    const { stats } = settingsReducer;
    return { freetextType,
        answer,
        selectedOption,
        modalVisible,
        modalConfirm,
        modalText,
        modalType,
        sending,
        newAchievements,
        currentAchievementIndex,
        errorMsg,
        activeMission,
        missionViewHeight,
        user,
        centerCoordinates,
        currentLocation,
        stats,
        updateHighscoreView,
        updateAchievementsView,
        updateProfileView,
        solutionSent };
};

export default connect(mapStateToProps,
{ answerSet,
    hideModal,
    showModal,
    setFreetextAvailable,
    solveMission,
    showAchievements,
    showMission,
    downloadMissions,
    forceViewUpdateAchievements,
    forceViewUpdateHighscore,
    forceViewUpdateProfile,
    resetSolutionSent })(MissionSolver);
<commit_msg>do not show old achievements when validation fails<commit_after>import React, { Component } from 'react';
import {
    View,
    Text,
    Linking
} from 'react-native';
import { connect } from 'react-redux';
import _ from 'lodash';
import I18n from 'react-native-i18n';
import Config from '../../constants/Config';
import { answerSet,
         hideModal,
         showModal,
         setFreetextAvailable,
         solveMission,
        showAchievements,
        resetSolutionSent } from '../../actions/AnswerSelectionActions';
import { showMission, downloadMissions } from '../../actions/MissionActions';
import { forceViewUpdateAchievements,
         forceViewUpdateHighscore,
         forceViewUpdateProfile } from '../../actions/NavigationActions';
import AnswerSelection from './AnswerSelection';
import { Input,
        Button,
        KortCoin,
        Popup,
        ModalSpinner,
        ButtonWithImage } from '../common';
import OpeningHours from './types/OpeningHours';
import AchievementPopup from '../achievements/AchievementPopup';
import CoordinateCalculations from '../../geolocation/CoordinateCalculations';

class MissionSolver extends Component {

    componentDidMount() {
        this.props.answerSet('', '');
        this.props.setFreetextAvailable('');
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.activeMission.inputType.options.length === 0 ||
            (nextProps.activeMission.inputType.options.length > 0 &&
                nextProps.activeMission.inputType.name === 'select')) {
            this.props.setFreetextAvailable(this.props.activeMission.inputType.name);
        }

        if (nextProps.solutionSent) {
            this.props.forceViewUpdateProfile(true);
            this.props.forceViewUpdateHighscore(true);
            if (nextProps.newAchievements.length > 0) {
                this.props.forceViewUpdateAchievements(true);
            }
            this.props.resetSolutionSent();
        }
    }

    onModalConfirm() {
        if (this.props.modalType === 'unsolvable') {
            const mission = this.props.activeMission;
            this.props.solveMission(this.props.user.id, mission, '', '', false, 0);
        } else if (this.props.modalType !== 'notLoggedIn') {
            this.props.hideModal(true);

            if (this.props.newAchievements.length > 0 && this.validateInput()) {
                this.props.showAchievements(0);
            } else {
                this.hideMission();
            }
        } else {
            this.props.hideModal(true);
        }
    }

    onModalDecline() {
        this.props.hideModal(true);
    }

    hideMission() {
        this.props.showMission(false);
        this.props.downloadMissions(this.props.centerCoordinates,
        Config.RADIUS_IN_M_FOR_MISSION_FETCHING, true, this.props.user.id);
    }

    /*
        true if mission is within 5km perimeter of location or user has at least 20 missions solved
    */
    checkIfMissionInsidePerimeter() {
        const latitude = this.props.activeMission.annotationCoordinate[0];
        const longitude = this.props.activeMission.annotationCoordinate[1];
        if (this.props.user.mission_count >= Config.NO_OF_MISSIONS_FOR_PERIMETER_CHECK) {
            return true;
        } else if (this.props.currentLocation && CoordinateCalculations.calculateDistance(
                { latitude, longitude }, this.props.currentLocation.coords)
                <= Config.RADIUS_IN_M_FOR_MISSION_SOLVING) {
                    return true;
            }
        return false;
    }

    solveMission() {
        const mission = this.props.activeMission;
        const validationMessage = this.validateInput() ? '' :
            mission.inputType.constraints.description;
        if (!this.props.user.loggedIn) {
             this.props.showModal(false, I18n.t('not_logged_in_error'), 'notLoggedIn');
        } else if (!this.checkIfMissionInsidePerimeter()) {
            this.props.showModal(false, I18n.t('mission_message_not_within_perimeter',
                { missionCount: Config.NO_OF_MISSIONS_FOR_PERIMETER_CHECK,
                  distance: Config.RADIUS_IN_M_FOR_MISSION_SOLVING }));
        } else if (this.props.answer !== '' && validationMessage === '') {
            this.props.solveMission(
                this.props.user.id, mission, this.props.answer, this.props.selectedOption, true,
                this.props.stats ? Config.ADDITIONAL_KOINS_FOR_STATS : 0);
        } else {
            this.props.showModal(false, I18n.t('mission_message_valid_answer',
                { validationMessage }), 'validation');
        }
    }

    confirmUnsolvable() {
        this.props.showModal(true, I18n.t('mission_message_unsolvable'), 'unsolvable');
    }

    validateInput() {
        const { re, upperBound, lowerBound } = this.props.activeMission.inputType.constraints;
            if (re) {
                const regex = new RegExp(re);
                return regex.test(this.props.answer);
            } else if (upperBound && lowerBound) {
                return lowerBound <= parseFloat(this.props.answer) &&
                    parseFloat(this.props.answer) <= upperBound;
            }
        return true;
    }

    createListItem(index, item) {
        return { value: index, name: item };
    }

    determineKeyboardType() {
        switch (this.props.freetextType) {
            case 'text':
                return 'default';
            case 'number':
                return 'numeric';
            default:
                return '';
        }
    }

    showNextAchievementOrClose() {
        if (this.props.currentAchievementIndex < this.props.newAchievements.length - 1) {
            this.props.showAchievements(this.props.currentAchievementIndex + 1);
        } else {
            this.props.showAchievements(-1);
            this.hideMission();
        }
    }

    openOSM() {
        const { osmId, osmType } = this.props.activeMission;
        const url = `https://www.openstreetmap.org/${osmType}/${osmId}`;
        Linking.openURL(url);
    }

    renderAnswerSelection() {
        if (this.props.activeMission.error_type === 'opening_hours') {
            return (<OpeningHours
                setAnswer={value => this.props.answerSet(value, '')}
                answer={this.props.answer}
            />);
        }

        if (!_.isEmpty(this.props.activeMission.inputType.options)) {
            const options = _.map(this.props.activeMission.inputType.options,
                (item, index) => { return { value: index, name: item }; });
            const sortedOptions = _.sortBy(options, 'name');
            if (this.props.activeMission.inputType.name !== 'select') {
                sortedOptions.unshift({ value: -1, name: I18n.t('mission_answer_options_other') });
            }
            return <AnswerSelection options={sortedOptions} />;
        }
        return null;
    }

    renderAnswerFreetext() {
        const keyboardType = this.determineKeyboardType();
        if (keyboardType !== '') {
            return (
                <Input
                    placeHolder={I18n.t('mission_answer_placeholder')}
                    keyboardType={keyboardType}
                    value={this.props.answer}
                    onChangeText={value => this.props.answerSet(value, '')}
                />
            );
        }
        return null;
    }

    renderModalHeader() {
        if (this.props.modalType === 'win') {
            return <KortCoin animationStyle='win'>{this.props.activeMission.koinReward}</KortCoin>;
        }
    }

    renderModal() {
                return (
                <Popup
                    onAccept={this.onModalConfirm.bind(this)}
                    visible={this.props.modalVisible}
                    confirm={this.props.modalConfirm}
                    onDecline={this.onModalDecline.bind(this)}
                    message={this.props.modalText}
                >
                     {this.renderModalHeader()}
                </Popup>
            );
    }

    renderAchievementsModal() {
        if (this.props.currentAchievementIndex > -1 &&
            this.validateInput() !== '' &&
            this.checkIfMissionInsidePerimeter()) {
        const currentAchievement = this.props.newAchievements[this.props.currentAchievementIndex];
             return (
                    <AchievementPopup
                        visible
                        onAccept={() => this.showNextAchievementOrClose()}
                        achievement={currentAchievement}
                        animateIndef
                    />
            );
        }
       return null;
    }

    renderSending() {
        return (
            <View>
                <ModalSpinner
                    visible={this.props.sending}
                />
                <Popup
                    visible={this.props.errorMsg !== null}
                    onAccept={() => this.props.hideModal(true)}
                    message={I18n.t('error_message_bad_connectivity')}
                />
            </View>
        );
    }

    render() {
        const { bgColor, missionTextStyle, headerStyle, additionalButtonsStyle,
            containerStyle, unsolvableButtonStyle, completeMissionButtonStyle } = styles;
        return (
            <View style={[bgColor, { height: this.props.missionViewHeight }]}>
                <View style={headerStyle}>
                    <KortCoin animationStyle='normal'>
                         {this.props.activeMission.koinReward}
                    </KortCoin>
                    <Text style={missionTextStyle}>{this.props.activeMission.question}</Text>
                </View>
                <View style={containerStyle} >
                    {this.renderAnswerSelection()}
                    {this.renderAnswerFreetext()}
                        <Button
                            onPress={this.solveMission.bind(this)}
                            style={completeMissionButtonStyle}
                        >Complete Mission
                        </Button>
                        <View style={additionalButtonsStyle}>
                        <ButtonWithImage
                            style={{ borderWidth: 1, paddingRight: 20 }}
                            onPress={() => this.openOSM()}
                            imgSource={'osm'}
                        >OSM
                        </ButtonWithImage>
                        <Button
                            onPress={this.confirmUnsolvable.bind(this)}
                            style={unsolvableButtonStyle}
                        >
                            Unable to solve
                        </Button>
                        </View>
               </View>
               {this.renderModal()}
               {this.renderAchievementsModal()}
               {this.renderSending()}
            </View>
        );
    }
}

const styles = {
    headerStyle: {
        height: 80,
        flexDirection: 'row',
        alignItems: 'center',
        marginLeft: 10,
        paddingRight: 30,
        paddingBottom: 20,
        backgroundColor: 'transparent'

    },
    missionTextStyle: {
        color: 'white',
        marginLeft: 10,
        marginRight: 40,
    },
    containerStyle: {
        flex: 1,
        alignSelf: 'stretch',
        alignItems: 'center',
        backgroundColor: 'transparent',
        justifyContent: 'space-between',
        marginBottom: 40,
    },
    bgColor: {
        backgroundColor: '#395971',
        alignItems: 'center',
        height: 300,
    },
    completeMissionButtonStyle: {
        alignSelf: 'center',
        justifyContent: 'center',
        backgroundColor: '#fff',
        borderRadius: 5,
        borderWidth: 1,
        borderColor: '#657C8E',
        marginLeft: 5,
        marginRight: 5,
        height: 50,
        width: 300,
    },
    unsolvableButtonStyle: {
        alignSelf: 'center',
        width: 150,
    },
    additionalButtonsStyle: {
        flexDirection: 'row',
    }
};

const mapStateToProps = ({ answerReducer, missionReducer,
    authReducer, mapReducer, settingsReducer, navigationReducer }) => {
    const { freetextType, answer, selectedOption, modalVisible,
        modalConfirm, modalText, modalType, sending,
        newAchievements, currentAchievementIndex, errorMsg, solutionSent } = answerReducer;
    const { activeMission, missionViewHeight } = missionReducer;
    const { user } = authReducer;
    const { updateHighscoreView, updateProfileView, updateAchievementsView } = navigationReducer;
    const { centerCoordinates, currentLocation } = mapReducer;
    const { stats } = settingsReducer;
    return { freetextType,
        answer,
        selectedOption,
        modalVisible,
        modalConfirm,
        modalText,
        modalType,
        sending,
        newAchievements,
        currentAchievementIndex,
        errorMsg,
        activeMission,
        missionViewHeight,
        user,
        centerCoordinates,
        currentLocation,
        stats,
        updateHighscoreView,
        updateAchievementsView,
        updateProfileView,
        solutionSent };
};

export default connect(mapStateToProps,
{ answerSet,
    hideModal,
    showModal,
    setFreetextAvailable,
    solveMission,
    showAchievements,
    showMission,
    downloadMissions,
    forceViewUpdateAchievements,
    forceViewUpdateHighscore,
    forceViewUpdateProfile,
    resetSolutionSent })(MissionSolver);
<|endoftext|>


<commit_before>"use strict";

// Settings
var updateInterval = 100;
var maxNumOfElements = 1000;
var errorDisplayTime = 5000;

// Module pattern to store sensor data
// http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
var sensorData = (function() {
    var plot;
    var points = [];
    var lastData;
    var interval = maxNumOfElements * updateInterval;
    var pub = {}; // public object - returned at end of module

    /* Private functions */

    function addData(data) {
        lastData = data;
        points.push([data.timestamp, data.parameter/data.scale])
        while (points.length > maxNumOfElements) {
            points.shift();
        }
    }

    // restict data to plotting interval
    function transformedPoints() {
        var x_max = points[points.length-1][0];
        var res = points.filter(function(elem, idx) {
            // check line segment delimiters
            if (elem === null) {
                return true;
            }
            return elem[0] >= x_max - interval;
        })
        res = res.map(function(elem, idx){
            // check line segment delimiters
            if (elem === null) {
                return null;
            }
            return [interval - (x_max - elem[0]), elem[1]]
        });
        return res;
    }

    function initPlot() {
        points.splice(points.length-1, 0, null) // start a new line segment
        plot = $.plot("#sensor-plot", [points], {
            yaxis: {
                min: pub.getMinimum(),
                max: pub.getMaximum()
            },
            xaxis: {
                show: false,
                min: 0,
                max: interval
            }
        });
    }

    /* Public functions */

    pub.updateSensor = function() {
        var url = './sensors/' + sensorSelector.getCurrSensor();
        return $.getJSON(url).then(addData);
    }

    pub.updatePlot = function() {
        plot.setData([transformedPoints()]);
        plot.draw();
    };

    pub.startNewSensor = function() {
        return sensorData.updateSensor().then(initPlot);
    };

    pub.getSensorValue = function() {
        return points[points.length-1][1];
    };

    pub.setSensorValue = function(form) {
        var url = './sensors/' + sensorSelector.getCurrSensor();
        return $.post(url, {newValue: form.newValue.value*lastData.scale});
    };

    pub.getDefault = function() {
        return lastData.default / lastData.scale
    };

    pub.getMinimum = function() {
        return lastData.minimum / lastData.scale
    };

    pub.getMaximum = function() {
        return lastData.maximum / lastData.scale
    };

    return pub; // expose externally
}());

// Module pattern to store all sensors and the currently selected one
var sensorSelector = (function() {
    var currSensor; // private
    var sensors = []; // private
    var pub = {}; // public object - returned at end of module

    /* Public functions */

    pub.initSensors = function() {
        return $.getJSON('./sensors').then(function(data, status) {
            assert( !isArrayEmpty(data) );
            sensors = data;
            currSensor = data[0];
        });
    };

    pub.getCurrSensor = function() {
        return currSensor;
    };

    pub.setCurrSensor = function(sensor) {
        assert(sensors.indexOf(sensor) > -1);
        currSensor = sensor;
    };

    pub.listSensors = function() {
        return sensors;
    };

    return pub; // expose externally
}());

// Handler for error messages and alerts
var errorHandler = (function() {
    var timeout;
    var pub = {}; // public object - returned at end of module

    /* Private functions */

    function displayAlert(message) {
        ractive.set('error', false);
    }

    /* Public functions */

    pub.handle = function(msg) {
        ractive.set('errorMsg', msg.responseText);
        if (!ractive.get('error')) {
            ractive.set('error', true);
        }
        clearTimeout(timeout);
        timeout = setTimeout(displayAlert, errorDisplayTime);
    };

    return pub; // expose externally
}());

function assert(condition, message) {
    if (!condition) {
        message = message || "Assertion failed";
        if (typeof Error !== "undefined") {
            throw new Error(message);
        }
        throw message; // Fallback
    }
}

function isArrayEmpty(array) {
    // the array is undefined or has no elements
    return typeof array === 'undefined' || array.length == 0;
}

function refreshApp() {
    sensorData.updateSensor().then(sensorData.updatePlot).done(function() {
        ractive.set('sensorValue', sensorData.getSensorValue())
    });

}

// Set up Ractive.js and event proxies
function initRactive() {
    ractive = new Ractive({
        // Where to render the template as node, an ID, or a CSS selector
        el: '#container',
        // ID of a script tag as template defined in index.html
        template: '#template',
        // Initial data for all template parameters
        data: {sensors: sensorSelector.listSensors(),
               currSensor: sensorSelector.getCurrSensor(),
               sensorValue: sensorData.getSensorValue(),
               sensorDefault: sensorData.getDefault(),
               sensorMin: sensorData.getMinimum(),
               sensorMax: sensorData.getMaximum(),
               error: false,
               errorMsg: ''}
    });
    ractive.on('change-sensor', function (event) {
        sensorSelector.setCurrSensor(event.context);
        ractive.set('currSensor', event.context);
        ractive.set('sensorMin', sensorData.getMinimum());
        ractive.set('sensorMax', sensorData.getMaximum());
        sensorData.startNewSensor();
    });
}

// Initialization, starting of ractive.js and refresh interval
var ractive;
$(document).ready(function() {
    $.ajaxSetup({
        "error": errorHandler.handle
    });
    sensorSelector.initSensors().then(
        sensorData.updateSensor).done(function() {
            initRactive();
            sensorData.startNewSensor();
            setInterval(refreshApp, updateInterval);
        })
});
<commit_msg>better usage of promises<commit_after>"use strict";

// Settings
var updateInterval = 100;
var maxNumOfElements = 1000;
var errorDisplayTime = 3000;

// Module pattern to store sensor data
// http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
var sensorData = (function() {
    var plot;
    var points = [];
    var lastData;
    var interval = maxNumOfElements * updateInterval;
    var pub = {}; // public object - returned at end of module

    /* Private functions */

    function addData(data) {
        lastData = data;
        points.push([data.timestamp, data.parameter/data.scale])
        while (points.length > maxNumOfElements) {
            points.shift();
        }
    }

    // restict data to plotting interval
    function transformedPoints() {
        var x_max = points[points.length-1][0];
        var res = points.filter(function(elem, idx) {
            // check line segment delimiters
            if (elem === null) {
                return true;
            }
            return elem[0] >= x_max - interval;
        })
        res = res.map(function(elem, idx){
            // check line segment delimiters
            if (elem === null) {
                return null;
            }
            return [interval - (x_max - elem[0]), elem[1]]
        });
        return res;
    }

    function initPlot() {
        points.splice(points.length-1, 0, null) // start a new line segment
        plot = $.plot("#sensor-plot", [points], {
            yaxis: {
                min: pub.getMinimum(),
                max: pub.getMaximum()
            },
            xaxis: {
                show: false,
                min: 0,
                max: interval
            }
        });
    }

    /* Public functions */

    pub.updateSensor = function() {
        var url = './sensors/' + sensorSelector.getCurrSensor();
        return $.getJSON(url).then(addData);
    }

    pub.updatePlot = function() {
        plot.setData([transformedPoints()]);
        plot.draw();
    };

    pub.startNewSensor = function() {
        return sensorData.updateSensor().then(initPlot);
    };

    pub.getSensorValue = function() {
        return points[points.length-1][1];
    };

    pub.setSensorValue = function(form) {
        var url = './sensors/' + sensorSelector.getCurrSensor();
        return $.post(url, {newValue: form.newValue.value*lastData.scale});
    };

    pub.getDefault = function() {
        return lastData.default / lastData.scale
    };

    pub.getMinimum = function() {
        return lastData.minimum / lastData.scale
    };

    pub.getMaximum = function() {
        return lastData.maximum / lastData.scale
    };

    return pub; // expose externally
}());

// Module pattern to store all sensors and the currently selected one
var sensorSelector = (function() {
    var currSensor; // private
    var sensors = []; // private
    var pub = {}; // public object - returned at end of module

    /* Public functions */

    pub.initSensors = function() {
        return $.getJSON('./sensors').then(function(data, status) {
            assert( !isArrayEmpty(data) );
            sensors = data;
            currSensor = data[0];
        });
    };

    pub.getCurrSensor = function() {
        return currSensor;
    };

    pub.setCurrSensor = function(sensor) {
        assert(sensors.indexOf(sensor) > -1);
        currSensor = sensor;
    };

    pub.listSensors = function() {
        return sensors;
    };

    return pub; // expose externally
}());

// Handler for error messages and alerts
var errorHandler = (function() {
    var timeout;
    var pub = {}; // public object - returned at end of module

    /* Private functions */

    function displayAlert(message) {
        return ractive.set('error', false);
    }

    /* Public functions */

    pub.handle = function(msg) {
        ractive.set('errorMsg', msg.responseText).then(function() {
            ractive.set('error', true);
            clearTimeout(timeout);
            timeout = setTimeout(displayAlert, errorDisplayTime);
        });
    };

    return pub; // expose externally
}());

function assert(condition, message) {
    if (!condition) {
        message = message || "Assertion failed";
        if (typeof Error !== "undefined") {
            throw new Error(message);
        }
        throw message; // Fallback
    }
}

function isArrayEmpty(array) {
    // the array is undefined or has no elements
    return typeof array === 'undefined' || array.length == 0;
}

function refreshApp() {
    sensorData.updateSensor().then(sensorData.updatePlot).done(function() {
        ractive.set('sensorValue', sensorData.getSensorValue());
    });

}

// Set up Ractive.js and event proxies
function initRactive() {
    ractive = new Ractive({
        // Where to render the template as node, an ID, or a CSS selector
        el: '#container',
        // ID of a script tag as template defined in index.html
        template: '#template',
        // Initial data for all template parameters
        data: {sensors: sensorSelector.listSensors(),
               currSensor: sensorSelector.getCurrSensor(),
               sensorValue: sensorData.getSensorValue(),
               sensorDefault: sensorData.getDefault(),
               sensorMin: sensorData.getMinimum(),
               sensorMax: sensorData.getMaximum(),
               error: false,
               errorMsg: ''}
    });
    ractive.on('change-sensor', function (event) {
        sensorSelector.setCurrSensor(event.context);
        ractive.set({currSensor: event.context,
                     sensorMin: sensorData.getMinimum(),
                     sensorMax: sensorData.getMaximum()}).then(
                     sensorData.startNewSensor);
    });
}

// Initialization, starting of ractive.js and refresh interval
var ractive;
$(document).ready(function() {
    $.ajaxSetup({
        "error": errorHandler.handle
    });
    sensorSelector.initSensors().then(
        sensorData.updateSensor).done(function() {
            initRactive();
            sensorData.startNewSensor();
            setInterval(refreshApp, updateInterval);
        })
});
<|endoftext|>