Project

General

Profile

« Previous | Next » 

Revision 261548ff

Added by koszko about 2 years ago

emply an sh-based build system; make some changes to blocking

View differences:

html/options_main.js
5 5
 * Redistribution terms are gathered in the `copyright' file.
6 6
 */
7 7

  
8
"use strict";
9

  
10
(() => {
11
    const get_storage = window.get_storage;
12
    const TYPE_PREFIX = window.TYPE_PREFIX;
13
    const TYPE_NAME = window.TYPE_NAME;
14
    const list_prefixes = window.list_prefixes;
15

  
16
    var storage;
17
    function by_id(id)
18
    {
19
	return document.getElementById(id);
20
    }
21

  
22
    function nice_name(prefix, name)
23
    {
24
	return `${name} (${TYPE_NAME[prefix]})`;
25
    }
26

  
27
    const item_li_template = by_id("item_li_template");
28
    const bag_component_li_template = by_id("bag_component_li_template");
29
    const chbx_component_li_template = by_id("chbx_component_li_template");
30
    const radio_component_li_template = by_id("radio_component_li_template");
31
    const import_li_template = by_id("import_li_template");
32
    /* Make sure they are later cloned without id. */
33
    item_li_template.removeAttribute("id");
34
    bag_component_li_template.removeAttribute("id");
35
    chbx_component_li_template.removeAttribute("id");
36
    radio_component_li_template.removeAttribute("id");
37
    import_li_template.removeAttribute("id");
38

  
39
    function item_li_id(prefix, item)
40
    {
41
	return `li_${prefix}_${item}`;
42
    }
43

  
44
    /* Insert into list of bags/pages/scripts */
45
    function add_li(prefix, item, at_the_end=false)
46
    {
47
	let ul = ul_by_prefix[prefix];
48
	let li = item_li_template.cloneNode(true);
49
	li.id = item_li_id(prefix, item);
50

  
51
	let span = li.firstElementChild;
52
	span.textContent = item;
53

  
54
	let edit_button = span.nextElementSibling;
55
	edit_button.addEventListener("click", () => edit_item(prefix, item));
56

  
57
	let remove_button = edit_button.nextElementSibling;
58
	remove_button.addEventListener("click",
59
				       () => storage.remove(prefix, item));
60

  
61
	let export_button = remove_button.nextElementSibling;
62
	export_button.addEventListener("click",
63
				       () => export_item(prefix, item));
8
/*
9
 * IMPORTS_START
10
 * IMPORT get_remote_storage
11
 * IMPORT TYPE_PREFIX
12
 * IMPORT TYPE_NAME
13
 * IMPORT list_prefixes
14
 * IMPORTS_END
15
 */
64 16

  
65
	if (!at_the_end) {
66
	    for (let element of ul.ul.children) {
67
		if (element.id < li.id || element.id.startsWith("work_"))
68
		    continue;
17
var storage;
18
function by_id(id)
19
{
20
    return document.getElementById(id);
21
}
22

  
23
function nice_name(prefix, name)
24
{
25
    return `${name} (${TYPE_NAME[prefix]})`;
26
}
27

  
28
const item_li_template = by_id("item_li_template");
29
const bag_component_li_template = by_id("bag_component_li_template");
30
const chbx_component_li_template = by_id("chbx_component_li_template");
31
const radio_component_li_template = by_id("radio_component_li_template");
32
const import_li_template = by_id("import_li_template");
33
/* Make sure they are later cloned without id. */
34
item_li_template.removeAttribute("id");
35
bag_component_li_template.removeAttribute("id");
36
chbx_component_li_template.removeAttribute("id");
37
radio_component_li_template.removeAttribute("id");
38
import_li_template.removeAttribute("id");
39

  
40
function item_li_id(prefix, item)
41
{
42
    return `li_${prefix}_${item}`;
43
}
44

  
45
/* Insert into list of bags/pages/scripts */
46
function add_li(prefix, item, at_the_end=false)
47
{
48
    let ul = ul_by_prefix[prefix];
49
    let li = item_li_template.cloneNode(true);
50
    li.id = item_li_id(prefix, item);
51

  
52
    let span = li.firstElementChild;
53
    span.textContent = item;
54

  
55
    let edit_button = span.nextElementSibling;
56
    edit_button.addEventListener("click", () => edit_item(prefix, item));
57

  
58
    let remove_button = edit_button.nextElementSibling;
59
    remove_button.addEventListener("click",
60
				   () => storage.remove(prefix, item));
61

  
62
    let export_button = remove_button.nextElementSibling;
63
    export_button.addEventListener("click",
64
				   () => export_item(prefix, item));
65

  
66
    if (!at_the_end) {
67
	for (let element of ul.ul.children) {
68
	    if (element.id < li.id || element.id.startsWith("work_"))
69
		continue;
69 70

  
70
		ul.ul.insertBefore(li, element);
71
		return;
72
	    }
71
	    ul.ul.insertBefore(li, element);
72
	    return;
73 73
	}
74

  
75
	ul.ul.appendChild(li);
76 74
    }
77 75

  
78
    const chbx_components_ul = by_id("chbx_components_ul");
79
    const radio_components_ul = by_id("radio_components_ul");
76
    ul.ul.appendChild(li);
77
}
80 78

  
81
    function chbx_li_id(prefix, item)
82
    {
83
	return `cli_${prefix}_${item}`;
84
    }
79
const chbx_components_ul = by_id("chbx_components_ul");
80
const radio_components_ul = by_id("radio_components_ul");
85 81

  
86
    function radio_li_id(prefix, item)
87
    {
88
	return `rli_${prefix}_${item}`;
89
    }
82
function chbx_li_id(prefix, item)
83
{
84
    return `cli_${prefix}_${item}`;
85
}
90 86

  
91
    //TODO: refactor the 2 functions below
87
function radio_li_id(prefix, item)
88
{
89
    return `rli_${prefix}_${item}`;
90
}
92 91

  
93
    function add_chbx_li(prefix, name)
94
    {
95
	if (prefix === TYPE_PREFIX.PAGE)
96
	    return;
92
//TODO: refactor the 2 functions below
97 93

  
98
	let li = chbx_component_li_template.cloneNode(true);
99
	li.id = chbx_li_id(prefix, name);
100
	li.setAttribute("data-prefix", prefix);
101
	li.setAttribute("data-name", name);
102

  
103
	let chbx = li.firstElementChild;
104
	let span = chbx.nextElementSibling;
105

  
106
	span.textContent = nice_name(prefix, name);
107

  
108
	chbx_components_ul.appendChild(li);
109
    }
110

  
111
    var radio_component_none_li = by_id("radio_component_none_li");
94
function add_chbx_li(prefix, name)
95
{
96
    if (prefix === TYPE_PREFIX.PAGE)
97
	return;
112 98

  
113
    function add_radio_li(prefix, name)
114
    {
115
	if (prefix === TYPE_PREFIX.PAGE)
116
	    return;
99
    let li = chbx_component_li_template.cloneNode(true);
100
    li.id = chbx_li_id(prefix, name);
101
    li.setAttribute("data-prefix", prefix);
102
    li.setAttribute("data-name", name);
117 103

  
118
	let li = radio_component_li_template.cloneNode(true);
119
	li.id = radio_li_id(prefix, name);
120
	li.setAttribute("data-prefix", prefix);
121
	li.setAttribute("data-name", name);
104
    let chbx = li.firstElementChild;
105
    let span = chbx.nextElementSibling;
122 106

  
123
	let radio = li.firstElementChild;
124
	let span = radio.nextElementSibling;
107
    span.textContent = nice_name(prefix, name);
125 108

  
126
	span.textContent = nice_name(prefix, name);
109
    chbx_components_ul.appendChild(li);
110
}
127 111

  
128
	radio_components_ul.insertBefore(li, radio_component_none_li);
129
    }
130

  
131
    const page_payload_span = by_id("page_payload");
132

  
133
    function set_page_components(components)
134
    {
135
	if (components === undefined) {
136
	    page_payload_span.setAttribute("data-payload", "no");
137
	    page_payload_span.textContent = "(None)";
138
	} else {
139
	    page_payload_span.setAttribute("data-payload", "yes");
140
	    let [prefix, name] = components;
141
	    page_payload_span.setAttribute("data-prefix", prefix);
142
	    page_payload_span.setAttribute("data-name", name);
143
	    page_payload_span.textContent = nice_name(prefix, name);
144
	}
145
    }
112
var radio_component_none_li = by_id("radio_component_none_li");
146 113

  
147
    const page_allow_chbx = by_id("page_allow_chbx");
114
function add_radio_li(prefix, name)
115
{
116
    if (prefix === TYPE_PREFIX.PAGE)
117
	return;
148 118

  
149
    /* Used to reset edited page. */
150
    function reset_work_page_li(ul, item, settings)
151
    {
152
	ul.work_name_input.value = maybe_string(item);
153
	settings = settings || {allow: false, components: undefined};
154
	page_allow_chbx.checked = !!settings.allow;
119
    let li = radio_component_li_template.cloneNode(true);
120
    li.id = radio_li_id(prefix, name);
121
    li.setAttribute("data-prefix", prefix);
122
    li.setAttribute("data-name", name);
155 123

  
156
	set_page_components(settings.components);
157
    }
124
    let radio = li.firstElementChild;
125
    let span = radio.nextElementSibling;
158 126

  
159
    function work_page_li_components()
160
    {
161
	if (page_payload_span.getAttribute("data-payload") === "no")
162
	    return undefined;
127
    span.textContent = nice_name(prefix, name);
163 128

  
164
	let prefix = page_payload_span.getAttribute("data-prefix");
165
	let name = page_payload_span.getAttribute("data-name");
166
	return [prefix, name];
167
    }
129
    radio_components_ul.insertBefore(li, radio_component_none_li);
130
}
168 131

  
169
    /* Used to get edited page data for saving. */
170
    function work_page_li_data(ul)
171
    {
172
	let url = ul.work_name_input.value;
173
	let settings = {
174
	    components : work_page_li_components(),
175
	    allow : !!page_allow_chbx.checked
176
	};
132
const page_payload_span = by_id("page_payload");
177 133

  
178
	return [url, settings];
134
function set_page_components(components)
135
{
136
    if (components === undefined) {
137
	page_payload_span.setAttribute("data-payload", "no");
138
	page_payload_span.textContent = "(None)";
139
    } else {
140
	page_payload_span.setAttribute("data-payload", "yes");
141
	let [prefix, name] = components;
142
	page_payload_span.setAttribute("data-prefix", prefix);
143
	page_payload_span.setAttribute("data-name", name);
144
	page_payload_span.textContent = nice_name(prefix, name);
179 145
    }
146
}
180 147

  
181
    const empty_bag_component_li = by_id("empty_bag_component_li");
182
    var bag_components_ul = by_id("bag_components_ul");
183

  
184
    /* Used to construct and update components list of edited bag. */
185
    function add_bag_components(components)
186
    {
187
	for (let component of components) {
188
	    let [prefix, name] = component;
189
	    let li = bag_component_li_template.cloneNode(true);
190
	    li.setAttribute("data-prefix", prefix);
191
	    li.setAttribute("data-name", name);
192
	    let span = li.firstElementChild;
193
	    span.textContent = nice_name(prefix, name);
194
	    let remove_but = span.nextElementSibling;
195
	    remove_but.addEventListener("click", () =>
196
					bag_components_ul.removeChild(li));
197
	    bag_components_ul.appendChild(li);
198
	}
199

  
200
	bag_components_ul.appendChild(empty_bag_component_li);
201
    }
148
const page_allow_chbx = by_id("page_allow_chbx");
202 149

  
203
    /* Used to reset edited bag. */
204
    function reset_work_bag_li(ul, item, components)
205
    {
206
	components = components || [];
150
/* Used to reset edited page. */
151
function reset_work_page_li(ul, item, settings)
152
{
153
    ul.work_name_input.value = maybe_string(item);
154
    settings = settings || {allow: false, components: undefined};
155
    page_allow_chbx.checked = !!settings.allow;
207 156

  
208
	ul.work_name_input.value = maybe_string(item);
209
	let old_components_ul = bag_components_ul;
210
	bag_components_ul = old_components_ul.cloneNode(false);
157
    set_page_components(settings.components);
158
}
211 159

  
212
	ul.work_li.insertBefore(bag_components_ul, old_components_ul);
213
	ul.work_li.removeChild(old_components_ul);
160
function work_page_li_components()
161
{
162
    if (page_payload_span.getAttribute("data-payload") === "no")
163
	return undefined;
214 164

  
215
	add_bag_components(components);
216
    }
165
    let prefix = page_payload_span.getAttribute("data-prefix");
166
    let name = page_payload_span.getAttribute("data-name");
167
    return [prefix, name];
168
}
217 169

  
218
    /* Used to get edited bag data for saving. */
219
    function work_bag_li_data(ul)
220
    {
221
	let components_ul = ul.work_name_input.nextElementSibling;
222
	let component_li = components_ul.firstElementChild;
170
/* Used to get edited page data for saving. */
171
function work_page_li_data(ul)
172
{
173
    let url = ul.work_name_input.value;
174
    let settings = {
175
	components : work_page_li_components(),
176
	allow : !!page_allow_chbx.checked
177
    };
223 178

  
224
	let components = [];
179
    return [url, settings];
180
}
225 181

  
226
	/* Last list element is empty li with id set. */
227
	while (component_li.id === '') {
228
	    components.push([component_li.getAttribute("data-prefix"),
229
			     component_li.getAttribute("data-name")]);
230
	    component_li = component_li.nextElementSibling;
231
	}
182
const empty_bag_component_li = by_id("empty_bag_component_li");
183
var bag_components_ul = by_id("bag_components_ul");
232 184

  
233
	return [ul.work_name_input.value, components];
185
/* Used to construct and update components list of edited bag. */
186
function add_bag_components(components)
187
{
188
    for (let component of components) {
189
	let [prefix, name] = component;
190
	let li = bag_component_li_template.cloneNode(true);
191
	li.setAttribute("data-prefix", prefix);
192
	li.setAttribute("data-name", name);
193
	let span = li.firstElementChild;
194
	span.textContent = nice_name(prefix, name);
195
	let remove_but = span.nextElementSibling;
196
	remove_but.addEventListener("click", () =>
197
				    bag_components_ul.removeChild(li));
198
	bag_components_ul.appendChild(li);
234 199
    }
235 200

  
236
    const script_url_input = by_id("script_url_field");
237
    const script_sha256_input = by_id("script_sha256_field");
238
    const script_contents_field = by_id("script_contents_field");
201
    bag_components_ul.appendChild(empty_bag_component_li);
202
}
239 203

  
240
    function maybe_string(maybe_defined)
241
    {
242
	return maybe_defined === undefined ? "" : maybe_defined + "";
243
    }
204
/* Used to reset edited bag. */
205
function reset_work_bag_li(ul, item, components)
206
{
207
    components = components || [];
244 208

  
245
    /* Used to reset edited script. */
246
    function reset_work_script_li(ul, name, data)
247
    {
248
	ul.work_name_input.value = maybe_string(name);
249
	if (data === undefined)
250
	    data = {};
251
	script_url_input.value = maybe_string(data.url);
252
	script_sha256_input.value = maybe_string(data.hash);
253
	script_contents_field.value = maybe_string(data.text);
254
    }
209
    ul.work_name_input.value = maybe_string(item);
210
    let old_components_ul = bag_components_ul;
211
    bag_components_ul = old_components_ul.cloneNode(false);
255 212

  
256
    /* Used to get edited script data for saving. */
257
    function work_script_li_data(ul)
258
    {
259
	return [ul.work_name_input.value, {
260
	    url : script_url_input.value,
261
	    hash : script_sha256_input.value,
262
	    text : script_contents_field.value
263
	}];
264
    }
213
    ul.work_li.insertBefore(bag_components_ul, old_components_ul);
214
    ul.work_li.removeChild(old_components_ul);
265 215

  
266
    function cancel_work(prefix)
267
    {
268
	let ul = ul_by_prefix[prefix];
216
    add_bag_components(components);
217
}
269 218

  
270
	if (ul.state === UL_STATE.IDLE)
271
	    return;
219
/* Used to get edited bag data for saving. */
220
function work_bag_li_data(ul)
221
{
222
    let components_ul = ul.work_name_input.nextElementSibling;
223
    let component_li = components_ul.firstElementChild;
272 224

  
273
	if (ul.state === UL_STATE.EDITING_ENTRY) {
274
	    add_li(prefix, ul.edited_item);
275
	}
225
    let components = [];
276 226

  
277
	ul.work_li.classList.add("hide");
278
	ul.state = UL_STATE.IDLE;
227
    /* Last list element is empty li with id set. */
228
    while (component_li.id === '') {
229
	components.push([component_li.getAttribute("data-prefix"),
230
			 component_li.getAttribute("data-name")]);
231
	component_li = component_li.nextElementSibling;
279 232
    }
280 233

  
281
    function save_work(prefix)
282
    {
283
	let ul = ul_by_prefix[prefix];
234
    return [ul.work_name_input.value, components];
235
}
284 236

  
285
	if (ul.state === UL_STATE.IDLE)
286
	    return;
237
const script_url_input = by_id("script_url_field");
238
const script_sha256_input = by_id("script_sha256_field");
239
const script_contents_field = by_id("script_contents_field");
287 240

  
288
	let [item, data] = ul.get_work_li_data(ul);
241
function maybe_string(maybe_defined)
242
{
243
    return maybe_defined === undefined ? "" : maybe_defined + "";
244
}
289 245

  
290
	/* Here we fire promises and return without waiting. */
246
/* Used to reset edited script. */
247
function reset_work_script_li(ul, name, data)
248
{
249
    ul.work_name_input.value = maybe_string(name);
250
    if (data === undefined)
251
	data = {};
252
    script_url_input.value = maybe_string(data.url);
253
    script_sha256_input.value = maybe_string(data.hash);
254
    script_contents_field.value = maybe_string(data.text);
255
}
291 256

  
292
	if (ul.state === UL_STATE.EDITING_ENTRY)
293
	    storage.replace(prefix, ul.edited_item, item, data);
294
	if (ul.state === UL_STATE.ADDING_ENTRY)
295
	    storage.set(prefix, item, data);
257
/* Used to get edited script data for saving. */
258
function work_script_li_data(ul)
259
{
260
    return [ul.work_name_input.value, {
261
	url : script_url_input.value,
262
	hash : script_sha256_input.value,
263
	text : script_contents_field.value
264
    }];
265
}
296 266

  
297
	cancel_work(prefix);
298
    }
267
function cancel_work(prefix)
268
{
269
    let ul = ul_by_prefix[prefix];
299 270

  
300
    function edit_item(prefix, item)
301
    {
302
	cancel_work(prefix);
271
    if (ul.state === UL_STATE.IDLE)
272
	return;
303 273

  
304
	let ul = ul_by_prefix[prefix];
305
	let li = by_id(item_li_id(prefix, item));
306
	ul.reset_work_li(ul, item, storage.get(prefix, item));
307
	ul.ul.insertBefore(ul.work_li, li);
308
	ul.ul.removeChild(li);
309
	ul.work_li.classList.remove("hide");
310

  
311
	ul.state = UL_STATE.EDITING_ENTRY;
312
	ul.edited_item = item;
274
    if (ul.state === UL_STATE.EDITING_ENTRY) {
275
	add_li(prefix, ul.edited_item);
313 276
    }
314 277

  
315
    const file_downloader = by_id("file_downloader");
278
    ul.work_li.classList.add("hide");
279
    ul.state = UL_STATE.IDLE;
280
}
316 281

  
317
    function recursively_export_item(prefix, name, added_items, items_data)
318
    {
319
	let key = prefix + name;
282
function save_work(prefix)
283
{
284
    let ul = ul_by_prefix[prefix];
320 285

  
321
	if (added_items.has(key))
322
	    return;
286
    if (ul.state === UL_STATE.IDLE)
287
	return;
323 288

  
324
	let data = storage.get(prefix, name);
325
	if (data === undefined) {
326
	    console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`);
327
	    return;
328
	}
289
    let [item, data] = ul.get_work_li_data(ul);
329 290

  
330
	if (prefix !== TYPE_PREFIX.SCRIPT) {
331
	    let components = prefix === TYPE_PREFIX.BAG ?
332
		data : [data.components];
291
    /* Here we fire promises and return without waiting. */
333 292

  
334
	    for (let [comp_prefix, comp_name] of components) {
335
		recursively_export_item(comp_prefix, comp_name,
336
					added_items, items_data);
337
	    }
338
	}
293
    if (ul.state === UL_STATE.EDITING_ENTRY)
294
	storage.replace(prefix, ul.edited_item, item, data);
295
    if (ul.state === UL_STATE.ADDING_ENTRY)
296
	storage.set(prefix, item, data);
339 297

  
340
	items_data.push({[key]: data});
341
	added_items.add(key);
342
    }
298
    cancel_work(prefix);
299
}
343 300

  
344
    function export_item(prefix, name)
345
    {
346
	let added_items = new Set();
347
	let items_data = [];
348
	recursively_export_item(prefix, name, added_items, items_data);
349
	let file = new Blob([JSON.stringify(items_data)],
350
			    {type: "application/json"});
351
	let url = URL.createObjectURL(file);
352
	file_downloader.setAttribute("href", url);
353
	file_downloader.setAttribute("download", prefix + name + ".json");
354
	file_downloader.click();
355
	file_downloader.removeAttribute("href");
356
	URL.revokeObjectURL(url);
357
    }
301
function edit_item(prefix, item)
302
{
303
    cancel_work(prefix);
358 304

  
359
    function add_new_item(prefix)
360
    {
361
	cancel_work(prefix);
305
    let ul = ul_by_prefix[prefix];
306
    let li = by_id(item_li_id(prefix, item));
307
    ul.reset_work_li(ul, item, storage.get(prefix, item));
308
    ul.ul.insertBefore(ul.work_li, li);
309
    ul.ul.removeChild(li);
310
    ul.work_li.classList.remove("hide");
362 311

  
363
	let ul = ul_by_prefix[prefix];
364
	ul.reset_work_li(ul);
365
	ul.work_li.classList.remove("hide");
366
	ul.ul.appendChild(ul.work_li);
312
    ul.state = UL_STATE.EDITING_ENTRY;
313
    ul.edited_item = item;
314
}
367 315

  
368
	ul.state = UL_STATE.ADDING_ENTRY;
369
    }
316
const file_downloader = by_id("file_downloader");
370 317

  
371
    const chbx_components_window = by_id("chbx_components_window");
318
function recursively_export_item(prefix, name, added_items, items_data)
319
{
320
    let key = prefix + name;
372 321

  
373
    function bag_components()
374
    {
375
	chbx_components_window.classList.remove("hide");
376
	radio_components_window.classList.add("hide");
322
    if (added_items.has(key))
323
	return;
377 324

  
378
	for (let li of chbx_components_ul.children) {
379
	    let chbx = li.firstElementChild;
380
	    chbx.checked = false;
381
	}
325
    let data = storage.get(prefix, name);
326
    if (data === undefined) {
327
	console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`);
328
	return;
382 329
    }
383 330

  
384
    function commit_bag_components()
385
    {
386
	let selected = [];
387

  
388
	for (let li of chbx_components_ul.children) {
389
	    let chbx = li.firstElementChild;
390
	    if (!chbx.checked)
391
		continue;
392

  
393
	    selected.push([li.getAttribute("data-prefix"),
394
			   li.getAttribute("data-name")]);
331
    if (prefix !== TYPE_PREFIX.SCRIPT) {
332
	let components = prefix === TYPE_PREFIX.BAG ?
333
	    data : [data.components];
334

  
335
	for (let [comp_prefix, comp_name] of components) {
336
	    recursively_export_item(comp_prefix, comp_name,
337
				    added_items, items_data);
395 338
	}
396

  
397
	add_bag_components(selected);
398
	cancel_components();
399 339
    }
400 340

  
401
    const radio_components_window = by_id("radio_components_window");
402
    var radio_component_none_input = by_id("radio_component_none_input");
403

  
404
    function page_components()
405
    {
406
	radio_components_window.classList.remove("hide");
407
	chbx_components_window.classList.add("hide");
341
    items_data.push({[key]: data});
342
    added_items.add(key);
343
}
344

  
345
function export_item(prefix, name)
346
{
347
    let added_items = new Set();
348
    let items_data = [];
349
    recursively_export_item(prefix, name, added_items, items_data);
350
    let file = new Blob([JSON.stringify(items_data)],
351
			{type: "application/json"});
352
    let url = URL.createObjectURL(file);
353
    file_downloader.setAttribute("href", url);
354
    file_downloader.setAttribute("download", prefix + name + ".json");
355
    file_downloader.click();
356
    file_downloader.removeAttribute("href");
357
    URL.revokeObjectURL(url);
358
}
359

  
360
function add_new_item(prefix)
361
{
362
    cancel_work(prefix);
363

  
364
    let ul = ul_by_prefix[prefix];
365
    ul.reset_work_li(ul);
366
    ul.work_li.classList.remove("hide");
367
    ul.ul.appendChild(ul.work_li);
368

  
369
    ul.state = UL_STATE.ADDING_ENTRY;
370
}
371

  
372
const chbx_components_window = by_id("chbx_components_window");
373

  
374
function bag_components()
375
{
376
    chbx_components_window.classList.remove("hide");
377
    radio_components_window.classList.add("hide");
378

  
379
    for (let li of chbx_components_ul.children) {
380
	let chbx = li.firstElementChild;
381
	chbx.checked = false;
382
    }
383
}
408 384

  
409
	radio_component_none_input.checked = true;
385
function commit_bag_components()
386
{
387
    let selected = [];
410 388

  
411
	let components = work_page_li_components();
412
	if (components === undefined)
413
	    return;
389
    for (let li of chbx_components_ul.children) {
390
	let chbx = li.firstElementChild;
391
	if (!chbx.checked)
392
	    continue;
414 393

  
415
	let [prefix, item] = components;
416
	let li = by_id(radio_li_id(prefix, item));
417
	if (li === null)
418
	    radio_component_none_input.checked = false;
419
	else
420
	    li.firstElementChild.checked = true;
394
	selected.push([li.getAttribute("data-prefix"),
395
		       li.getAttribute("data-name")]);
421 396
    }
422 397

  
423
    function commit_page_components()
424
    {
425
	let components = null;
398
    add_bag_components(selected);
399
    cancel_components();
400
}
426 401

  
427
	for (let li of radio_components_ul.children) {
428
	    let radio = li.firstElementChild;
429
	    if (!radio.checked)
430
		continue;
402
const radio_components_window = by_id("radio_components_window");
403
var radio_component_none_input = by_id("radio_component_none_input");
431 404

  
432
	    components = [li.getAttribute("data-prefix"),
433
			  li.getAttribute("data-name")];
405
function page_components()
406
{
407
    radio_components_window.classList.remove("hide");
408
    chbx_components_window.classList.add("hide");
434 409

  
435
	    if (radio.id === "radio_component_none_input")
436
		components = undefined;
410
    radio_component_none_input.checked = true;
437 411

  
438
	    break;
439
	}
412
    let components = work_page_li_components();
413
    if (components === undefined)
414
	return;
440 415

  
441
	if (components !== null)
442
	    set_page_components(components);
443
	cancel_components();
444
    }
416
    let [prefix, item] = components;
417
    let li = by_id(radio_li_id(prefix, item));
418
    if (li === null)
419
	radio_component_none_input.checked = false;
420
    else
421
	li.firstElementChild.checked = true;
422
}
445 423

  
446
    function cancel_components()
447
    {
448
	chbx_components_window.classList.add("hide");
449
	radio_components_window.classList.add("hide");
450
    }
424
function commit_page_components()
425
{
426
    let components = null;
451 427

  
452
    const UL_STATE = {
453
	EDITING_ENTRY : 0,
454
	ADDING_ENTRY : 1,
455
	IDLE : 2
456
    };
457

  
458
    const ul_by_prefix = {
459
	[TYPE_PREFIX.PAGE] : {
460
	    ul : by_id("pages_ul"),
461
	    work_li : by_id("work_page_li"),
462
	    work_name_input : by_id("page_url_field"),
463
	    reset_work_li : reset_work_page_li,
464
	    get_work_li_data : work_page_li_data,
465
	    select_components : page_components,
466
	    commit_components : commit_page_components,
467
	    state : UL_STATE.IDLE,
468
	    edited_item : undefined,
469
	},
470
	[TYPE_PREFIX.BAG] : {
471
	    ul : by_id("bags_ul"),
472
	    work_li : by_id("work_bag_li"),
473
	    work_name_input : by_id("bag_name_field"),
474
	    reset_work_li : reset_work_bag_li,
475
	    get_work_li_data : work_bag_li_data,
476
	    select_components : bag_components,
477
	    commit_components : commit_bag_components,
478
	    state : UL_STATE.IDLE,
479
	    edited_item : undefined,
480
	},
481
	[TYPE_PREFIX.SCRIPT] : {
482
	    ul : by_id("scripts_ul"),
483
	    work_li : by_id("work_script_li"),
484
	    work_name_input : by_id("script_name_field"),
485
	    reset_work_li : reset_work_script_li,
486
	    get_work_li_data : work_script_li_data,
487
	    state : UL_STATE.IDLE,
488
	    edited_item : undefined,
489
	}
490
    }
428
    for (let li of radio_components_ul.children) {
429
	let radio = li.firstElementChild;
430
	if (!radio.checked)
431
	    continue;
432

  
433
	components = [li.getAttribute("data-prefix"),
434
		      li.getAttribute("data-name")];
435

  
436
	if (radio.id === "radio_component_none_input")
437
	    components = undefined;
438

  
439
	break;
440
    }
441

  
442
    if (components !== null)
443
	set_page_components(components);
444
    cancel_components();
445
}
446

  
447
function cancel_components()
448
{
449
    chbx_components_window.classList.add("hide");
450
    radio_components_window.classList.add("hide");
451
}
452

  
453
const UL_STATE = {
454
    EDITING_ENTRY : 0,
455
    ADDING_ENTRY : 1,
456
    IDLE : 2
457
};
458

  
459
const ul_by_prefix = {
460
    [TYPE_PREFIX.PAGE] : {
461
	ul : by_id("pages_ul"),
462
	work_li : by_id("work_page_li"),
463
	work_name_input : by_id("page_url_field"),
464
	reset_work_li : reset_work_page_li,
465
	get_work_li_data : work_page_li_data,
466
	select_components : page_components,
467
	commit_components : commit_page_components,
468
	state : UL_STATE.IDLE,
469
	edited_item : undefined,
470
    },
471
    [TYPE_PREFIX.BAG] : {
472
	ul : by_id("bags_ul"),
473
	work_li : by_id("work_bag_li"),
474
	work_name_input : by_id("bag_name_field"),
475
	reset_work_li : reset_work_bag_li,
476
	get_work_li_data : work_bag_li_data,
477
	select_components : bag_components,
478
	commit_components : commit_bag_components,
479
	state : UL_STATE.IDLE,
480
	edited_item : undefined,
481
    },
482
    [TYPE_PREFIX.SCRIPT] : {
483
	ul : by_id("scripts_ul"),
484
	work_li : by_id("work_script_li"),
485
	work_name_input : by_id("script_name_field"),
486
	reset_work_li : reset_work_script_li,
487
	get_work_li_data : work_script_li_data,
488
	state : UL_STATE.IDLE,
489
	edited_item : undefined,
490
    }
491
}
492

  
493
const import_window = by_id("import_window");
494
const import_loading_radio = by_id("import_loading_radio");
495
const import_failed_radio = by_id("import_failed_radio");
496
const import_selection_radio = by_id("import_selection_radio");
497
const bad_file_errormsg = by_id("bad_file_errormsg");
498

  
499
/*
500
 * Newer browsers could utilise `text' method of File objects.
501
 * Older ones require FileReader.
502
 */
491 503

  
492
    const import_window = by_id("import_window");
493
    const import_loading_radio = by_id("import_loading_radio");
494
    const import_failed_radio = by_id("import_failed_radio");
495
    const import_selection_radio = by_id("import_selection_radio");
496
    const bad_file_errormsg = by_id("bad_file_errormsg");
504
function _read_file(file, resolve, reject)
505
{
506
    let reader = new FileReader();
507

  
508
    reader.onload = () => resolve(reader.result);
509
    reader.onerror = () => reject(reader.error);
510
    reader.readAsText(file);
511
}
512

  
513
function read_file(file)
514
{
515
    return new Promise((resolve, reject) =>
516
		       _read_file(file, resolve, reject));
517
}
518

  
519
async function import_from_file(event)
520
{
521
    let files = event.target.files;
522
    if (files.length < 1)
523
	return;
524

  
525
    import_window.classList.remove("hide");
526
    import_loading_radio.checked = true;
527

  
528
    let result = undefined;
529

  
530
    try {
531
	result = JSON.parse(await read_file(files[0]));
532
    } catch(e) {
533
	bad_file_errormsg.textContent = "" + e;
534
	import_failed_radio.checked = true;
535
	return;
536
    }
537

  
538
    let errormsg = validate_settings(result);
539
    if (errormsg !== false) {
540
	bad_file_errormsg.textContent = errormsg;
541
	import_failed_radio.checked = true;
542
	return;
543
    }
544

  
545
    populate_import_list(result);
546
    import_selection_radio.checked = true;
547
}
548

  
549
function validate_settings(settings)
550
{
551
    // TODO
552
    return false;
553
}
554

  
555
function import_li_id(prefix, item)
556
{
557
    return `ili_${prefix}_${item}`;
558
}
559

  
560
let import_ul = by_id("import_ul");
561
let import_chbxs_colliding = undefined;
562
let settings_import_map = undefined;
563

  
564
function populate_import_list(settings)
565
{
566
    let old_children = import_ul.children;
567
    while (old_children[0] !== undefined)
568
	import_ul.removeChild(old_children[0]);
569

  
570
    import_chbxs_colliding = [];
571
    settings_import_map = new Map();
572

  
573
    for (let setting of settings) {
574
	let [key, value] = Object.entries(setting)[0];
575
	let prefix = key[0];
576
	let name = key.substring(1);
577
	add_import_li(prefix, name);
578
	settings_import_map.set(key, value);
579
    }
580
}
581

  
582
function add_import_li(prefix, name)
583
{
584
    let li = import_li_template.cloneNode(true);
585
    let name_span = li.firstElementChild;
586
    let chbx = name_span.nextElementSibling;
587
    let warning_span = chbx.nextElementSibling;
588

  
589
    li.setAttribute("data-prefix", prefix);
590
    li.setAttribute("data-name", name);
591
    li.id = import_li_id(prefix, name);
592
    name_span.textContent = nice_name(prefix, name);
593

  
594
    if (storage.get(prefix, name) !== undefined) {
595
	import_chbxs_colliding.push(chbx);
596
	warning_span.textContent = "(will overwrite existing setting!)";
597
    }
598

  
599
    import_ul.appendChild(li);
600
}
601

  
602
function check_all_imports()
603
{
604
    for (let li of import_ul.children)
605
	li.firstElementChild.nextElementSibling.checked = true;
606
}
607

  
608
function uncheck_all_imports()
609
{
610
    for (let li of import_ul.children)
611
	li.firstElementChild.nextElementSibling.checked = false;
612
}
613

  
614
function uncheck_colliding_imports()
615
{
616
    for (let chbx of import_chbxs_colliding)
617
	chbx.checked = false;
618
}
619

  
620
const file_opener_form = by_id("file_opener_form");
621

  
622
function hide_import_window()
623
{
624
    import_window.classList.add("hide");
625
    /* Let GC free some memory */
626
    import_chbxs_colliding = undefined;
627
    settings_import_map = undefined;
497 628

  
498 629
    /*
499
     * Newer browsers could utilise `text' method of File objects.
500
     * Older ones require FileReader.
630
     * Reset file <input>. Without this, a second attempt to import the same
631
     * file would result in "change" event on happening on <input> element.
501 632
     */
633
    file_opener_form.reset();
634
}
502 635

  
503
    function _read_file(file, resolve, reject)
504
    {
505
	let reader = new FileReader();
636
function commit_import()
637
{
638
    for (let li of import_ul.children) {
639
	let chbx = li.firstElementChild.nextElementSibling;
506 640

  
507
	reader.onload = () => resolve(reader.result);
508
	reader.onerror = () => reject(reader.error);
509
	reader.readAsText(file);
510
    }
511

  
512
    function read_file(file)
513
    {
514
	return new Promise((resolve, reject) =>
515
			   _read_file(file, resolve, reject));
516
    }
641
	if (!chbx.checked)
642
	    continue;
517 643

  
518
    async function import_from_file(event)
519
    {
520
	let files = event.target.files;
521
	if (files.length < 1)
522
	    return;
523

  
524
	import_window.classList.remove("hide");
525
	import_loading_radio.checked = true;
526

  
527
	let result = undefined;
528

  
529
	try {
530
	    result = JSON.parse(await read_file(files[0]));
531
	} catch(e) {
532
	    bad_file_errormsg.textContent = "" + e;
533
	    import_failed_radio.checked = true;
534
	    return;
535
	}
536

  
537
	let errormsg = validate_settings(result);
538
	if (errormsg !== false) {
539
	    bad_file_errormsg.textContent = errormsg;
540
	    import_failed_radio.checked = true;
541
	    return;
644
	let prefix = li.getAttribute("data-prefix");
645
	let name = li.getAttribute("data-name");
646
	let key = prefix + name;
647
	let value = settings_import_map.get(key);
648
	storage.set(prefix, name, value);
649
    }
650

  
651
    hide_import_window();
652
}
653

  
654
function initialize_import_facility()
655
{
656
    let import_but = by_id("import_but");
657
    let file_opener = by_id("file_opener");
658
    let import_failok_but = by_id("import_failok_but");
659
    let check_all_import_but = by_id("check_all_import_but");
660
    let uncheck_all_import_but = by_id("uncheck_all_import_but");
661
    let uncheck_existing_import_but = by_id("uncheck_existing_import_but");
662
    let commit_import_but = by_id("commit_import_but");
663
    let cancel_import_but = by_id("cancel_import_but");
664
    import_but.addEventListener("click", () => file_opener.click());
665
    file_opener.addEventListener("change", import_from_file);
666
    import_failok_but.addEventListener("click", hide_import_window);
667
    check_all_import_but.addEventListener("click", check_all_imports);
668
    uncheck_all_import_but.addEventListener("click", uncheck_all_imports);
669
    uncheck_colliding_import_but
670
	.addEventListener("click", uncheck_colliding_imports);
671
    commit_import_but.addEventListener("click", commit_import);
672
    cancel_import_but.addEventListener("click", hide_import_window);
673
}
674

  
675
async function main()
676
{
677
    storage = await get_remote_storage();
678

  
679
    for (let prefix of list_prefixes) {
680
	for (let item of storage.get_all_names(prefix).sort()) {
681
	    add_li(prefix, item, true);
682
	    add_chbx_li(prefix, item);
683
	    add_radio_li(prefix, item);
542 684
	}
543 685

  
544
	populate_import_list(result);
545
	import_selection_radio.checked = true;
546
    }
547

  
548
    function validate_settings(settings)
549
    {
550
	// TODO
551
	return false;
552
    }
686
	let name = TYPE_NAME[prefix];
553 687

  
554
    function import_li_id(prefix, item)
555
    {
556
	return `ili_${prefix}_${item}`;
557
    }
688
	let add_but = by_id(`add_${name}_but`);
689
	let discard_but = by_id(`discard_${name}_but`);
690
	let save_but = by_id(`save_${name}_but`);
558 691

  
559
    let import_ul = by_id("import_ul");
560
    let import_chbxs_colliding = undefined;
561
    let settings_import_map = undefined;
562

  
563
    function populate_import_list(settings)
564
    {
565
	let old_children = import_ul.children;
566
	while (old_children[0] !== undefined)
567
	    import_ul.removeChild(old_children[0]);
568

  
569
	import_chbxs_colliding = [];
570
	settings_import_map = new Map();
571

  
572
	for (let setting of settings) {
573
	    let [key, value] = Object.entries(setting)[0];
574
	    let prefix = key[0];
575
	    let name = key.substring(1);
576
	    add_import_li(prefix, name);
577
	    settings_import_map.set(key, value);
578
	}
579
    }
692
	add_but.addEventListener("click", () => add_new_item(prefix));
693
	discard_but.addEventListener("click", () => cancel_work(prefix));
694
	save_but.addEventListener("click", () => save_work(prefix));
580 695

  
581
    function add_import_li(prefix, name)
582
    {
583
	let li = import_li_template.cloneNode(true);
584
	let name_span = li.firstElementChild;
585
	let chbx = name_span.nextElementSibling;
586
	let warning_span = chbx.nextElementSibling;
696
	if (prefix === TYPE_PREFIX.SCRIPT)
697
	    continue;
587 698

  
588
	li.setAttribute("data-prefix", prefix);
589
	li.setAttribute("data-name", name);
590
	li.id = import_li_id(prefix, name);
591
	name_span.textContent = nice_name(prefix, name);
592

  
593
	if (storage.get(prefix, name) !== undefined) {
594
	    import_chbxs_colliding.push(chbx);
595
	    warning_span.textContent = "(will overwrite existing setting!)";
596
	}
699
	let ul = ul_by_prefix[prefix];
597 700

  
598
	import_ul.appendChild(li);
599
    }
701
	let commit_components_but = by_id(`commit_${name}_components_but`);
702
	let cancel_components_but = by_id(`cancel_${name}_components_but`);
703
	let select_components_but = by_id(`select_${name}_components_but`);
600 704

  
601
    function check_all_imports()
602
    {
603
	for (let li of import_ul.children)
604
	    li.firstElementChild.nextElementSibling.checked = true;
705
	commit_components_but
706
	    .addEventListener("click", ul.commit_components);
707
	select_components_but
708
	    .addEventListener("click", ul.select_components);
709
	cancel_components_but.addEventListener("click", cancel_components);
605 710
    }
606 711

  
607
    function uncheck_all_imports()
608
    {
609
	for (let li of import_ul.children)
610
	    li.firstElementChild.nextElementSibling.checked = false;
611
    }
612

  
613
    function uncheck_colliding_imports()
614
    {
615
	for (let chbx of import_chbxs_colliding)
616
	    chbx.checked = false;
617
    }
712
    initialize_import_facility();
618 713

  
619
    const file_opener_form = by_id("file_opener_form");
714
    storage.add_change_listener(handle_change);
715
}
620 716

  
621
    function hide_import_window()
622
    {
623
	import_window.classList.add("hide");
624
	/* Let GC free some memory */
625
	import_chbxs_colliding = undefined;
626
	settings_import_map = undefined;
717
function handle_change(change)
718
{
719
    if (change.old_val === undefined) {
720
	add_li(change.prefix, change.item);
721
	add_chbx_li(change.prefix, change.item);
722
	add_radio_li(change.prefix, change.item);
627 723

  
628
	/*
629
	 * Reset file <input>. Without this, a second attempt to import the same
630
	 * file would result in "change" event on happening on <input> element.
631
	 */
632
	file_opener_form.reset();
724
	return;
633 725
    }
634 726

  
635
    function commit_import()
636
    {
637
	for (let li of import_ul.children) {
638
	    let chbx = li.firstElementChild.nextElementSibling;
639

  
640
	    if (!chbx.checked)
641
		continue;
642

  
643
	    let prefix = li.getAttribute("data-prefix");
644
	    let name = li.getAttribute("data-name");
645
	    let key = prefix + name;
646
	    let value = settings_import_map.get(key);
647
	    storage.set(prefix, name, value);
648
	}
649

  
650
	hide_import_window();
651
    }
727
    if (change.new_val !== undefined)
728
	return;
652 729

  
653
    function initialize_import_facility()
654
    {
655
	let import_but = by_id("import_but");
656
	let file_opener = by_id("file_opener");
657
	let import_failok_but = by_id("import_failok_but");
658
	let check_all_import_but = by_id("check_all_import_but");
659
	let uncheck_all_import_but = by_id("uncheck_all_import_but");
660
	let uncheck_existing_import_but = by_id("uncheck_existing_import_but");
661
	let commit_import_but = by_id("commit_import_but");
662
	let cancel_import_but = by_id("cancel_import_but");
663
	import_but.addEventListener("click", () => file_opener.click());
664
	file_opener.addEventListener("change", import_from_file);
665
	import_failok_but.addEventListener("click", hide_import_window);
666
	check_all_import_but.addEventListener("click", check_all_imports);
667
	uncheck_all_import_but.addEventListener("click", uncheck_all_imports);
668
	uncheck_colliding_import_but
669
	    .addEventListener("click", uncheck_colliding_imports);
670
	commit_import_but.addEventListener("click", commit_import);
671
	cancel_import_but.addEventListener("click", hide_import_window);
730
    let ul = ul_by_prefix[change.prefix];
731
    if (ul.state === UL_STATE.EDITING_ENTRY &&
732
	ul.edited_item === change.item) {
733
	ul.state = UL_STATE.ADDING_ENTRY;
734
	return;
672 735
    }
673 736

  
674
    async function main()
675
    {
676
	storage = await get_storage();
677

  
678
	for (let prefix of list_prefixes) {
679
	    for (let item of storage.get_all_names(prefix).sort()) {
680
		add_li(prefix, item, true);
681
		add_chbx_li(prefix, item);
682
		add_radio_li(prefix, item);
683
	    }
684

  
685
	    let name = TYPE_NAME[prefix];
686

  
687
	    let add_but = by_id(`add_${name}_but`);
688
	    let discard_but = by_id(`discard_${name}_but`);
689
	    let save_but = by_id(`save_${name}_but`);
690

  
691
	    add_but.addEventListener("click", () => add_new_item(prefix));
692
	    discard_but.addEventListener("click", () => cancel_work(prefix));
693
	    save_but.addEventListener("click", () => save_work(prefix));
694

  
695
	    if (prefix === TYPE_PREFIX.SCRIPT)
696
		continue;
697

  
698
	    let ul = ul_by_prefix[prefix];
699

  
700
	    let commit_components_but = by_id(`commit_${name}_components_but`);
701
	    let cancel_components_but = by_id(`cancel_${name}_components_but`);
702
	    let select_components_but = by_id(`select_${name}_components_but`);
703

  
704
	    commit_components_but
705
		.addEventListener("click", ul.commit_components);
706
	    select_components_but
707
		.addEventListener("click", ul.select_components);
708
	    cancel_components_but.addEventListener("click", cancel_components);
709
	}
710

  
711
	initialize_import_facility();
737
    let uls_creators = [[ul.ul, item_li_id]];
712 738

  
713
	storage.add_change_listener(handle_change);
739
    if (change.prefix !== TYPE_PREFIX.PAGE) {
740
	uls_creators.push([chbx_components_ul, chbx_li_id]);
741
	uls_creators.push([radio_components_ul, radio_li_id]);
714 742
    }
715 743

  
716
    function handle_change(change)
717
    {
718
	if (change.old_val === undefined) {
719
	    add_li(change.prefix, change.item);
720
	    add_chbx_li(change.prefix, change.item);
721
	    add_radio_li(change.prefix, change.item);
722

  
723
	    return;
724
	}
725

  
726
	if (change.new_val !== undefined)
727
	    return;
728

  
729
	let ul = ul_by_prefix[change.prefix];
730
	if (ul.state === UL_STATE.EDITING_ENTRY &&
731
	    ul.edited_item === change.item) {
732
	    ul.state = UL_STATE.ADDING_ENTRY;
733
	    return;
734
	}
735

  
736
	let uls_creators = [[ul.ul, item_li_id]];
737

  
738
	if (change.prefix !== TYPE_PREFIX.PAGE) {
739
	    uls_creators.push([chbx_components_ul, chbx_li_id]);
740
	    uls_creators.push([radio_components_ul, radio_li_id]);
741
	}
742

  
743
	for (let [components_ul, id_creator] of uls_creators) {
744
	    let li = by_id(id_creator(change.prefix, change.item));
745
	    components_ul.removeChild(li);
746
	}
744
    for (let [components_ul, id_creator] of uls_creators) {
745
	let li = by_id(id_creator(change.prefix, change.item));
746
	components_ul.removeChild(li);
747 747
    }
748
}
748 749

  
749
    main();
750
})();
750
main();

Also available in: Unified diff