Project

General

Profile

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

haketilo / html / item_list.js @ 7218849a

1
/**
2
 * This file is part of Haketilo.
3
 *
4
 * Function: Showing a list of resources/mappings in a browser.
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
#IMPORT html/dialog.js
46

    
47
#FROM html/item_preview.js IMPORT resource_preview, mapping_preview
48
#FROM html/DOM_helpers.js IMPORT clone_template
49

    
50
function preview_item(list_ctx, item, ignore_dialog=false)
51
{
52
    if (list_ctx.dialog_ctx.shown && !ignore_dialog)
53
	return;
54

    
55
    list_ctx.preview_ctx = list_ctx.preview_cb(
56
	item.definition,
57
	list_ctx.preview_ctx,
58
	list_ctx.dialog_ctx
59
    );
60
    list_ctx.preview_container.prepend(list_ctx.preview_ctx.main_div);
61

    
62
    if (list_ctx.previewed_item !== null)
63
	list_ctx.previewed_item.li.classList.remove("item_li_highlight");
64

    
65
    list_ctx.previewed_item = item;
66
    item.li.classList.add("item_li_highlight");
67

    
68
    list_ctx.preview_container.classList.remove("hide");
69
}
70

    
71
function insert_item(list_ctx, definition, idx)
72
{
73
    const li = document.createElement("li");
74
    li.innerText = definition.long_name;
75
    if (idx)
76
	list_ctx.items[idx - 1].li.after(li);
77
    else
78
	list_ctx.ul.prepend(li);
79

    
80
    const item = {definition, li};
81
    list_ctx.items.splice(idx, 0, item);
82
    list_ctx.by_identifier.set(definition.identifier, item);
83

    
84
    li.addEventListener("click", () => preview_item(list_ctx, item));
85

    
86
    return item;
87
}
88

    
89
const coll = new Intl.Collator();
90

    
91
function item_cmp(def1, def2)
92
{
93
    return coll.compare(def1.long_name, def2.long_name) ||
94
	coll.compare(def1.identifier, def2.identifier);
95
}
96

    
97
function find_item_idx(list_ctx, definition)
98
{
99
    /* Perform a binary search of item's (new or not) index in sorted array. */
100
    let left = 0, right = list_ctx.items.length;
101

    
102
    while (left < right) {
103
	const mid = (left + right) >> 1;
104
	if (item_cmp(definition, list_ctx.items[mid].definition) > 0)
105
	    left = mid + 1;
106
	else /* <= 0 */
107
	    right = mid;
108
    }
109

    
110
    return left;
111
}
112

    
113
function item_changed(list_ctx, change)
114
{
115
    /* Remove item. */
116
    const old_item = list_ctx.by_identifier.get(change.key);
117
    if (old_item !== undefined) {
118
	list_ctx.items.splice(find_item_idx(list_ctx, old_item.definition), 1);
119
	list_ctx.by_identifier.delete(change.key);
120
	old_item.li.remove();
121

    
122
	if (list_ctx.previewed_item === old_item) {
123
	    list_ctx.preview_container.classList.add("hide");
124
	    list_ctx.previewed_item = null;
125
	}
126
    }
127

    
128
    if (change.new_val === undefined)
129
	return;
130

    
131
    const new_item = insert_item(list_ctx, change.new_val,
132
				 find_item_idx(list_ctx, change.new_val));
133
    if (list_ctx.previewed_item === old_item)
134
	preview_item(list_ctx, new_item, true);
135
}
136

    
137
async function remove_clicked(list_ctx)
138
{
139
    if (list_ctx.dialog_ctx.shown || list_ctx.previewed_item === null)
140
	return;
141

    
142
    const identifier = list_ctx.previewed_item.definition.identifier;
143

    
144
    if (!(await dialog.ask(list_ctx.dialog_ctx,
145
			   `Are you sure you want to delete '${identifier}'?`)))
146
	return;
147

    
148
    try {
149
	await list_ctx.remove_cb(identifier);
150
    } catch(e) {
151
	console.error(e);
152
	dialog.error(list_ctx.dialog_ctx, `Couldn't remove '${identifier}' :(`)
153
    }
154
}
155

    
156
async function item_list(preview_cb, track_cb, remove_cb)
157
{
158
    const list_ctx = clone_template("item_list");
159

    
160
    const [tracking, definitions] =
161
	  await track_cb(ch => item_changed(list_ctx, ch));
162

    
163
    definitions.sort(item_cmp);
164

    
165
    Object.assign(list_ctx, {
166
	items: [],
167
	by_identifier: new Map(),
168
	tracking,
169
	previewed_item: null,
170
	preview_cb,
171
	remove_cb,
172
	dialog_ctx: dialog.make(() => on_dialog_show(list_ctx),
173
				() => on_dialog_hide(list_ctx))
174
    });
175
    list_ctx.dialog_container.append(list_ctx.dialog_ctx.main_div);
176

    
177
    for (const def of definitions)
178
	insert_item(list_ctx, def, list_ctx.items.length);
179

    
180
    list_ctx.remove_but
181
	.addEventListener("click", () => remove_clicked(list_ctx));
182

    
183
    return list_ctx;
184
}
185

    
186
function on_dialog_show(list_ctx)
187
{
188
    list_ctx.ul.classList.add("list_disabled");
189
    list_ctx.preview_container.classList.add("hide");
190
    list_ctx.dialog_container.classList.remove("hide");
191
}
192

    
193
function on_dialog_hide(list_ctx)
194
{
195
    list_ctx.ul.classList.remove("list_disabled");
196
    if (list_ctx.previewed_item !== null)
197
	list_ctx.preview_container.classList.remove("hide");
198
    list_ctx.dialog_container.classList.add("hide");
199
}
200

    
201
async function remove_single_item(item_type, identifier)
202
{
203
    const transaction_ctx =
204
	  await haketilodb.start_items_transaction([item_type], {});
205
    await haketilodb[`remove_${item_type}`](identifier, transaction_ctx);
206
    await haketilodb.finalize_transaction(transaction_ctx);
207
}
208

    
209
function resource_list()
210
{
211
      return item_list(resource_preview, haketilodb.track.resource,
212
		       id => remove_single_item("resource", id));
213
}
214
#EXPORT resource_list
215

    
216
function mapping_list()
217
{
218
      return item_list(mapping_preview, haketilodb.track.mapping,
219
		       id => remove_single_item("mapping", id));
220
}
221
#EXPORT mapping_list
222

    
223
function destroy_list(list_ctx)
224
{
225
    haketilodb.untrack(list_ctx.tracking);
226
    list_ctx.main_div.remove();
227
}
228
#EXPORT destroy_list
(16-16/29)