@panoramax/web-viewer 3.0.2-develop-a8ea8e60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +6 -0
- package/.gitlab-ci.yml +71 -0
- package/CHANGELOG.md +428 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/Dockerfile +14 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/build/editor.html +1 -0
- package/build/index.css +36 -0
- package/build/index.css.map +1 -0
- package/build/index.html +1 -0
- package/build/index.js +25 -0
- package/build/index.js.map +1 -0
- package/build/map.html +1 -0
- package/build/viewer.html +1 -0
- package/config/env.js +104 -0
- package/config/getHttpsConfig.js +66 -0
- package/config/getPackageJson.js +25 -0
- package/config/jest/babelTransform.js +29 -0
- package/config/jest/cssTransform.js +14 -0
- package/config/jest/fileTransform.js +40 -0
- package/config/modules.js +134 -0
- package/config/paths.js +72 -0
- package/config/pnpTs.js +35 -0
- package/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
- package/config/webpack.config.js +885 -0
- package/config/webpackDevServer.config.js +127 -0
- package/docs/01_Start.md +149 -0
- package/docs/02_Usage.md +828 -0
- package/docs/03_URL_settings.md +140 -0
- package/docs/04_Advanced_examples.md +214 -0
- package/docs/05_Compatibility.md +85 -0
- package/docs/09_Develop.md +62 -0
- package/docs/90_Releases.md +27 -0
- package/docs/images/class_diagram.drawio +129 -0
- package/docs/images/class_diagram.jpg +0 -0
- package/docs/images/screenshot.jpg +0 -0
- package/mkdocs.yml +45 -0
- package/package.json +254 -0
- package/public/editor.html +54 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +59 -0
- package/public/map.html +53 -0
- package/public/viewer.html +67 -0
- package/scripts/build.js +217 -0
- package/scripts/start.js +176 -0
- package/scripts/test.js +52 -0
- package/src/Editor.css +37 -0
- package/src/Editor.js +359 -0
- package/src/StandaloneMap.js +114 -0
- package/src/Viewer.css +203 -0
- package/src/Viewer.js +1186 -0
- package/src/components/CoreView.css +64 -0
- package/src/components/CoreView.js +159 -0
- package/src/components/Loader.css +56 -0
- package/src/components/Loader.js +111 -0
- package/src/components/Map.css +65 -0
- package/src/components/Map.js +841 -0
- package/src/components/Photo.css +36 -0
- package/src/components/Photo.js +687 -0
- package/src/img/arrow_360.svg +14 -0
- package/src/img/arrow_flat.svg +11 -0
- package/src/img/arrow_triangle.svg +10 -0
- package/src/img/arrow_turn.svg +9 -0
- package/src/img/bg_aerial.jpg +0 -0
- package/src/img/bg_streets.jpg +0 -0
- package/src/img/loader_base.jpg +0 -0
- package/src/img/loader_hd.jpg +0 -0
- package/src/img/logo_dead.svg +91 -0
- package/src/img/marker.svg +17 -0
- package/src/img/marker_blue.svg +20 -0
- package/src/img/switch_big.svg +44 -0
- package/src/img/switch_mini.svg +48 -0
- package/src/index.js +10 -0
- package/src/translations/de.json +163 -0
- package/src/translations/en.json +164 -0
- package/src/translations/eo.json +6 -0
- package/src/translations/es.json +164 -0
- package/src/translations/fi.json +1 -0
- package/src/translations/fr.json +164 -0
- package/src/translations/hu.json +133 -0
- package/src/translations/nl.json +1 -0
- package/src/translations/zh_Hant.json +136 -0
- package/src/utils/API.js +709 -0
- package/src/utils/Exif.js +198 -0
- package/src/utils/I18n.js +75 -0
- package/src/utils/Map.js +382 -0
- package/src/utils/PhotoAdapter.js +45 -0
- package/src/utils/Utils.js +568 -0
- package/src/utils/Widgets.js +477 -0
- package/src/viewer/URLHash.js +334 -0
- package/src/viewer/Widgets.css +711 -0
- package/src/viewer/Widgets.js +1196 -0
- package/tests/Editor.test.js +125 -0
- package/tests/StandaloneMap.test.js +44 -0
- package/tests/Viewer.test.js +363 -0
- package/tests/__snapshots__/Editor.test.js.snap +300 -0
- package/tests/__snapshots__/StandaloneMap.test.js.snap +30 -0
- package/tests/__snapshots__/Viewer.test.js.snap +195 -0
- package/tests/components/CoreView.test.js +91 -0
- package/tests/components/Loader.test.js +38 -0
- package/tests/components/Map.test.js +230 -0
- package/tests/components/Photo.test.js +335 -0
- package/tests/components/__snapshots__/Loader.test.js.snap +15 -0
- package/tests/components/__snapshots__/Map.test.js.snap +767 -0
- package/tests/components/__snapshots__/Photo.test.js.snap +205 -0
- package/tests/data/Map_geocoder_ban.json +36 -0
- package/tests/data/Map_geocoder_nominatim.json +56 -0
- package/tests/data/Viewer_pictures_1.json +148 -0
- package/tests/setupTests.js +5 -0
- package/tests/utils/API.test.js +906 -0
- package/tests/utils/Exif.test.js +124 -0
- package/tests/utils/I18n.test.js +28 -0
- package/tests/utils/Map.test.js +105 -0
- package/tests/utils/Utils.test.js +300 -0
- package/tests/utils/Widgets.test.js +107 -0
- package/tests/utils/__snapshots__/API.test.js.snap +132 -0
- package/tests/utils/__snapshots__/Exif.test.js.snap +43 -0
- package/tests/utils/__snapshots__/Map.test.js.snap +48 -0
- package/tests/utils/__snapshots__/Utils.test.js.snap +41 -0
- package/tests/utils/__snapshots__/Widgets.test.js.snap +44 -0
- package/tests/viewer/URLHash.test.js +537 -0
- package/tests/viewer/Widgets.test.js +127 -0
- package/tests/viewer/__snapshots__/URLHash.test.js.snap +98 -0
- package/tests/viewer/__snapshots__/Widgets.test.js.snap +393 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// Every single icon imported separately to reduce bundle size
|
|
2
|
+
import { icon } from "@fortawesome/fontawesome-svg-core";
|
|
3
|
+
import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
|
|
4
|
+
import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons/faCircleExclamation";
|
|
5
|
+
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
|
6
|
+
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons/faMagnifyingGlass";
|
|
7
|
+
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
|
8
|
+
import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck";
|
|
9
|
+
import { faCopy } from "@fortawesome/free-solid-svg-icons/faCopy";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new button, already styled
|
|
14
|
+
* @param {string} id The component ID
|
|
15
|
+
* @param {string|Element} content The text content
|
|
16
|
+
* @param {string} [title] A title label on overlay
|
|
17
|
+
* @param {string[]} [classes] List of CSS classes to add
|
|
18
|
+
* @returns {Element} The created button
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
export function createButton(id, content = null, title = null, classes = []) {
|
|
22
|
+
const btn = document.createElement("button");
|
|
23
|
+
if(content) {
|
|
24
|
+
if(content instanceof HTMLElement || content instanceof Node) {
|
|
25
|
+
btn.appendChild(content);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
btn.innerHTML = content;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
btn.id = id;
|
|
32
|
+
if(Array.isArray(classes)) {
|
|
33
|
+
classes = classes.filter(c => c != null && c.length > 0);
|
|
34
|
+
}
|
|
35
|
+
btn.classList.add("gvs-btn", "gvs-widget-bg", ...classes);
|
|
36
|
+
if(title) { btn.title = title; }
|
|
37
|
+
return btn;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new "expandable" button, already styled
|
|
42
|
+
* @param {string} id The component ID
|
|
43
|
+
* @param {object} icon The FontAwesome icon definition
|
|
44
|
+
* @param {string} label The label text
|
|
45
|
+
* @param {Widgets} container The widgets container
|
|
46
|
+
* @param {string[]} [classes] List of CSS classes to add
|
|
47
|
+
* @returns {Element} The created button
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
export function createExpandableButton(id, icon, label, container, classes = []) {
|
|
51
|
+
const btn = document.createElement("button");
|
|
52
|
+
btn.id = id;
|
|
53
|
+
btn.appendChild(fa(icon));
|
|
54
|
+
if(!container._viewer.isWidthSmall()) {
|
|
55
|
+
btn.appendChild(document.createTextNode(label));
|
|
56
|
+
btn.appendChild(fa(faChevronDown));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
btn.title = label;
|
|
60
|
+
}
|
|
61
|
+
btn.classList.add("gvs-btn", "gvs-widget-bg", "gvs-btn-expandable", ...classes);
|
|
62
|
+
return btn;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new search bar
|
|
67
|
+
* @param {string} id The bar ID
|
|
68
|
+
* @param {string} placeholder The default label to display when search field is empty
|
|
69
|
+
* @param {function} onInput Event handler for search text input (should return a Promise)
|
|
70
|
+
* @param {function} onResultClick Event handler for result entry click
|
|
71
|
+
* @param {Widgets} container The widgets container
|
|
72
|
+
* @param {boolean} [nonClosingPanel] Should the search result closes other panels
|
|
73
|
+
* @param {boolean} [reduced] Should the search bar be reduced by default ?
|
|
74
|
+
* @param {Element} [preContent] DOM element to insert before search input
|
|
75
|
+
* @returns {Element} The search bar
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
export function createSearchBar(
|
|
79
|
+
id, placeholder, onInput, onResultClick,
|
|
80
|
+
container, nonClosingPanel = false, reduced = false,
|
|
81
|
+
preContent = null,
|
|
82
|
+
) {
|
|
83
|
+
// Container
|
|
84
|
+
const bar = document.createElement("div");
|
|
85
|
+
bar.classList.add("gvs-widget-bg", "gvs-search-bar");
|
|
86
|
+
bar.id = id;
|
|
87
|
+
if(reduced) { bar.classList.add("gvs-search-bar-reducable"); }
|
|
88
|
+
|
|
89
|
+
// Pre-content
|
|
90
|
+
if(preContent) {
|
|
91
|
+
bar.appendChild(preContent);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Input field
|
|
95
|
+
const input = document.createElement("input");
|
|
96
|
+
input.type = "text";
|
|
97
|
+
input.placeholder = placeholder;
|
|
98
|
+
bar.appendChild(input);
|
|
99
|
+
const extendInput = () => {
|
|
100
|
+
bar.classList.remove("gvs-search-bar-reduced");
|
|
101
|
+
};
|
|
102
|
+
const reduceInput = () => {
|
|
103
|
+
bar.classList.add("gvs-search-bar-reduced");
|
|
104
|
+
};
|
|
105
|
+
if(reduced) { reduceInput(); }
|
|
106
|
+
|
|
107
|
+
// Status icon
|
|
108
|
+
const icon = document.createElement("span");
|
|
109
|
+
icon.classList.add("gvs-search-bar-icon");
|
|
110
|
+
const iconSearch = fa(faMagnifyingGlass);
|
|
111
|
+
const iconLoading = fa(faCircleNotch, { classes: ["fa-spin"] });
|
|
112
|
+
const iconEmpty = fa(faXmark);
|
|
113
|
+
const iconWarn = fa(faCircleExclamation);
|
|
114
|
+
icon.appendChild(iconSearch);
|
|
115
|
+
bar.appendChild(icon);
|
|
116
|
+
|
|
117
|
+
// List of results
|
|
118
|
+
const list = createPanel(container, bar, [], ["gvs-search-bar-results"], nonClosingPanel);
|
|
119
|
+
bar.appendChild(list);
|
|
120
|
+
|
|
121
|
+
// Change status icon
|
|
122
|
+
const switchIcon = newStatusIcon => {
|
|
123
|
+
icon.innerHTML = "";
|
|
124
|
+
icon.appendChild(newStatusIcon);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Reset search bar
|
|
128
|
+
const resetSearch = () => {
|
|
129
|
+
if(bar._throttler) { clearTimeout(bar._throttler); }
|
|
130
|
+
input.value = "";
|
|
131
|
+
list.innerHTML = "";
|
|
132
|
+
list._toggle(false);
|
|
133
|
+
delete bar._lastSearch;
|
|
134
|
+
switchIcon(iconSearch);
|
|
135
|
+
onResultClick(null);
|
|
136
|
+
if(reduced) { reduceInput(); }
|
|
137
|
+
};
|
|
138
|
+
bar.resetSearch = resetSearch;
|
|
139
|
+
|
|
140
|
+
// Handle result item click
|
|
141
|
+
const goItem = (entry) => {
|
|
142
|
+
if(reduced) {
|
|
143
|
+
onResultClick(entry);
|
|
144
|
+
resetSearch();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if(bar._throttler) { clearTimeout(bar._throttler); }
|
|
148
|
+
input.value = entry.title;
|
|
149
|
+
list.innerHTML = "";
|
|
150
|
+
list._toggle(false);
|
|
151
|
+
onResultClick(entry);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Force item selection
|
|
156
|
+
input.setItem = (text) => {
|
|
157
|
+
if(bar._throttler) { clearTimeout(bar._throttler); }
|
|
158
|
+
input.value = text;
|
|
159
|
+
list.innerHTML = "";
|
|
160
|
+
list._toggle(false);
|
|
161
|
+
switchIcon(iconEmpty);
|
|
162
|
+
if(reduced) { extendInput(); }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Handle search
|
|
166
|
+
const goSearch = () => {
|
|
167
|
+
if(bar._throttler) { clearTimeout(bar._throttler); }
|
|
168
|
+
|
|
169
|
+
if(input.value.length === 0) {
|
|
170
|
+
list.innerHTML = "";
|
|
171
|
+
list._toggle(false);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
bar._throttler = setTimeout(() => {
|
|
176
|
+
list.innerHTML = "";
|
|
177
|
+
list._toggle(false);
|
|
178
|
+
switchIcon(iconLoading);
|
|
179
|
+
|
|
180
|
+
onInput(input.value).then(data => {
|
|
181
|
+
switchIcon(iconEmpty);
|
|
182
|
+
list._toggle(true);
|
|
183
|
+
|
|
184
|
+
if(!data || data.length == 0) {
|
|
185
|
+
list.innerHTML = `<div class="gvs-search-empty">${container._t.gvs.search_empty}</li>`;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
data.forEach(entry => {
|
|
190
|
+
const listEntry = document.createElement("div");
|
|
191
|
+
listEntry.classList.add("gvs-search-bar-result");
|
|
192
|
+
listEntry.innerHTML = `${entry.title}<br /><small>${entry?.subtitle || ""}</small>`;
|
|
193
|
+
list.appendChild(listEntry);
|
|
194
|
+
listEntry.addEventListener("click", () => goItem(entry));
|
|
195
|
+
});
|
|
196
|
+
}).catch(e => {
|
|
197
|
+
console.error(e);
|
|
198
|
+
switchIcon(iconWarn);
|
|
199
|
+
});
|
|
200
|
+
}, 250);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
input.addEventListener("change", goSearch);
|
|
204
|
+
input.addEventListener("keypress", goSearch);
|
|
205
|
+
input.addEventListener("paste", goSearch);
|
|
206
|
+
input.addEventListener("input", goSearch);
|
|
207
|
+
icon.addEventListener("click", () => {
|
|
208
|
+
if(icon.firstChild == iconEmpty || icon.firstChild == iconWarn) {
|
|
209
|
+
resetSearch();
|
|
210
|
+
}
|
|
211
|
+
if(reduced && icon.firstChild == iconSearch) {
|
|
212
|
+
if(!bar.classList.contains("gvs-search-bar-reduced")) { reduceInput(); }
|
|
213
|
+
else { extendInput(); }
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return bar;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Creates a panel associated to a button
|
|
222
|
+
* @param {Widgets} container The widgets container
|
|
223
|
+
* @param {Element} btn The component to associate to
|
|
224
|
+
* @param {Element[]} [elements] DOM elements to append into
|
|
225
|
+
* @param {str[]} [classes] CSS classes to add
|
|
226
|
+
* @param {boolean} [nonClosingPanel] Should this panel closes other when opened
|
|
227
|
+
* @returns {Element} The created panel
|
|
228
|
+
* @private
|
|
229
|
+
*/
|
|
230
|
+
export function createPanel(container, btn, elements = [], classes = [], nonClosingPanel = false) {
|
|
231
|
+
const panel = document.createElement("div");
|
|
232
|
+
panel.id = btn.id + "-panel";
|
|
233
|
+
if(Array.isArray(classes)) {
|
|
234
|
+
classes = classes.filter(c => c != null && c.length > 0);
|
|
235
|
+
}
|
|
236
|
+
panel.classList.add("gvs-panel", "gvs-widget-bg", "gvs-hidden", ...classes);
|
|
237
|
+
for(let e of elements) {
|
|
238
|
+
panel.appendChild(e);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const togglePanel = (e, visible) => {
|
|
242
|
+
if(e) { e.stopPropagation(); }
|
|
243
|
+
if(visible === true) { panel.classList.remove("gvs-hidden"); }
|
|
244
|
+
else if(visible === false) { panel.classList.add("gvs-hidden"); }
|
|
245
|
+
else {
|
|
246
|
+
panel.classList.toggle("gvs-hidden");
|
|
247
|
+
visible = !panel.classList.contains("gvs-hidden");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Hide all other panels
|
|
251
|
+
if(visible && !nonClosingPanel) {
|
|
252
|
+
closeOtherPanels(panel, container._viewer.container);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
panel._toggle = v => togglePanel(null, v);
|
|
256
|
+
|
|
257
|
+
if(btn.tagName == "BUTTON") {
|
|
258
|
+
btn.addEventListener("click", togglePanel);
|
|
259
|
+
btn.addEventListener("hover", togglePanel);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return panel;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Makes all previously opened panels closed if clicked outside of one.
|
|
267
|
+
* @param {Element} target The DOM element which has been clicked
|
|
268
|
+
* @param {Element} container The viewer container
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
export function closeOtherPanels(target, container) {
|
|
272
|
+
const isPanel = p => (
|
|
273
|
+
p.classList.contains("gvs-panel")
|
|
274
|
+
|| p.classList.contains("gvs-search-bar-result")
|
|
275
|
+
|| p.classList.contains("gvs-search-empty")
|
|
276
|
+
|| p.classList.contains("gvs-search-bar-reducable")
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Find nearest panel
|
|
280
|
+
if(!isPanel(target) && target?.parentNode) {
|
|
281
|
+
target = target.parentNode;
|
|
282
|
+
while(target instanceof Element) {
|
|
283
|
+
if(isPanel(target)) { break; }
|
|
284
|
+
else { target = target.parentNode; }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Click outside of open panel = closing
|
|
289
|
+
for(const p of container.getElementsByClassName("gvs-panel")) {
|
|
290
|
+
if(p != target && !p.contains(target) && !p.classList.contains("gvs-hidden")) {
|
|
291
|
+
p.classList.add("gvs-hidden");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for(const p of container.getElementsByClassName("gvs-search-bar-reducable")) {
|
|
295
|
+
if(p != target && !p.contains(target) && !p.classList.contains("gvs-search-bar-reduced")) {
|
|
296
|
+
p.resetSearch();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Creates a new group of elements, already styled
|
|
303
|
+
* @param {str} id
|
|
304
|
+
* @param {str} position (format: component-corner, with component = main/mini, and corner = top-left, top-right, top, bottom-left, bottom, bottom-right)
|
|
305
|
+
* @param {Element[]} [elements] The children elements to add
|
|
306
|
+
* @param {str[]} [classes] The CSS classes to add
|
|
307
|
+
* @returns {Element} The created group
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
export function createGroup(id, position, container, elements = [], classes = []) {
|
|
311
|
+
const group = document.createElement("div");
|
|
312
|
+
group.id = id;
|
|
313
|
+
if(Array.isArray(classes)) {
|
|
314
|
+
classes = classes.filter(c => c != null && c.length > 0);
|
|
315
|
+
}
|
|
316
|
+
group.classList.add("gvs-group", ...classes);
|
|
317
|
+
for(let e of elements) {
|
|
318
|
+
group.appendChild(e);
|
|
319
|
+
}
|
|
320
|
+
container._corners[position].appendChild(group);
|
|
321
|
+
return group;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Make all buttons with data-copy=* or data-input=* attributes able to copy to clipboard.
|
|
326
|
+
*
|
|
327
|
+
* @param {Element} container The parent container
|
|
328
|
+
* @param {object} t The translation container
|
|
329
|
+
* @private
|
|
330
|
+
*/
|
|
331
|
+
export function enableCopyButton(container, t) {
|
|
332
|
+
for(let btn of container.getElementsByTagName("button")) {
|
|
333
|
+
const field = btn.getAttribute("data-input");
|
|
334
|
+
const copy = btn.getAttribute("data-copy");
|
|
335
|
+
if(field || copy) {
|
|
336
|
+
btn.addEventListener("click", () => {
|
|
337
|
+
let text;
|
|
338
|
+
if(field) {
|
|
339
|
+
const inputField = document.getElementById(field);
|
|
340
|
+
text = inputField.innerText || inputField.value;
|
|
341
|
+
}
|
|
342
|
+
else if(copy) {
|
|
343
|
+
text = btn.getAttribute("data-copy");
|
|
344
|
+
}
|
|
345
|
+
navigator.clipboard.writeText(text);
|
|
346
|
+
const btnOrigContent = btn.innerHTML;
|
|
347
|
+
btn.innerHTML = `${t.gvs.copied} ${fat(faCheck)}`;
|
|
348
|
+
btn.classList.add("gvs-btn-active");
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
btn.innerHTML = btnOrigContent;
|
|
351
|
+
btn.classList.remove("gvs-btn-active");
|
|
352
|
+
}, 2000);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Make a button usable
|
|
360
|
+
* @param {Element} btn
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
export function enableButton(btn) {
|
|
364
|
+
btn.removeAttribute("disabled");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Make a button unusable
|
|
369
|
+
* @param {Element} btn
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
export function disableButton(btn) {
|
|
373
|
+
btn.setAttribute("disabled", "");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Transform Font Awesome icon definition into HTML element
|
|
378
|
+
* @param {IconDefinition} i The icon to use
|
|
379
|
+
* @param {object} [o] [FontAwesome icon parameters](https://origin.fontawesome.com/docs/apis/javascript/methods#icon-icondefinition-params)
|
|
380
|
+
* @returns {Element} HTML element
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
export function fa(i, o) {
|
|
384
|
+
return icon(i, o).node[0];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Transform Font Awesome icon definition into HTML text
|
|
389
|
+
* @param {IconDefinition} i The icon to use
|
|
390
|
+
* @param {object} [o] [FontAwesome icon parameters](https://origin.fontawesome.com/docs/apis/javascript/methods#icon-icondefinition-params)
|
|
391
|
+
* @returns {string} HTML element as text
|
|
392
|
+
* @private
|
|
393
|
+
*/
|
|
394
|
+
export function fat(i, o) {
|
|
395
|
+
return icon(i, o).html[0];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Table cell with a copy link
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
export function createLinkCell(id, url, title, buttonTitle) {
|
|
403
|
+
const link = document.createElement("a");
|
|
404
|
+
link.href = url;
|
|
405
|
+
link.target = "_blank";
|
|
406
|
+
link.title = title;
|
|
407
|
+
link.textContent = id;
|
|
408
|
+
|
|
409
|
+
const buttonContainer = createButtonSpan(`${fat(faCopy)} ${buttonTitle}`, id);
|
|
410
|
+
return [link, buttonContainer];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Create a light table
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
export function createTable(className, rows) {
|
|
418
|
+
const table = document.createElement("table");
|
|
419
|
+
table.className = className;
|
|
420
|
+
|
|
421
|
+
rows.forEach(({ section, value, values, classes }) => {
|
|
422
|
+
const tr = document.createElement("tr");
|
|
423
|
+
const th = document.createElement("th");
|
|
424
|
+
th.scope = "row";
|
|
425
|
+
th.textContent = section;
|
|
426
|
+
tr.appendChild(th);
|
|
427
|
+
|
|
428
|
+
const td = document.createElement("td");
|
|
429
|
+
if(classes) { td.classList.add(...classes); }
|
|
430
|
+
if(values) { values.forEach(v => td.appendChild(v)); }
|
|
431
|
+
else if(value instanceof HTMLElement) { td.appendChild(value); }
|
|
432
|
+
else { td.innerHTML = value; }
|
|
433
|
+
tr.appendChild(td);
|
|
434
|
+
|
|
435
|
+
table.appendChild(tr);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
return table;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Create block header
|
|
443
|
+
* @private
|
|
444
|
+
*/
|
|
445
|
+
export function createHeader(tag, innerHTML) {
|
|
446
|
+
const header = document.createElement(tag);
|
|
447
|
+
header.innerHTML = innerHTML;
|
|
448
|
+
return header;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Create copy to clipboard button
|
|
453
|
+
* @private
|
|
454
|
+
*/
|
|
455
|
+
export function createButtonSpan(innerHTML, dataCopy = null) {
|
|
456
|
+
const button = document.createElement("button");
|
|
457
|
+
button.innerHTML = innerHTML;
|
|
458
|
+
if (dataCopy) button.setAttribute("data-copy", dataCopy);
|
|
459
|
+
|
|
460
|
+
const span = document.createElement("span");
|
|
461
|
+
span.className = "gvs-input-btn";
|
|
462
|
+
span.appendChild(button);
|
|
463
|
+
|
|
464
|
+
return span;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Create an input label
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
export function createLabel(forAttr, text, faIcon = null) {
|
|
472
|
+
const label = document.createElement("label");
|
|
473
|
+
label.htmlFor = forAttr;
|
|
474
|
+
if(faIcon) { label.appendChild(fa(faIcon)); }
|
|
475
|
+
label.appendChild(document.createTextNode(text));
|
|
476
|
+
return label;
|
|
477
|
+
}
|