Source: map/importer/ui/CatalogUi.js

import { Pagination } from "../../../search/Pagination.js";
import { UiElement } from "../../../ui/UiElement.js";
import { registerDataTransfer } from "../../../ui/dnd/util.js";
import { IndexBinder } from "../binder/IndexBinder.js";
import { createVEFLayerConfig } from "../../../utils/universal/ogc/createVEFLayerConfig.js";
import "./CatalogUi.css";
import { loadLayer } from "../../utils/loadLayer.js";

export { CatalogUi };

/**
 * Ui class for importing NRT layers from DWS
 * 
 * @memberof vef.map.importer.ui
 */
class CatalogUi extends UiElement {

    title = "Catalog";
    classes = ["catalog-ui", "importer-ui"];
    html = `
        <div class="catalog-input-wrapper">
            <input class="catalog-input" type="text" spellcheck="false" placeholder="Search for layers..."></input>
            <button class="catalog-search-button" title="search"><i class="fas fa-search"></i></button>
            <button title="filter" class="btn-filter"><i class="fas fa-filter"></i></button>
        </div>
        <div class="catalog-filters">
            <div class="catalog-filter">
                <h4>Layer Type</h4>
                <label>Show WMS</label> <input class="show-wms" type="checkbox" checked/> -
                <label>Show WFS</label> <input class="show-wfs" type="checkbox"/>
            </div>
        </div>
        <div class="catalog-results"></div>
        <div class="results-pagination"></div>
    `;

    constructor(target, options) {
        super(target, {
            "add_layers": [],
            "show_info": []
        });

        this.page_ = 0;
        this.limit_ = 6;
        this.pagination_ = null;
        this.url_ = (options && ("url" in options)) ? options.url : null;
        this.indices_ = (options && ("indices" in options)) ? options.indices : null;
        this.indexBinder_ = new IndexBinder(this.url_, this.indices_);

        this.setHtml(this.html);
        this.setClass(this.classes);

        this.initPagination_();
        this.initEvents_();
        this.applySearch_();
    }

    initPagination_() {
        this.pagination_ = new Pagination(this.query(".results-pagination"));

        this.pagination_.on("changed", page => {
            this.applySearch_(page);
        })
    }

    initEvents_() {
        let searchTimeout = null;
        this.query(".catalog-search-button").addEventListener("click", () => this.applySearch_());
        this.query(".catalog-input").addEventListener("keydown", e => {
            if (e.which == 13) { // "enter" key
                clearTimeout(searchTimeout);
                searchTimeout = null;
                this.applySearch_();
            } else {
                if (searchTimeout) clearTimeout(searchTimeout);
                searchTimeout = setTimeout(() => {
                    searchTimeout = null;
                    this.applySearch_();
                }, 1000);
            }
        });

        this.query(".btn-filter").addEventListener("click", e => {
            const element = this.getElement();
            if (element.classList.contains("filters-active")) {
                element.classList.remove("filters-active");
                this.applySearch_();
            } else {
                element.classList.add("filters-active");
            }
        });
    }

    async showResults_(response) {
        const results = this.query(".catalog-results");
        results.style.height = "auto";

        if (response.hits == 0) {
            results.innerHTML = "<b style='color: var(--primary-color);'>No layers found.</b>";
            return;
        }

        results.innerHTML = "";

        for (let i = 0; i < response.records.length; ++i) {
            const record = response.records[i];
            const cache = await createVEFLayerConfig(record);
            if (!cache) return;

            const layer = loadLayer({}, cache);

            const result = document.createElement("div");
            result.classList.add("result");
            result.innerHTML = `
                <div class="result-drag">
                    <i class="fas fa-arrows-alt"></i>
                </div>
                <div class="result-content">
                    <a href="#" class="layer-title"></a>
                    <div class="layer-type"></div>
                    <div class="layer-abstract"></div>
                    <a class="service-url" target="_blank"></a>
                    <a title="layer info" href="#" class="info-button fas fa-info"></a>
                </div>
            `;

            const title = result.querySelector(".layer-title");
            title.innerText = record.title;
            title.title = record.title;
            title.addEventListener("click", e => {
                e.preventDefault();
                const layers = {};
                layers[layer.uniqueId] = layer;
                this.fire("add_layers", {
                    structure: { "#": [layer.uniqueId] },
                    layers: layers
                });
            });

            const btnInfo = result.querySelector(".info-button");
            btnInfo.addEventListener("click", e => {
                e.preventDefault();
                this.fire("show_info", layer);
            });

            // remove html from abstract
            const abstractDiv = document.createElement("div");
            abstractDiv.innerHTML = record.description;

            const abstract = result.querySelector(".layer-abstract");
            abstract.innerText = abstractDiv.innerText.replace(/(\r\n|\n|\r)/gm, " ");
            abstract.title = abstractDiv.innerText;

            const type = result.querySelector(".layer-type");
            switch (layer.type.toLowerCase()) {
                case "wms":
                    type.innerHTML = "<i class='vef vef-raster'></i> WMS"
                    break;
                case "wfs":
                    type.innerHTML = "<i class='fas fa-draw-polygon'></i> WFS"
                    break;
            }

            // find url
            for (let i = 0; i < record.resources.length; ++i) {
                const resource = record.resources[i];
                if (resource.type == "link") {
                    const a = result.querySelector(".service-url");
                    a.href = resource.uri;
                    a.title = resource.uri;
                    a.innerText = resource.uri;
                    break;
                }
            }

            this.initDragDrop_(result, layer);
            results.appendChild(result);
        }
    }

    initDragDrop_(result, layer) {
        result.draggable = true;
        result.addEventListener('dragstart', (e) => {
            e.stopPropagation();
            e.dataTransfer.setData("layer_transfer_id", registerDataTransfer(layer));
        });
    }

    emptyResults_() {
        this.pagination_.show(0, 0);
        const results = this.query(".catalog-results");
        results.style.height = results.clientHeight + "px";
        results.innerHTML = `
            <div style="text-align: center; margin: 20px 0">
                <i style="color:var(--primary-color); font-size: 18px;" class="fas fa-spinner fa-spin fa-fw"></i>'
            </div>
        `;
    }

    request_(params) {
        let query = "";

        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4 && xhr.status == 200) {
                const response = JSON.parse(xhr.responseText);

                const page = Math.ceil(response.offset / this.limit_) + 1;
                const pages = Math.ceil(response.totalHits / this.limit_);

                this.pagination_.show(page, pages);
                this.showResults_(response);
            }
        };
        xhr.open("GET", query, true);
        xhr.setRequestHeader('Accept', 'application/json;charset=UTF-8');
        xhr.send();
    }

    applySearch_(page) {
        this.emptyResults_();
        this.page_ = (Number.isFinite(page) && (page >= 0)) ? page : 1;

        const params = {
            hits: this.limit_,
            offset: Math.floor((this.page_ - 1) * this.limit_),
            queryFacets: {},
            operator: "AND",
            query: ""
        }

        let query = this.query(".catalog-input").value.trim();

        if (query.length > 0) {
            if (!(query.includes('"') || query.includes("'"))) query = "*" + query + "*";
            params.query = `(title:(${query})^3 OR (${query}))`;
        } else {
            params.sorts = ["_random"]
        }

        const showWms = this.query(".show-wms").checked;
        const showWfs = this.query(".show-wfs").checked;
        let typeQuery = "";
        if (showWms) typeQuery += '"wms"';
        if (showWfs) {
            if (typeQuery.length > 0) typeQuery += " OR ";
            typeQuery += '"wfs"';
        }
        if (typeQuery) {
            params.query += ` resources.metadata.type:(${typeQuery})`;
        }

        this.indexBinder_.fetch(params)
            .then(response => {
                const page = Math.ceil(response.offset / this.limit_) + 1;
                const pages = Math.ceil(response.totalHits / this.limit_);
                this.showResults_(response);
                this.pagination_.show(page, pages);
            })
            .catch(e => {
                console.error(e);
            })
    }

}