aboutsummaryrefslogtreecommitdiff
/**
 * This file is part of Haketilo.
 *
 * Function: Show available repositories and allow querying them for resources.
 *
 * Copyright (C) 2022 Wojtek Kosior
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute forms of that code without the copy of the GNU
 * GPL normally required by section 4, provided you include this
 * license notice and, in case of non-source distribution, a URL
 * through which recipients can access the Corresponding Source.
 * If you modify file(s) with this exception, you may extend this
 * exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * As a special exception to the GPL, any HTML file which merely
 * makes function calls to this code, and for that purpose
 * includes it by reference shall be deemed a separate work for
 * copyright law purposes. If you modify this code, you may extend
 * this exception to your version of the code, but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
 * license. Although I request that you do not make use of this code in a
 * proprietary program, I am not going to enforce this in court.
 */

#IMPORT common/indexeddb.js AS haketilodb

#FROM common/browser.js    IMPORT browser
#FROM html/DOM_helpers.js  IMPORT clone_template, Showable
#FROM common/entities.js   IMPORT item_id_string, version_string
#FROM html/install.js      IMPORT InstallView
#FROM common/jsonschema.js IMPORT haketilo_validator, haketilo_schemas

#FROM html/repo_query_cacher_client.js IMPORT indirect_fetch

const coll = new Intl.Collator();

function ResultEntry(repo_entry, mapping_ref) {
    Object.assign(this, clone_template("repo_query_single_result"));
    Object.assign(this, {repo_entry, mapping_ref});

    this.mapping_name.innerText = mapping_ref.long_name;
    this.mapping_id.innerText = item_id_string(mapping_ref);

    const iv = repo_entry.query_view.install_view;

    function start_install() {
	iv.show(repo_entry.repo_url, "mapping",
		mapping_ref.identifier, mapping_ref.version);
    }

    const cb = repo_entry.query_view.install_view.when_hidden(start_install);
    this.install_but.addEventListener("click", cb);
}

function RepoEntry(query_view, repo_url) {
    Object.assign(this, clone_template("repo_query_single_repo"));
    Object.assign(this, {query_view, repo_url});
    this.results_shown_before = false;

    this.repo_url_label.innerText = repo_url;

    const encoded_queried_url = encodeURIComponent(query_view.url);
    const query_url = `${repo_url}query?url=${encoded_queried_url}`;

    const query_results = async () => {
	try {
	    var response = await indirect_fetch(query_view.tab_id, query_url);
	} catch(e) {
	    console.error("Haketilo:", e);
	    throw "Failure to communicate with repository :(";
	}

	if (!response.ok)
	    throw `Repository sent HTTP code ${response.status} :(`;

	try {
	    var json = await response.json();
	} catch(e) {
	    console.error("Haketilo:", e);
	    throw "Repository's response is not valid JSON :(";
	}

	const $id =
	      `https://hydrilla.koszko.org/schemas/api_query_result-1.0.1.schema.json`;
	const schema = haketilo_schemas[$id];
	const result = haketilo_validator.validate(json, schema);
	if (result.errors.length > 0) {
	    console.error("Haketilo:", result.errors);

	    const reg = new RegExp(schema.properties.$schema.pattern);
	    if (json.$schema && !reg.test(json.$schema))
		throw "Results were served using unsupported Hydrilla API version. You might need to update Haketilo.";

	    throw "Results were served using a nonconforming response format.";
	}

	return json.mappings;
    }

    const populate_results = async () => {
	this.results_shown_before = true;

	try {
	    var results = await query_results();
	} catch(e) {
	    this.info_div.innerText = e;
	    return;
	}

	this.result_entries = results.map(ref => new ResultEntry(this, ref));

	if (this.result_entries.length > 0) {
	    this.results_list.classList.remove("hide");
	    this.info_div.remove();

	    const to_append = this.result_entries.map(re => re.main_li);
	    this.results_list.append(...to_append);
	} else {
	    this.info_div.innerText = "No results :(";
	}
    }

    let show_results = () => {
	if (!query_view.shown)
	    return;

	if (!this.results_shown_before)
	    populate_results();

	this.list_container.classList.remove("hide");
	this.hide_results_but.classList.remove("hide");
	this.show_results_but.classList.add("hide");
    }
    show_results = query_view.install_view.when_hidden(show_results);

    let hide_results = () => {
	if (!query_view.shown)
	    return;

	this.list_container.classList.add("hide");
	this.hide_results_but.classList.add("hide");
	this.show_results_but.classList.remove("hide");
    }
    hide_results = query_view.install_view.when_hidden(hide_results);

    this.show_results_but.addEventListener("click", show_results);
    this.hide_results_but.addEventListener("click", hide_results);
}

const container_ids = ["repos_list_container", "install_view_container"];

function RepoQueryView(tab, on_view_show, on_view_hide) {
    Showable.call(this, on_view_show, on_view_hide);

    Object.assign(this, clone_template("repo_query"));
    this.tab_id = tab.id;
#IF MOZILLA
    this.incognito = tab.incognito;
    if (this.incognito) {
	[...this.top_text.childNodes].forEach(n => n.remove());
	this.top_text.append(
	    clone_template("repo_query_private_mode_error").main_span
	);
    }
#ENDIF

    const show_container = name => {
	for (const cid of container_ids) {
	    if (cid !== name)
		this[cid].classList.add("hide");
	}
	this[name].classList.remove("hide");
    }

    this.install_view = new InstallView(
	this.tab_id,
	() => show_container("install_view_container"),
	() => show_container("repos_list_container")
    );
    this.install_view_container.prepend(this.install_view.main_div);

    const show_super = this.show;
    this.show = async url => {
	if (!show_super())
	    return;

#IF MOZILLA
	if (this.incognito) {
	    this.repo_entries = [];
	    return;
	}
#ENDIF

	this.url = url;
	this.url_span.innerText = url;

	[...this.repos_list.children].forEach(c => c.remove());

	const repo_urls = await haketilodb.get_repos();
	repo_urls.sort((a, b) => coll.compare(a, b));
	this.repo_entries = repo_urls.map(ru => new RepoEntry(this, ru));

	if (repo_urls.length === 0) {
	    const info_li = document.createElement("li");
	    info_li.innerText = "You have no repositories configured :(";
	    this.repos_list.append(info_li);
	    return;
	}

	this.repos_list.append(...this.repo_entries.map(re => re.main_li));
    }

    this.cancel_but.addEventListener("click",
				     this.install_view.when_hidden(this.hide));
}
#EXPORT RepoQueryView