Project

General

Profile

« Previous | Next » 

Revision 659f532e

Added by Wojtek Kosior about 2 years ago

add import/export functionality

View differences:

html/options_main.js
37 37
	return document.getElementById(id);
38 38
    }
39 39

  
40
    function nice_name(prefix, name)
41
    {
42
	return `${name} (${TYPE_NAME[prefix]})`;
43
    }
44

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

  
50 57
    function item_li_id(prefix, item)
51 58
    {
......
69 76
	remove_button.addEventListener("click",
70 77
				       () => storage.remove(prefix, item));
71 78

  
79
	let export_button = remove_button.nextElementSibling;
80
	export_button.addEventListener("click",
81
				       () => export_item(prefix, item));
82

  
72 83
	if (!at_the_end) {
73 84
	    for (let element of ul.ul.children) {
74 85
		if (element.id < li.id || element.id.startsWith("work_"))
......
110 121
	let chbx = li.firstElementChild;
111 122
	let span = chbx.nextElementSibling;
112 123

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

  
115 126
	chbx_components_ul.appendChild(li);
116 127
    }
......
130 141
	let radio = li.firstElementChild;
131 142
	let span = radio.nextElementSibling;
132 143

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

  
135 146
	radio_components_ul.insertBefore(li, radio_component_none_li);
136 147
    }
......
147 158
	    let [prefix, name] = components;
148 159
	    page_payload_span.setAttribute("data-prefix", prefix);
149 160
	    page_payload_span.setAttribute("data-name", name);
150
	    page_payload_span.textContent = `${name} (${TYPE_NAME[prefix]})`;
161
	    page_payload_span.textContent = nice_name(prefix, name);
151 162
	}
152 163
    }
153 164

  
......
197 208
	    li.setAttribute("data-prefix", prefix);
198 209
	    li.setAttribute("data-name", name);
199 210
	    let span = li.firstElementChild;
200
	    span.textContent = `${name} (${TYPE_NAME[prefix]})`;
211
	    span.textContent = nice_name(prefix, name);
201 212
	    let remove_but = span.nextElementSibling;
202 213
	    remove_but.addEventListener("click", () =>
203 214
					bag_components_ul.removeChild(li));
......
319 330
	ul.edited_item = item;
320 331
    }
321 332

  
333
    const file_downloader = by_id("file_downloader");
334

  
335
    function recursively_export_item(prefix, name, added_items, items_data)
336
    {
337
	let key = prefix + name;
338

  
339
	if (added_items.has(key))
340
	    return;
341

  
342
	let data = storage.get(prefix, name);
343
	if (data === undefined) {
344
	    console.log(`${TYPE_NAME[prefix]} '${name}' for export not found`);
345
	    return;
346
	}
347

  
348
	if (prefix !== TYPE_PREFIX.SCRIPT) {
349
	    let components = prefix === TYPE_PREFIX.BAG ?
350
		data : [data.components];
351

  
352
	    for (let [comp_prefix, comp_name] of components) {
353
		recursively_export_item(comp_prefix, comp_name,
354
					added_items, items_data);
355
	    }
356
	}
357

  
358
	items_data.push({[key]: data});
359
	added_items.add(key);
360
    }
361

  
362
    function export_item(prefix, name)
363
    {
364
	let added_items = new Set();
365
	let items_data = [];
366
	recursively_export_item(prefix, name, added_items, items_data);
367
	let file = new Blob([JSON.stringify(items_data)],
368
			    {type: "application/json"});
369
	let url = URL.createObjectURL(file);
370
	file_downloader.setAttribute("href", url);
371
	file_downloader.setAttribute("download", prefix + name + ".json");
372
	file_downloader.click();
373
	file_downloader.removeAttribute("href");
374
	URL.revokeObjectURL(url);
375
    }
376

  
322 377
    function add_new_item(prefix)
323 378
    {
324 379
	cancel_work(prefix);
......
452 507
	}
453 508
    }
454 509

  
510
    const import_window = by_id("import_window");
511
    const import_loading_radio = by_id("import_loading_radio");
512
    const import_failed_radio = by_id("import_failed_radio");
513
    const import_selection_radio = by_id("import_selection_radio");
514
    const bad_file_errormsg = by_id("bad_file_errormsg");
515

  
516
    /*
517
     * Newer browsers could utilise `text' method of File objects.
518
     * Older ones require FileReader.
519
     */
520

  
521
    function _read_file(file, resolve, reject)
522
    {
523
	let reader = new FileReader();
524

  
525
	reader.onload = () => resolve(reader.result);
526
	reader.onerror = () => reject(reader.error);
527
	reader.readAsText(file);
528
    }
529

  
530
    function read_file(file)
531
    {
532
	return new Promise((resolve, reject) =>
533
			   _read_file(file, resolve, reject));
534
    }
535

  
536
    async function import_from_file(event)
537
    {
538
	let files = event.target.files;
539
	if (files.length < 1)
540
	    return;
541

  
542
	import_window.classList.remove("hide");
543
	import_loading_radio.checked = true;
544

  
545
	let result = undefined;
546

  
547
	try {
548
	    result = JSON.parse(await read_file(files[0]));
549
	} catch(e) {
550
	    bad_file_errormsg.textContent = "" + e;
551
	    import_failed_radio.checked = true;
552
	    return;
553
	}
554

  
555
	let errormsg = validate_settings(result);
556
	if (errormsg !== false) {
557
	    bad_file_errormsg.textContent = errormsg;
558
	    import_failed_radio.checked = true;
559
	    return;
560
	}
561

  
562
	populate_import_list(result);
563
	import_selection_radio.checked = true;
564
    }
565

  
566
    function validate_settings(settings)
567
    {
568
	// TODO
569
	return false;
570
    }
571

  
572
    function import_li_id(prefix, item)
573
    {
574
	return `ili_${prefix}_${item}`;
575
    }
576

  
577
    let import_ul = by_id("import_ul");
578
    let import_chbxs_colliding = undefined;
579
    let settings_import_map = undefined;
580

  
581
    function populate_import_list(settings)
582
    {
583
	let old_children = import_ul.children;
584
	while (old_children[0] !== undefined)
585
	    import_ul.removeChild(old_children[0]);
586

  
587
	import_chbxs_colliding = [];
588
	settings_import_map = new Map();
589

  
590
	for (let setting of settings) {
591
	    let [key, value] = Object.entries(setting)[0];
592
	    let prefix = key[0];
593
	    let name = key.substring(1);
594
	    add_import_li(prefix, name);
595
	    settings_import_map.set(key, value);
596
	}
597
    }
598

  
599
    function add_import_li(prefix, name)
600
    {
601
	let li = import_li_template.cloneNode(true);
602
	let name_span = li.firstElementChild;
603
	let chbx = name_span.nextElementSibling;
604
	let warning_span = chbx.nextElementSibling;
605

  
606
	li.setAttribute("data-prefix", prefix);
607
	li.setAttribute("data-name", name);
608
	li.id = import_li_id(prefix, name);
609
	name_span.textContent = nice_name(prefix, name);
610

  
611
	if (storage.get(prefix, name) !== undefined) {
612
	    import_chbxs_colliding.push(chbx);
613
	    warning_span.textContent = "(will overwrite existing setting!)";
614
	}
615

  
616
	import_ul.appendChild(li);
617
    }
618

  
619
    function check_all_imports()
620
    {
621
	for (let li of import_ul.children)
622
	    li.firstElementChild.nextElementSibling.checked = true;
623
    }
624

  
625
    function uncheck_all_imports()
626
    {
627
	for (let li of import_ul.children)
628
	    li.firstElementChild.nextElementSibling.checked = false;
629
    }
630

  
631
    function uncheck_colliding_imports()
632
    {
633
	for (let chbx of import_chbxs_colliding)
634
	    chbx.checked = false;
635
    }
636

  
637
    const file_opener_form = by_id("file_opener_form");
638

  
639
    function hide_import_window()
640
    {
641
	import_window.classList.add("hide");
642
	/* Let GC free some memory */
643
	import_chbxs_colliding = undefined;
644
	settings_import_map = undefined;
645

  
646
	/*
647
	 * Reset file <input>. Without this, a second attempt to import the same
648
	 * file would result in "change" event on happening on <input> element.
649
	 */
650
	file_opener_form.reset();
651
    }
652

  
653
    function commit_import()
654
    {
655
	for (let li of import_ul.children) {
656
	    let chbx = li.firstElementChild.nextElementSibling;
657

  
658
	    if (!chbx.checked)
659
		continue;
660

  
661
	    let prefix = li.getAttribute("data-prefix");
662
	    let name = li.getAttribute("data-name");
663
	    let key = prefix + name;
664
	    let value = settings_import_map.get(key);
665
	    storage.set(prefix, name, value);
666
	}
667

  
668
	hide_import_window();
669
    }
670

  
671
    function initialize_import_facility()
672
    {
673
	let import_but = by_id("import_but");
674
	let file_opener = by_id("file_opener");
675
	let import_failok_but = by_id("import_failok_but");
676
	let check_all_import_but = by_id("check_all_import_but");
677
	let uncheck_all_import_but = by_id("uncheck_all_import_but");
678
	let uncheck_existing_import_but = by_id("uncheck_existing_import_but");
679
	let commit_import_but = by_id("commit_import_but");
680
	let cancel_import_but = by_id("cancel_import_but");
681
	import_but.addEventListener("click", () => file_opener.click());
682
	file_opener.addEventListener("change", import_from_file);
683
	import_failok_but.addEventListener("click", hide_import_window);
684
	check_all_import_but.addEventListener("click", check_all_imports);
685
	uncheck_all_import_but.addEventListener("click", uncheck_all_imports);
686
	uncheck_colliding_import_but
687
	    .addEventListener("click", uncheck_colliding_imports);
688
	commit_import_but.addEventListener("click", commit_import);
689
	cancel_import_but.addEventListener("click", hide_import_window);
690
    }
691

  
455 692
    async function main()
456 693
    {
457 694
	storage = await get_storage();
......
489 726
	    cancel_components_but.addEventListener("click", cancel_components);
490 727
	}
491 728

  
729
	initialize_import_facility();
730

  
492 731
	storage.add_change_listener(handle_change);
493 732
    }
494 733

  

Also available in: Unified diff