Project

General

Profile

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

haketilo / html / repo_query.js @ 9bee4afa

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
#FROM common/jsonschema.js IMPORT haketilo_validator, haketilo_schemas
51

    
52
#FROM html/repo_query_cacher_client.js IMPORT indirect_fetch
53

    
54
const coll = new Intl.Collator();
55

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

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

    
63
    const iv = repo_entry.query_view.install_view;
64

    
65
    function start_install() {
66
	iv.show(repo_entry.repo_url, "mapping",
67
		mapping_ref.identifier, mapping_ref.version);
68
    }
69

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

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

    
79
    this.repo_url_label.innerText = repo_url;
80

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

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

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

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

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

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

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

    
116
	return json.mappings;
117
    }
118

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

    
122
	try {
123
	    var results = await query_results();
124
	} catch(e) {
125
	    this.info_div.innerText = e;
126
	    return;
127
	}
128

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

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

    
135
	    const to_append = this.result_entries.map(re => re.main_li);
136
	    this.results_list.append(...to_append);
137
	} else {
138
	    this.info_div.innerText = "No results :(";
139
	}
140
    }
141

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

    
146
	if (!this.results_shown_before)
147
	    populate_results();
148

    
149
	this.list_container.classList.remove("hide");
150
	this.hide_results_but.classList.remove("hide");
151
	this.show_results_but.classList.add("hide");
152
    }
153
    show_results = query_view.install_view.when_hidden(show_results);
154

    
155
    let hide_results = () => {
156
	if (!query_view.shown)
157
	    return;
158

    
159
	this.list_container.classList.add("hide");
160
	this.hide_results_but.classList.add("hide");
161
	this.show_results_but.classList.remove("hide");
162
    }
163
    hide_results = query_view.install_view.when_hidden(hide_results);
164

    
165
    this.show_results_but.addEventListener("click", show_results);
166
    this.hide_results_but.addEventListener("click", hide_results);
167
}
168

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

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

    
174
    Object.assign(this, clone_template("repo_query"));
175
    this.tab_id = tab.id;
176
#IF MOZILLA
177
    this.incognito = tab.incognito;
178
    if (this.incognito) {
179
	[...this.top_text.childNodes].forEach(n => n.remove());
180
	this.top_text.append(
181
	    clone_template("repo_query_private_mode_error").main_span
182
	);
183
    }
184
#ENDIF
185

    
186
    const show_container = name => {
187
	for (const cid of container_ids) {
188
	    if (cid !== name)
189
		this[cid].classList.add("hide");
190
	}
191
	this[name].classList.remove("hide");
192
    }
193

    
194
    this.install_view = new InstallView(
195
	this.tab_id,
196
	() => show_container("install_view_container"),
197
	() => show_container("repos_list_container")
198
    );
199
    this.install_view_container.prepend(this.install_view.main_div);
200

    
201
    const show_super = this.show;
202
    this.show = async url => {
203
	if (!show_super())
204
	    return;
205

    
206
#IF MOZILLA
207
	if (this.incognito) {
208
	    this.repo_entries = [];
209
	    return;
210
	}
211
#ENDIF
212

    
213
	this.url = url;
214
	this.url_span.innerText = url;
215

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

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

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

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

    
232
    this.cancel_but.addEventListener("click",
233
				     this.install_view.when_hidden(this.hide));
234
}
235
#EXPORT RepoQueryView
(21-21/28)