Project

General

Profile

Download (6.77 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / html / repo_query.js @ 72553a2d

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

    
44
#IMPORT common/indexeddb.js AS haketilodb
45

    
46
#FROM common/browser.js   IMPORT browser
47
#FROM html/DOM_helpers.js IMPORT clone_template, Showable
48
#FROM common/entities.js  IMPORT item_id_string, version_string
49
#FROM html/install.js     IMPORT InstallView
50

    
51
const coll = new Intl.Collator();
52

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

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

    
60
    const iv = repo_entry.query_view.install_view;
61

    
62
    function start_install() {
63
	iv.show(repo_entry.repo_url, "mapping",
64
		mapping_ref.identifier, mapping_ref.version);
65
    }
66

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

    
71
const query_schema_url_regex = new RegExp(
72
    "^https://hydrilla\\.koszko\\.org/schemas/api_query_result-1\\.([1-9][0-9]*\\.)*schema\\.json$"
73
);
74

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

    
80
    this.repo_url_label.innerText = repo_url;
81

    
82
    const query_results = async () => {
83
	const msg = [
84
	    "repo_query",
85
	    `${repo_url}query?url=${encodeURIComponent(query_view.url)}`
86
	];
87
	const response = await browser.tabs.sendMessage(query_view.tab_id, msg);
88

    
89
	if ("error" in response)
90
	    throw "Failure to communicate with repository :(";
91

    
92
	if (!response.ok)
93
	    throw `Repository sent HTTP code ${response.status} :(`;
94
	if ("error_json" in response)
95
	    throw "Repository's response is not valid JSON :(";
96

    
97
	if (!response.json["$schema"])
98
	    throw "Results were served using a nonconforming response format.";
99
	if (!query_schema_url_regex.test(response.json["$schema"]))
100
	    throw "Results were served using unsupported Hydrilla API version. You might need to update Haketilo.";
101

    
102
	/* TODO: here we should perform JSON schema validation! */
103

    
104
	return response.json.mappings;
105
    }
106

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

    
110
	try {
111
	    var results = await query_results();
112
	} catch(e) {
113
	    this.info_div.innerText = e;
114
	    return;
115
	}
116

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

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

    
123
	    const to_append = this.result_entries.map(re => re.main_li);
124
	    this.results_list.append(...to_append);
125
	} else {
126
	    this.info_div.innerText = "No results :(";
127
	}
128
    }
129

    
130
    let show_results = () => {
131
	if (!query_view.shown)
132
	    return;
133

    
134
	if (!this.results_shown_before)
135
	    populate_results();
136

    
137
	this.list_container.classList.remove("hide");
138
	this.hide_results_but.classList.remove("hide");
139
	this.show_results_but.classList.add("hide");
140
    }
141
    show_results = query_view.install_view.when_hidden(show_results);
142

    
143
    let hide_results = () => {
144
	if (!query_view.shown)
145
	    return;
146

    
147
	this.list_container.classList.add("hide");
148
	this.hide_results_but.classList.add("hide");
149
	this.show_results_but.classList.remove("hide");
150
    }
151
    hide_results = query_view.install_view.when_hidden(hide_results);
152

    
153
    this.show_results_but.addEventListener("click", show_results);
154
    this.hide_results_but.addEventListener("click", hide_results);
155
}
156

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

    
159
function RepoQueryView(tab_id, on_view_show, on_view_hide) {
160
    Showable.call(this, on_view_show, on_view_hide);
161

    
162
    Object.assign(this, clone_template("repo_query"));
163
    this.tab_id = tab_id;
164

    
165
    const show_container = name => {
166
	for (const cid of container_ids) {
167
	    if (cid !== name)
168
		this[cid].classList.add("hide");
169
	}
170
	this[name].classList.remove("hide");
171
    }
172

    
173
    this.install_view = new InstallView(
174
	tab_id,
175
	() => show_container("install_view_container"),
176
	() => show_container("repos_list_container")
177
    );
178
    this.install_view_container.prepend(this.install_view.main_div);
179

    
180
    const show_super = this.show;
181
    this.show = async url => {
182
	if (!show_super())
183
	    return;
184

    
185
	this.url = url;
186
	this.url_span.innerText = url;
187

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

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

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

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

    
204
    this.cancel_but.addEventListener("click",
205
				     this.install_view.when_hidden(this.hide));
206
}
207
#EXPORT RepoQueryView
(21-21/27)