Project

General

Profile

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

haketilo / html / options_main.js @ 6bae771d

1
/**
2
 * Myext HTML options page main script
3
 *
4
 * Copyright (C) 2021 Wojtek Kosior
5
 *
6
 * This code is dual-licensed under:
7
 * - Asshole license 1.0,
8
 * - GPLv3 or (at your option) any later version
9
 *
10
 * "dual-licensed" means you can choose the license you prefer.
11
 *
12
 * This code is released under a permissive license because I disapprove of
13
 * copyright and wouldn't be willing to sue a violator. Despite not putting
14
 * this code under copyleft (which is also kind of copyright), I do not want
15
 * it to be made proprietary. Hence, the permissive alternative to GPL is the
16
 * Asshole license 1.0 that allows me to call you an asshole if you use it.
17
 * This means you're legally ok regardless of how you utilize this code but if
18
 * you make it into something nonfree, you're an asshole.
19
 *
20
 * You should have received a copy of both GPLv3 and Asshole license 1.0
21
 * together with this code. If not, please see:
22
 * - https://www.gnu.org/licenses/gpl-3.0.en.html
23
 * - https://koszko.org/asshole-license.txt
24
 */
25

    
26
"use strict";
27

    
28
(() => {
29
    const get_storage = window.get_storage;
30
    const TYPE_PREFIX = window.TYPE_PREFIX;
31
    const TYPE_NAME = window.TYPE_NAME;
32
    const list_prefixes = window.list_prefixes;
33

    
34
    var storage;
35
    function by_id(id)
36
    {
37
	return document.getElementById(id);
38
    }
39

    
40
    const item_li_template = by_id("item_li_template");
41
    const bag_component_li_template = by_id("bag_component_li_template");
42
    const chbx_component_li_template = by_id("chbx_component_li_template");
43
    const radio_component_li_template = by_id("radio_component_li_template");
44
    /* Make sure they are later cloned without id. */
45
    item_li_template.removeAttribute("id");
46
    bag_component_li_template.removeAttribute("id");
47
    chbx_component_li_template.removeAttribute("id");
48
    radio_component_li_template.removeAttribute("id");
49

    
50
    function item_li_id(prefix, item)
51
    {
52
	return `li_${prefix}_${item}`;
53
    }
54

    
55
    /* Insert into list of bags/pages/scripts */
56
    function add_li(prefix, item, at_the_end=false)
57
    {
58
	let ul = ul_by_prefix[prefix];
59
	let li = item_li_template.cloneNode(true);
60
	li.id = item_li_id(prefix, item);
61

    
62
	let span = li.firstElementChild;
63
	span.textContent = item;
64

    
65
	let edit_button = span.nextElementSibling;
66
	edit_button.addEventListener("click", () => edit_item(prefix, item));
67

    
68
	let remove_button = edit_button.nextElementSibling;
69
	remove_button.addEventListener("click",
70
				       () => storage.remove(prefix, item));
71

    
72
	if (!at_the_end) {
73
	    for (let element of ul.ul.children) {
74
		if (element.id < li.id || element.id.startsWith("work_"))
75
		    continue;
76

    
77
		ul.ul.insertBefore(li, element);
78
		return;
79
	    }
80
	}
81

    
82
	ul.ul.appendChild(li);
83
    }
84

    
85
    const chbx_components_ul = by_id("chbx_components_ul");
86
    const radio_components_ul = by_id("radio_components_ul");
87

    
88
    function chbx_li_id(prefix, item)
89
    {
90
	return `cli_${prefix}_${item}`;
91
    }
92

    
93
    function radio_li_id(prefix, item)
94
    {
95
	return `rli_${prefix}_${item}`;
96
    }
97

    
98
    //TODO: refactor the 2 functions below
99

    
100
    function add_chbx_li(prefix, name)
101
    {
102
	if (prefix === TYPE_PREFIX.PAGE)
103
	    return;
104

    
105
	let li = chbx_component_li_template.cloneNode(true);
106
	li.id = chbx_li_id(prefix, name);
107
	li.setAttribute("data-prefix", prefix);
108
	li.setAttribute("data-name", name);
109

    
110
	let chbx = li.firstElementChild;
111
	let span = chbx.nextElementSibling;
112

    
113
	span.textContent = `${name} (${TYPE_NAME[prefix]})`;
114

    
115
	chbx_components_ul.appendChild(li);
116
    }
117

    
118
    var radio_component_none_li = by_id("radio_component_none_li");
119

    
120
    function add_radio_li(prefix, name)
121
    {
122
	if (prefix === TYPE_PREFIX.PAGE)
123
	    return;
124

    
125
	let li = radio_component_li_template.cloneNode(true);
126
	li.id = radio_li_id(prefix, name);
127
	li.setAttribute("data-prefix", prefix);
128
	li.setAttribute("data-name", name);
129

    
130
	let radio = li.firstElementChild;
131
	let span = radio.nextElementSibling;
132

    
133
	span.textContent = `${name} (${TYPE_NAME[prefix]})`;
134

    
135
	radio_components_ul.insertBefore(li, radio_component_none_li);
136
    }
137

    
138
    const page_payload_span = by_id("page_payload");
139

    
140
    function set_page_components(components)
141
    {
142
	if (components === undefined) {
143
	    page_payload_span.setAttribute("data-payload", "no");
144
	    page_payload_span.textContent = "(None)";
145
	} else {
146
	    page_payload_span.setAttribute("data-payload", "yes");
147
	    let [prefix, name] = components;
148
	    page_payload_span.setAttribute("data-prefix", prefix);
149
	    page_payload_span.setAttribute("data-name", name);
150
	    page_payload_span.textContent = `${name} (${TYPE_NAME[prefix]})`;
151
	}
152
    }
153

    
154
    const page_allow_chbx = by_id("page_allow_chbx");
155

    
156
    /* Used to reset edited page. */
157
    function reset_work_page_li(ul, item, settings)
158
    {
159
	ul.work_name_input.value = maybe_string(item);
160
	settings = settings || {allow: false, components: undefined};
161
	page_allow_chbx.checked = !!settings.allow;
162

    
163
	set_page_components(settings.components);
164
    }
165

    
166
    function work_page_li_components()
167
    {
168
	if (page_payload_span.getAttribute("data-payload") === "no")
169
	    return undefined;
170

    
171
	let prefix = page_payload_span.getAttribute("data-prefix");
172
	let name = page_payload_span.getAttribute("data-name");
173
	return [prefix, name];
174
    }
175

    
176
    /* Used to get edited page data for saving. */
177
    function work_page_li_data(ul)
178
    {
179
	let url = ul.work_name_input.value;
180
	let settings = {
181
	    components : work_page_li_components(),
182
	    allow : !!page_allow_chbx.checked
183
	};
184

    
185
	return [url, settings];
186
    }
187

    
188
    const empty_bag_component_li = by_id("empty_bag_component_li");
189
    var bag_components_ul = by_id("bag_components_ul");
190

    
191
    /* Used to construct and update components list of edited bag. */
192
    function add_bag_components(components)
193
    {
194
	for (let component of components) {
195
	    let [prefix, name] = component;
196
	    let li = bag_component_li_template.cloneNode(true);
197
	    li.setAttribute("data-prefix", prefix);
198
	    li.setAttribute("data-name", name);
199
	    let span = li.firstElementChild;
200
	    span.textContent = `${name} (${TYPE_NAME[prefix]})`;
201
	    let remove_but = span.nextElementSibling;
202
	    remove_but.addEventListener("click", () =>
203
					bag_components_ul.removeChild(li));
204
	    bag_components_ul.appendChild(li);
205
	}
206

    
207
	bag_components_ul.appendChild(empty_bag_component_li);
208
    }
209

    
210
    /* Used to reset edited bag. */
211
    function reset_work_bag_li(ul, item, components)
212
    {
213
	components = components || [];
214

    
215
	ul.work_name_input.value = maybe_string(item);
216
	let old_components_ul = bag_components_ul;
217
	bag_components_ul = old_components_ul.cloneNode(false);
218

    
219
	ul.work_li.insertBefore(bag_components_ul, old_components_ul);
220
	ul.work_li.removeChild(old_components_ul);
221

    
222
	add_bag_components(components);
223
    }
224

    
225
    /* Used to get edited bag data for saving. */
226
    function work_bag_li_data(ul)
227
    {
228
	let components_ul = ul.work_name_input.nextElementSibling;
229
	let component_li = components_ul.firstElementChild;
230

    
231
	let components = [];
232

    
233
	/* Last list element is empty li with id set. */
234
	while (component_li.id === '') {
235
	    components.push([component_li.getAttribute("data-prefix"),
236
			     component_li.getAttribute("data-name")]);
237
	    component_li = component_li.nextElementSibling;
238
	}
239

    
240
	return [ul.work_name_input.value, components];
241
    }
242

    
243
    const script_url_input = by_id("script_url_field");
244
    const script_sha256_input = by_id("script_sha256_field");
245
    const script_contents_field = by_id("script_contents_field");
246

    
247
    function maybe_string(maybe_defined)
248
    {
249
	return maybe_defined === undefined ? "" : maybe_defined + "";
250
    }
251

    
252
    /* Used to reset edited script. */
253
    function reset_work_script_li(ul, name, data)
254
    {
255
	ul.work_name_input.value = maybe_string(name);
256
	if (data === undefined)
257
	    data = {};
258
	script_url_input.value = maybe_string(data.url);
259
	script_sha256_input.value = maybe_string(data.hash);
260
	script_contents_field.value = maybe_string(data.text);
261
    }
262

    
263
    /* Used to get edited script data for saving. */
264
    function work_script_li_data(ul)
265
    {
266
	return [ul.work_name_input.value, {
267
	    url : script_url_input.value,
268
	    hash : script_sha256_input.value,
269
	    text : script_contents_field.value
270
	}];
271
    }
272

    
273
    function cancel_work(prefix)
274
    {
275
	let ul = ul_by_prefix[prefix];
276

    
277
	if (ul.state === UL_STATE.IDLE)
278
	    return;
279

    
280
	if (ul.state === UL_STATE.EDITING_ENTRY) {
281
	    add_li(prefix, ul.edited_item);
282
	}
283

    
284
	ul.work_li.classList.add("hide");
285
	ul.state = UL_STATE.IDLE;
286
    }
287

    
288
    function save_work(prefix)
289
    {
290
	let ul = ul_by_prefix[prefix];
291

    
292
	if (ul.state === UL_STATE.IDLE)
293
	    return;
294

    
295
	let [item, data] = ul.get_work_li_data(ul);
296

    
297
	/* Here we fire promises and return without waiting. */
298

    
299
	if (ul.state === UL_STATE.EDITING_ENTRY)
300
	    storage.replace(prefix, ul.edited_item, item, data);
301
	if (ul.state === UL_STATE.ADDING_ENTRY)
302
	    storage.set(prefix, item, data);
303

    
304
	cancel_work(prefix);
305
    }
306

    
307
    function edit_item(prefix, item)
308
    {
309
	cancel_work(prefix);
310

    
311
	let ul = ul_by_prefix[prefix];
312
	let li = by_id(item_li_id(prefix, item));
313
	ul.reset_work_li(ul, item, storage.get(prefix, item));
314
	ul.ul.insertBefore(ul.work_li, li);
315
	ul.ul.removeChild(li);
316
	ul.work_li.classList.remove("hide");
317

    
318
	ul.state = UL_STATE.EDITING_ENTRY;
319
	ul.edited_item = item;
320
    }
321

    
322
    function add_new_item(prefix)
323
    {
324
	cancel_work(prefix);
325

    
326
	let ul = ul_by_prefix[prefix];
327
	ul.reset_work_li(ul);
328
	ul.work_li.classList.remove("hide");
329
	ul.ul.appendChild(ul.work_li);
330

    
331
	ul.state = UL_STATE.ADDING_ENTRY;
332
    }
333

    
334
    const chbx_components_window = by_id("chbx_components_window");
335

    
336
    function bag_components()
337
    {
338
	chbx_components_window.classList.remove("hide");
339
	radio_components_window.classList.add("hide");
340

    
341
	for (let li of chbx_components_ul.children) {
342
	    let chbx = li.firstElementChild;
343
	    chbx.checked = false;
344
	}
345
    }
346

    
347
    function commit_bag_components()
348
    {
349
	let selected = [];
350

    
351
	for (let li of chbx_components_ul.children) {
352
	    let chbx = li.firstElementChild;
353
	    if (!chbx.checked)
354
		continue;
355

    
356
	    selected.push([li.getAttribute("data-prefix"),
357
			   li.getAttribute("data-name")]);
358
	}
359

    
360
	add_bag_components(selected);
361
	cancel_components();
362
    }
363

    
364
    const radio_components_window = by_id("radio_components_window");
365
    var radio_component_none_input = by_id("radio_component_none_input");
366

    
367
    function page_components()
368
    {
369
	radio_components_window.classList.remove("hide");
370
	chbx_components_window.classList.add("hide");
371

    
372
	radio_component_none_input.checked = true;
373

    
374
	let components = work_page_li_components();
375
	if (components === undefined)
376
	    return;
377

    
378
	let [prefix, item] = components;
379
	let li = by_id(radio_li_id(prefix, item));
380
	if (li === null)
381
	    radio_component_none_input.checked = false;
382
	else
383
	    li.firstElementChild.checked = true;
384
    }
385

    
386
    function commit_page_components()
387
    {
388
	let components = null;
389

    
390
	for (let li of radio_components_ul.children) {
391
	    let radio = li.firstElementChild;
392
	    if (!radio.checked)
393
		continue;
394

    
395
	    components = [li.getAttribute("data-prefix"),
396
			  li.getAttribute("data-name")];
397

    
398
	    if (radio.id === "radio_component_none_input")
399
		components = undefined;
400

    
401
	    break;
402
	}
403

    
404
	if (components !== null)
405
	    set_page_components(components);
406
	cancel_components();
407
    }
408

    
409
    function cancel_components()
410
    {
411
	chbx_components_window.classList.add("hide");
412
	radio_components_window.classList.add("hide");
413
    }
414

    
415
    const UL_STATE = {
416
	EDITING_ENTRY : 0,
417
	ADDING_ENTRY : 1,
418
	IDLE : 2
419
    };
420

    
421
    const ul_by_prefix = {
422
	[TYPE_PREFIX.PAGE] : {
423
	    ul : by_id("pages_ul"),
424
	    work_li : by_id("work_page_li"),
425
	    work_name_input : by_id("page_url_field"),
426
	    reset_work_li : reset_work_page_li,
427
	    get_work_li_data : work_page_li_data,
428
	    select_components : page_components,
429
	    commit_components : commit_page_components,
430
	    state : UL_STATE.IDLE,
431
	    edited_item : undefined,
432
	},
433
	[TYPE_PREFIX.BAG] : {
434
	    ul : by_id("bags_ul"),
435
	    work_li : by_id("work_bag_li"),
436
	    work_name_input : by_id("bag_name_field"),
437
	    reset_work_li : reset_work_bag_li,
438
	    get_work_li_data : work_bag_li_data,
439
	    select_components : bag_components,
440
	    commit_components : commit_bag_components,
441
	    state : UL_STATE.IDLE,
442
	    edited_item : undefined,
443
	},
444
	[TYPE_PREFIX.SCRIPT] : {
445
	    ul : by_id("scripts_ul"),
446
	    work_li : by_id("work_script_li"),
447
	    work_name_input : by_id("script_name_field"),
448
	    reset_work_li : reset_work_script_li,
449
	    get_work_li_data : work_script_li_data,
450
	    state : UL_STATE.IDLE,
451
	    edited_item : undefined,
452
	}
453
    }
454

    
455
    async function main()
456
    {
457
	storage = await get_storage();
458

    
459
	for (let prefix of list_prefixes) {
460
	    for (let item of storage.get_all_names(prefix).sort()) {
461
		add_li(prefix, item, true);
462
		add_chbx_li(prefix, item);
463
		add_radio_li(prefix, item);
464
	    }
465

    
466
	    let name = TYPE_NAME[prefix];
467

    
468
	    let add_but = by_id(`add_${name}_but`);
469
	    let discard_but = by_id(`discard_${name}_but`);
470
	    let save_but = by_id(`save_${name}_but`);
471

    
472
	    add_but.addEventListener("click", () => add_new_item(prefix));
473
	    discard_but.addEventListener("click", () => cancel_work(prefix));
474
	    save_but.addEventListener("click", () => save_work(prefix));
475

    
476
	    if (prefix === TYPE_PREFIX.SCRIPT)
477
		continue;
478

    
479
	    let ul = ul_by_prefix[prefix];
480

    
481
	    let commit_components_but = by_id(`commit_${name}_components_but`);
482
	    let cancel_components_but = by_id(`cancel_${name}_components_but`);
483
	    let select_components_but = by_id(`select_${name}_components_but`);
484

    
485
	    commit_components_but
486
		.addEventListener("click", ul.commit_components);
487
	    select_components_but
488
		.addEventListener("click", ul.select_components);
489
	    cancel_components_but.addEventListener("click", cancel_components);
490
	}
491

    
492
	storage.add_change_listener(handle_change);
493
    }
494

    
495
    function handle_change(change)
496
    {
497
	if (change.old_val === undefined) {
498
	    add_li(change.prefix, change.item);
499
	    add_chbx_li(change.prefix, change.item);
500
	    add_radio_li(change.prefix, change.item);
501

    
502
	    return;
503
	}
504

    
505
	if (change.new_val !== undefined)
506
	    return;
507

    
508
	let ul = ul_by_prefix[change.prefix];
509
	if (ul.state === UL_STATE.EDITING_ENTRY &&
510
	    ul.edited_item === change.item) {
511
	    ul.state = UL_STATE.ADDING_ENTRY;
512
	    return;
513
	}
514

    
515
	let uls_creators = [[ul.ul, item_li_id]];
516

    
517
	if (change.prefix !== TYPE_PREFIX.PAGE) {
518
	    uls_creators.push([chbx_components_ul, chbx_li_id]);
519
	    uls_creators.push([radio_components_ul, radio_li_id]);
520
	}
521

    
522
	for (let [components_ul, id_creator] of uls_creators) {
523
	    let li = by_id(id_creator(change.prefix, change.item));
524
	    components_ul.removeChild(li);
525
	}
526
    }
527

    
528
    main();
529
})();
(4-4/4)