@eodash/eodash 5.2.0 → 5.3.1

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.
Files changed (119) hide show
  1. package/core/client/components/DashboardLayout.vue +0 -1
  2. package/core/client/composables/index.js +53 -59
  3. package/core/client/eodashSTAC/EodashCollection.js +196 -94
  4. package/core/client/eodashSTAC/auth.js +86 -0
  5. package/core/client/eodashSTAC/createLayers.js +204 -4
  6. package/core/client/eodashSTAC/helpers.js +254 -62
  7. package/core/client/eodashSTAC/parquet.js +0 -13
  8. package/core/client/eodashSTAC/triggers.js +1 -1
  9. package/core/client/store/actions.js +14 -0
  10. package/core/client/store/stac.js +46 -8
  11. package/core/client/store/states.js +6 -0
  12. package/core/client/types.ts +206 -3
  13. package/core/client/utils/bands-editor/arithmetic.js +144 -0
  14. package/core/client/utils/bands-editor/colors.js +36 -0
  15. package/core/client/utils/bands-editor/dom.js +196 -0
  16. package/core/client/utils/bands-editor/exampleSchema.json +1320 -0
  17. package/core/client/utils/bands-editor/index.js +68 -0
  18. package/core/client/utils/bands-editor/rgb.js +102 -0
  19. package/core/client/utils/index.js +5 -2
  20. package/core/client/views/Dashboard.vue +1 -1
  21. package/core/client/vite-env.d.ts +122 -0
  22. package/dist/client/{DashboardLayout-Dq9Kfe6O.js → DashboardLayout-BAstYnhU.js} +4 -5
  23. package/dist/client/{DynamicWebComponent-DCBMXskE.js → DynamicWebComponent-7v4_DFqP.js} +1 -1
  24. package/dist/client/{EodashDatePicker-DtngxU6s.js → EodashDatePicker-IVHLv9UN.js} +20 -22
  25. package/dist/client/{EodashItemFilter-ClQebJQt.js → EodashItemFilter-BPMpnXjo.js} +46 -31
  26. package/dist/client/EodashLayerControl-CSnQh2tb.js +1517 -0
  27. package/dist/client/{EodashLayoutSwitcher-DQ8SfVDd.js → EodashLayoutSwitcher-CPpGM8Pb.js} +4 -4
  28. package/dist/client/EodashMapBtns-C_jyUJ2x.js +301 -0
  29. package/dist/client/{EodashStacInfo-Dt1nF06x.js → EodashStacInfo-DjuWc0Iz.js} +1 -1
  30. package/dist/client/EodashTimeSlider-CDh9Lf02.js +53 -0
  31. package/dist/client/{EodashTools-DV5ykmWc.js → EodashTools-DSvDUUlL.js} +10 -7
  32. package/dist/client/{ExportState-B6zZQUmE.js → ExportState-BhjxS0jG.js} +145 -120
  33. package/dist/client/{Footer-DNhXs8k6.js → Footer-C3PPcdjv.js} +1 -1
  34. package/dist/client/{Header-BjhN5JY4.js → Header-E5NbT7HE.js} +2 -2
  35. package/dist/client/MobileLayout-DY7OHr1k.js +118 -0
  36. package/dist/client/{PopUp-CgpvNr3o.js → PopUp-CSPXdqKI.js} +79 -43
  37. package/dist/client/{ProcessList-vecpxThi.js → ProcessList-C3HV7G0b.js} +5 -6
  38. package/dist/client/{VImg-CETuikH2.js → VImg-FoXcOnWF.js} +6 -3
  39. package/dist/client/{VMain-Ci9DyaGU.js → VMain-Ck2g1QOG.js} +1 -1
  40. package/dist/client/{VTooltip-J4ac48X7.js → VTooltip-F_1Zcvhp.js} +2 -2
  41. package/dist/client/{WidgetsContainer-CCML4TyV.js → WidgetsContainer-Cq9uZEuN.js} +1 -1
  42. package/dist/client/asWebComponent-DZeEbWG0.js +8895 -0
  43. package/dist/client/{async-B7jIrM53.js → async-Dk79llLt.js} +2 -2
  44. package/dist/client/easing-CH0-9wR8.js +35 -0
  45. package/dist/client/eo-dash.js +1 -1
  46. package/dist/client/{forwardRefs-BQclvjMq.js → forwardRefs-BbvoXHtj.js} +58 -45
  47. package/dist/client/{handling-BS24aG1q.js → handling-DxucYlYh.js} +12 -6
  48. package/dist/client/{helpers-wXK7Ywio.js → helpers-CI_7CUmn.js} +568 -281
  49. package/dist/client/index-BO5uGfUe.js +571 -0
  50. package/dist/client/{index-9KR-G20t.js → index-C13BiO9C.js} +2 -2
  51. package/dist/client/{index-4UCzZi8B.js → index-DcCcdbgR.js} +26 -13
  52. package/dist/client/{index-B2XpdgR6.js → index-KrGHjH-_.js} +63 -36
  53. package/dist/client/templates.js +82 -15
  54. package/dist/client/{transition-yBii4fu6.js → transition-Ctkv90El.js} +1 -1
  55. package/dist/node/cli.js +6 -6
  56. package/dist/types/core/client/eodashSTAC/EodashCollection.d.ts +24 -10
  57. package/dist/types/core/client/eodashSTAC/auth.d.ts +7 -0
  58. package/dist/types/core/client/eodashSTAC/createLayers.d.ts +15 -3
  59. package/dist/types/core/client/eodashSTAC/helpers.d.ts +47 -16
  60. package/dist/types/core/client/plugins/vuetify.d.ts +14 -14
  61. package/dist/types/core/client/store/actions.d.ts +2 -0
  62. package/dist/types/core/client/store/stac.d.ts +16 -7
  63. package/dist/types/core/client/store/states.d.ts +4 -0
  64. package/dist/types/core/client/types.d.ts +170 -2
  65. package/dist/types/core/client/utils/bands-editor/arithmetic.d.ts +8 -0
  66. package/dist/types/core/client/utils/bands-editor/colors.d.ts +15 -0
  67. package/dist/types/core/client/utils/bands-editor/dom.d.ts +42 -0
  68. package/dist/types/core/client/utils/bands-editor/index.d.ts +20 -0
  69. package/dist/types/core/client/utils/bands-editor/rgb.d.ts +15 -0
  70. package/dist/types/core/client/utils/index.d.ts +1 -1
  71. package/dist/types/templates/baseConfig.d.ts +87 -1
  72. package/dist/types/templates/expert.d.ts +6 -6
  73. package/dist/types/templates/explore.d.ts +67 -0
  74. package/dist/types/templates/index.d.ts +1 -1
  75. package/dist/types/templates/{light.d.ts → lite.d.ts} +5 -5
  76. package/dist/types/widgets/EodashItemCatalog/index.vue.d.ts +21 -0
  77. package/dist/types/widgets/EodashItemCatalog/methods/filters.d.ts +49 -0
  78. package/dist/types/widgets/EodashItemCatalog/methods/handlers.d.ts +4 -0
  79. package/dist/types/widgets/EodashItemCatalog/methods/map.d.ts +12 -0
  80. package/dist/types/widgets/EodashItemCatalog/types.d.ts +14 -0
  81. package/dist/types/widgets/EodashMap/EodashMapBtns.vue.d.ts +2 -0
  82. package/dist/types/widgets/EodashMap/index.vue.d.ts +108 -2
  83. package/dist/types/widgets/EodashMap/methods/create-layers-config.d.ts +1 -1
  84. package/dist/types/widgets/EodashMap/methods/index.d.ts +1 -1
  85. package/dist/types/widgets/EodashProcess/methods/custom-endpoints/layers/eoxhub-workspaces-endpoint.d.ts +1 -1
  86. package/dist/types/widgets/EodashTimeSlider.vue.d.ts +7 -0
  87. package/dist/types/widgets/EodashTools.vue.d.ts +10 -10
  88. package/dist/types/widgets/ExportState.vue.d.ts +2 -0
  89. package/package.json +28 -27
  90. package/templates/baseConfig.js +10 -5
  91. package/templates/compare.js +2 -2
  92. package/templates/expert.js +5 -5
  93. package/templates/explore.js +62 -0
  94. package/templates/index.js +1 -1
  95. package/templates/{light.js → lite.js} +1 -1
  96. package/widgets/EodashDatePicker.vue +15 -18
  97. package/widgets/EodashItemCatalog/index.vue +161 -0
  98. package/widgets/EodashItemCatalog/methods/filters.js +216 -0
  99. package/widgets/EodashItemCatalog/methods/handlers.js +50 -0
  100. package/widgets/EodashItemCatalog/methods/map.js +144 -0
  101. package/widgets/EodashItemCatalog/types.ts +15 -0
  102. package/widgets/EodashItemFilter.vue +35 -28
  103. package/widgets/EodashLayerControl.vue +10 -6
  104. package/widgets/EodashLayoutSwitcher.vue +1 -1
  105. package/widgets/EodashMap/EodashMapBtns.vue +18 -9
  106. package/widgets/EodashMap/index.vue +22 -12
  107. package/widgets/EodashMap/methods/create-layers-config.js +9 -6
  108. package/widgets/EodashMap/methods/index.js +27 -13
  109. package/widgets/EodashProcess/index.vue +17 -1
  110. package/widgets/EodashProcess/methods/custom-endpoints/chart/veda-endpoint.js +9 -3
  111. package/widgets/EodashProcess/methods/handling.js +2 -0
  112. package/widgets/EodashProcess/methods/outputs.js +1 -0
  113. package/widgets/EodashTimeSlider.vue +40 -0
  114. package/widgets/EodashTools.vue +7 -3
  115. package/widgets/ExportState.vue +53 -22
  116. package/dist/client/EodashLayerControl-BLBds28C.js +0 -154
  117. package/dist/client/EodashMapBtns-B89_YBDw.js +0 -326
  118. package/dist/client/MobileLayout-JelB6w1G.js +0 -118
  119. package/dist/client/asWebComponent-ZyEzWOOf.js +0 -19092
@@ -0,0 +1,1517 @@
1
+ import { withAsyncContext, computed, ref, createElementBlock, openBlock, createCommentVNode, mergeProps, unref, renderSlot, createElementVNode, toDisplayString } from 'vue';
2
+ import 'color-legend-element';
3
+ import '@eox/timecontrol';
4
+ import { M as mapCompareEl, h as mapEl, N as getColFromLayer } from './helpers-CI_7CUmn.js';
5
+ import { _ as _export_sfc, E as useSTAcStore, M as layerControlFormValueCompare, N as layerControlFormValue, J as eodashCompareCollections, I as eodashCollections } from './asWebComponent-DZeEbWG0.js';
6
+ import { storeToRefs } from 'pinia';
7
+
8
+ /**
9
+ * Taken from jQuery 2.1.3
10
+ *
11
+ * #### NOTE
12
+ * Not plain objects is,
13
+ * - Any object or value whose internal [[Class]] property is not "[object Object]"
14
+ * - DOM nodes
15
+ * - window
16
+ *
17
+ * @param {Object} obj - Variable name
18
+ * @returns {Boolean}
19
+ */
20
+ function isPlainObject (obj) {
21
+ if (obj === null) return false
22
+
23
+ if (typeof obj !== 'object' || obj.nodeType || (obj === obj.window)) return false
24
+
25
+ if (obj.constructor && !hasOwnProperty(obj.constructor.prototype, 'isPrototypeOf')) return false
26
+
27
+ /* Most likely |obj| is a plain object, created by {} or constructed with new Object */
28
+ return true
29
+ }
30
+
31
+ function deepCopy (target) {
32
+ return isPlainObject(target) ? extend({}, target) : Array.isArray(target) ? target.map(deepCopy) : target
33
+ }
34
+
35
+ function extend (destination, ...args) {
36
+ args.forEach(source => {
37
+ if (source) {
38
+ Object.keys(source).forEach(property => {
39
+ if (source[property] && isPlainObject(source[property])) {
40
+ if (!hasOwnProperty(destination, property)) destination[property] = {};
41
+ extend(destination[property], source[property]);
42
+ } else if (Array.isArray(source[property])) {
43
+ destination[property] = deepCopy(source[property]);
44
+ } else {
45
+ destination[property] = source[property];
46
+ }
47
+ });
48
+ }
49
+ });
50
+
51
+ return destination
52
+ }
53
+
54
+ /**
55
+ * Helper function to check own property key
56
+ *
57
+ * @see https://eslint.org/docs/rules/no-prototype-builtins
58
+ */
59
+ function hasOwnProperty (obj, key) {
60
+ return obj && Object.prototype.hasOwnProperty.call(obj, key)
61
+ }
62
+
63
+ /**
64
+ * All editors should extend from this class
65
+ */
66
+ class AbstractEditor {
67
+ constructor (options, defaults) {
68
+ this.defaults = defaults;
69
+ this.jsoneditor = options.jsoneditor;
70
+ this.theme = this.jsoneditor.theme;
71
+ this.template_engine = this.jsoneditor.template;
72
+ this.iconlib = this.jsoneditor.iconlib;
73
+ this.translate = this.jsoneditor.translate || this.defaults.translate;
74
+ this.translateProperty = this.jsoneditor.translateProperty || this.defaults.translateProperty;
75
+ this.original_schema = options.schema;
76
+ this.schema = this.jsoneditor.expandSchema(this.original_schema);
77
+ this.active = true;
78
+ this.isUiOnly = false;
79
+ this.options = extend({}, (this.options || {}), (this.schema.options || {}), (options.schema.options || {}), options);
80
+ this.enforceConstEnabled = this.options.enforce_const ?? this.jsoneditor.options.enforce_const;
81
+ this.formname = this.jsoneditor.options.form_name_root || 'root';
82
+
83
+ if (!options.path && !this.schema.id) this.schema.id = this.formname;
84
+ this.path = options.path || this.formname;
85
+ this.formname = options.formname || this.path.replace(/\.([^.]+)/g, '[$1]');
86
+
87
+ this.parent = options.parent;
88
+ this.key = this.parent !== undefined ? this.path.split('.').slice(this.parent.path.split('.').length).join('.') : this.path;
89
+
90
+ this.link_watchers = [];
91
+ this.watchLoop = false;
92
+ this.optInWidget = this.options.opt_in_widget ?? this.jsoneditor.options.opt_in_widget;
93
+
94
+ if (options.container) this.setContainer(options.container);
95
+ this.registerDependencies();
96
+ }
97
+
98
+ onChildEditorChange (editor, eventData) {
99
+ this.onChange(true, false, eventData);
100
+ }
101
+
102
+ notify () {
103
+ if (this.path) this.jsoneditor.notifyWatchers(this.path);
104
+ }
105
+
106
+ change (eventData) {
107
+ if (this.parent) this.parent.onChildEditorChange(this, eventData);
108
+ else if (this.jsoneditor) this.jsoneditor.onChange(eventData);
109
+ }
110
+
111
+ onChange (bubble, fromTemplate, eventData) {
112
+ this.notify();
113
+
114
+ if (!fromTemplate) {
115
+ if (this.watch_listener) this.watch_listener();
116
+ }
117
+
118
+ if (bubble) this.change(eventData);
119
+ }
120
+
121
+ register () {
122
+ this.jsoneditor.registerEditor(this);
123
+ if (this.input && !this.label) {
124
+ const ariaLabel = this.getTitle() || this.formname;
125
+ this.input.setAttribute('aria-label', ariaLabel);
126
+ }
127
+ this.onChange();
128
+ }
129
+
130
+ unregister () {
131
+ if (!this.jsoneditor) return
132
+ this.jsoneditor.unregisterEditor(this);
133
+ }
134
+
135
+ getNumColumns () {
136
+ return 12
137
+ }
138
+
139
+ isActive () {
140
+ return this.active
141
+ }
142
+
143
+ activate () {
144
+ this.active = true;
145
+ this.optInCheckbox.checked = true;
146
+ this.enable();
147
+ this.change();
148
+ }
149
+
150
+ deactivate () {
151
+ /* only non required properties can be deactivated. */
152
+ if (!this.isRequired()) {
153
+ this.active = false;
154
+ this.optInCheckbox.checked = false;
155
+ this.disable();
156
+ this.change();
157
+ }
158
+ }
159
+
160
+ registerDependencies () {
161
+ this.dependenciesFulfilled = true;
162
+ const deps = this.options.dependencies;
163
+ if (!deps) {
164
+ return
165
+ }
166
+
167
+ Object.keys(deps).forEach(dependency => {
168
+ let path;
169
+ const isFullPath = dependency.startsWith(this.jsoneditor.root.path);
170
+
171
+ if (isFullPath) {
172
+ path = dependency;
173
+ } else {
174
+ path = this.path.split('.');
175
+ path[path.length - 1] = dependency;
176
+ path = path.join('.');
177
+ }
178
+
179
+ this.jsoneditor.watch(path, () => {
180
+ this.evaluateDependencies();
181
+ });
182
+ });
183
+ }
184
+
185
+ evaluateDependencies () {
186
+ const wrapper = this.container || this.control;
187
+ if (!wrapper || this.jsoneditor === null) {
188
+ return
189
+ }
190
+
191
+ const deps = this.options.dependencies;
192
+ if (!deps) {
193
+ return
194
+ }
195
+
196
+ // Assume true and set to false if any unmet dependencies are found
197
+ const previousStatus = this.dependenciesFulfilled;
198
+ this.dependenciesFulfilled = true;
199
+
200
+ Object.keys(deps).forEach(dependency => {
201
+ let path;
202
+ const isFullPath = dependency.startsWith(this.jsoneditor.root.path);
203
+
204
+ if (isFullPath) {
205
+ path = dependency;
206
+ } else {
207
+ path = this.path.split('.');
208
+ path[path.length - 1] = dependency;
209
+ path = path.join('.');
210
+ }
211
+
212
+ const choices = deps[dependency];
213
+ this.checkDependency(path, choices);
214
+ });
215
+
216
+ if (this.dependenciesFulfilled !== previousStatus) {
217
+ this.notify();
218
+ }
219
+
220
+ let displayMode = this.dependenciesFulfilled ? 'block' : 'none';
221
+
222
+ if (this.options.hidden) {
223
+ displayMode = 'none';
224
+ }
225
+
226
+ if (wrapper.tagName === 'TD') {
227
+ Object.keys(wrapper.childNodes).forEach(child => (wrapper.childNodes[child].style.display = displayMode));
228
+ } else wrapper.style.display = displayMode;
229
+ }
230
+
231
+ checkDependency (path, choices) {
232
+ if (this.path === path || this.jsoneditor === null) {
233
+ return
234
+ }
235
+
236
+ const editor = this.jsoneditor.getEditor(path);
237
+ const value = editor ? editor.getValue() : undefined;
238
+
239
+ if (!editor || !editor.dependenciesFulfilled || !value) {
240
+ this.dependenciesFulfilled = false;
241
+ } else if (Array.isArray(choices)) {
242
+ this.dependenciesFulfilled = choices.some(choice => {
243
+ if (JSON.stringify(value) === JSON.stringify(choice)) {
244
+ return true
245
+ }
246
+ });
247
+ } else if (typeof choices === 'object') {
248
+ if (typeof value !== 'object') {
249
+ this.dependenciesFulfilled = choices === value;
250
+ } else {
251
+ Object.keys(choices).some(key => {
252
+ if (!hasOwnProperty(choices, key)) {
253
+ return false
254
+ }
255
+ if (!hasOwnProperty(value, key) || choices[key] !== value[key]) {
256
+ this.dependenciesFulfilled = false;
257
+ return true
258
+ }
259
+ });
260
+ }
261
+ } else if (typeof choices === 'string' || typeof choices === 'number') {
262
+ this.dependenciesFulfilled = this.dependenciesFulfilled && value === choices;
263
+ } else if (typeof choices === 'boolean') {
264
+ if (choices) {
265
+ this.dependenciesFulfilled = this.dependenciesFulfilled && (value || value.length > 0);
266
+ } else {
267
+ this.dependenciesFulfilled = this.dependenciesFulfilled && (!value || value.length === 0);
268
+ }
269
+ }
270
+ }
271
+
272
+ setContainer (container) {
273
+ this.container = container;
274
+ this.setContainerAttributes();
275
+ if (this.schema.id) this.container.setAttribute('data-schemaid', this.schema.id);
276
+ if (this.schema.type && typeof this.schema.type === 'string') this.container.setAttribute('data-schematype', this.schema.type);
277
+ this.container.setAttribute('data-schemapath', this.path);
278
+ }
279
+
280
+ setOptInCheckbox () {
281
+ let optIn;
282
+
283
+ if (this.optInWidget === 'switch') {
284
+ optIn = this.theme.getOptInSwitch(this.formname);
285
+ } else {
286
+ optIn = this.theme.getOptInCheckbox(this.formname);
287
+ }
288
+
289
+ this.optInCheckbox = optIn.checkbox;
290
+ this.optInContainer = optIn.container;
291
+
292
+ this.optInCheckbox.addEventListener('click', () => {
293
+ if (this.isActive()) {
294
+ this.deactivate();
295
+ } else {
296
+ this.activate();
297
+ }
298
+ });
299
+
300
+ /* append active/deactive checkbox if show_opt_in is true */
301
+ const globalOptIn = this.jsoneditor.options.show_opt_in;
302
+ const parentOptInDefined = (typeof this.parent.options.show_opt_in !== 'undefined');
303
+ const parentOptInEnabled = (parentOptInDefined && this.parent.options.show_opt_in === true);
304
+ const parentOptInDisabled = (parentOptInDefined && this.parent.options.show_opt_in === false);
305
+
306
+ if (parentOptInEnabled || (!parentOptInDisabled && globalOptIn) || (!parentOptInDefined && globalOptIn)) {
307
+ /* and control to type object editors if they are not required */
308
+ if (this.parent && this.parent.schema.type === 'object' && !this.isRequired() && this.header) {
309
+ this.header.insertBefore(this.optInContainer, this.header.firstChild);
310
+ this.optInAppended = true;
311
+ }
312
+ }
313
+ }
314
+
315
+ preBuild () {
316
+
317
+ }
318
+
319
+ build () {
320
+
321
+ }
322
+
323
+ postBuild () {
324
+ this.setupWatchListeners();
325
+ this.addLinks();
326
+ this.register();
327
+ this.setValue(this.getDefault(), true);
328
+ this.updateHeaderText();
329
+ this.onWatchedFieldChange();
330
+
331
+ if (this.options.titleHidden) {
332
+ this.theme.visuallyHidden(this.label);
333
+ this.theme.visuallyHidden(this.header);
334
+ }
335
+
336
+ if (this.enforceConstEnabled && this.schema.const) {
337
+ this.disable();
338
+ }
339
+ }
340
+
341
+ setupWatchListeners () {
342
+ /* Watched fields */
343
+ this.watched = {};
344
+ if (this.schema.vars) this.schema.watch = this.schema.vars;
345
+ this.watched_values = {};
346
+ this.watch_listener = () => {
347
+ if (this.refreshWatchedFieldValues()) {
348
+ this.onWatchedFieldChange();
349
+ }
350
+ };
351
+
352
+ if (hasOwnProperty(this.schema, 'watch')) {
353
+ let path; let pathParts; let first; let root; let adjustedPath;
354
+ const myPath = this.container.getAttribute('data-schemapath');
355
+
356
+ Object.keys(this.schema.watch).forEach(name => {
357
+ path = this.schema.watch[name];
358
+ if (Array.isArray(path)) {
359
+ if (path.length < 2) return
360
+ pathParts = [path[0]].concat(path[1].split('.'));
361
+ } else {
362
+ pathParts = path.split('.');
363
+ if (!this.theme.closest(this.container, `[data-schemaid="${pathParts[0]}"]`)) pathParts.unshift('#');
364
+ }
365
+ first = pathParts.shift();
366
+
367
+ if (first === '#') first = this.jsoneditor.schema.id || this.jsoneditor.root.formname;
368
+
369
+ /* Find the root node for this template variable */
370
+ root = this.theme.closest(this.container, `[data-schemaid="${first}"]`);
371
+ if (!root) throw new Error(`Could not find ancestor node with id ${first}`)
372
+
373
+ /* Keep track of the root node and path for use when rendering the template */
374
+ adjustedPath = `${root.getAttribute('data-schemapath')}.${pathParts.join('.')}`;
375
+
376
+ if (myPath.startsWith(adjustedPath)) this.watchLoop = true;
377
+ this.jsoneditor.watch(adjustedPath, this.watch_listener);
378
+
379
+ this.watched[name] = adjustedPath;
380
+ });
381
+ }
382
+
383
+ /* Dynamic header */
384
+ if (this.schema.headerTemplate) {
385
+ this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine);
386
+ }
387
+ }
388
+
389
+ addLinks () {
390
+ /* Add links */
391
+ if (!this.no_link_holder) {
392
+ this.link_holder = this.theme.getLinksHolder();
393
+ /* if description element exists, insert the link before */
394
+ if (typeof this.description !== 'undefined') this.description.parentNode.insertBefore(this.link_holder, this.description);
395
+ /* otherwise just insert link at bottom of container */
396
+ else this.container.appendChild(this.link_holder);
397
+ if (this.schema.links) {
398
+ for (let i = 0; i < this.schema.links.length; i++) {
399
+ this.addLink(this.getLink(this.schema.links[i]));
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ onMove () {}
406
+
407
+ getButton (text, icon, title, args = []) {
408
+ const btnClass = `json-editor-btn-${icon}`;
409
+ if (!this.iconlib) icon = null;
410
+ else icon = this.iconlib.getIcon(icon);
411
+
412
+ text = this.translate(text, args);
413
+ title = this.translate(title, args);
414
+
415
+ if (!icon && title) {
416
+ text = title;
417
+ title = null;
418
+ }
419
+
420
+ const btn = this.theme.getButton(text, icon, title);
421
+ btn.classList.add(btnClass);
422
+ return btn
423
+ }
424
+
425
+ setButtonText (button, text, icon, title, args = []) {
426
+ if (!this.iconlib) icon = null;
427
+ else icon = this.iconlib.getIcon(icon);
428
+
429
+ text = this.translate(text, args);
430
+ title = this.translate(title, args);
431
+
432
+ if (!icon && title) {
433
+ text = title;
434
+ title = null;
435
+ }
436
+
437
+ return this.theme.setButtonText(button, text, icon, title)
438
+ }
439
+
440
+ addLink (link) {
441
+ if (this.link_holder) this.link_holder.appendChild(link);
442
+ }
443
+
444
+ getLink (data) {
445
+ let holder;
446
+ let link;
447
+
448
+ /* Get mime type of the link */
449
+ const mime = data.mediaType || 'application/javascript';
450
+ const type = mime.split('/')[0];
451
+
452
+ /* Template to generate the link href */
453
+ const href = this.jsoneditor.compileTemplate(data.href, this.template_engine);
454
+ const relTemplate = this.jsoneditor.compileTemplate(data.rel ? data.rel : data.href, this.template_engine);
455
+
456
+ /* Template to generate the link's download attribute */
457
+ let download = null;
458
+ if (data.download) download = data.download;
459
+
460
+ if (download && download !== true) {
461
+ download = this.jsoneditor.compileTemplate(download, this.template_engine);
462
+ }
463
+
464
+ /* Image links */
465
+ if (type === 'image') {
466
+ holder = this.theme.getBlockLinkHolder();
467
+ link = document.createElement('a');
468
+ link.setAttribute('target', '_blank');
469
+ const image = document.createElement('img');
470
+
471
+ this.theme.createImageLink(holder, link, image);
472
+
473
+ /* When a watched field changes, update the url */
474
+ this.link_watchers.push(vars => {
475
+ const url = href(vars);
476
+ const rel = relTemplate(vars);
477
+ link.setAttribute('href', url);
478
+ link.setAttribute('title', rel || url);
479
+ image.setAttribute('src', url);
480
+ });
481
+ /* Audio/Video links */
482
+ } else if (['audio', 'video'].includes(type)) {
483
+ holder = this.theme.getBlockLinkHolder();
484
+
485
+ link = this.theme.getBlockLink();
486
+ link.setAttribute('target', '_blank');
487
+
488
+ const media = document.createElement(type);
489
+ media.setAttribute('controls', 'controls');
490
+
491
+ this.theme.createMediaLink(holder, link, media);
492
+
493
+ /* When a watched field changes, update the url */
494
+ this.link_watchers.push(vars => {
495
+ const url = href(vars);
496
+ const rel = relTemplate(vars);
497
+ link.setAttribute('href', url);
498
+ link.textContent = rel || url;
499
+ media.setAttribute('src', url);
500
+ });
501
+ /* Text links or blank link */
502
+ } else {
503
+ link = holder = this.theme.getBlockLink();
504
+ holder.setAttribute('target', '_blank');
505
+ holder.textContent = data.rel;
506
+ holder.style.display = 'none'; /* Prevent blank links from showing up when using custom view */
507
+
508
+ /* When a watched field changes, update the url */
509
+ this.link_watchers.push(vars => {
510
+ const url = href(vars);
511
+ const rel = relTemplate(vars);
512
+ if (url) holder.style.display = '';
513
+ holder.setAttribute('href', url);
514
+ holder.textContent = rel || url;
515
+ });
516
+ }
517
+
518
+ if (download && link) {
519
+ if (download === true) {
520
+ link.setAttribute('download', '');
521
+ } else {
522
+ this.link_watchers.push(vars => {
523
+ link.setAttribute('download', download(vars));
524
+ });
525
+ }
526
+ }
527
+
528
+ if (data.class) {
529
+ const classNames = data.class.split(' ');
530
+
531
+ classNames.forEach((className) => {
532
+ link.classList.add(className);
533
+ });
534
+ }
535
+
536
+ return holder
537
+ }
538
+
539
+ refreshWatchedFieldValues () {
540
+ if (!this.watched_values) return
541
+ const watched = {};
542
+ let changed = false;
543
+
544
+ if (this.watched) {
545
+ Object.keys(this.watched).forEach(name => {
546
+ const editor = this.jsoneditor.getEditor(this.watched[name]);
547
+ const val = editor ? editor.getValue() : null;
548
+ if (this.watched_values[name] !== val) changed = true;
549
+ watched[name] = val;
550
+ });
551
+ }
552
+
553
+ watched.self = this.getValue();
554
+ if (this.watched_values.self !== watched.self) changed = true;
555
+
556
+ this.watched_values = watched;
557
+
558
+ return changed
559
+ }
560
+
561
+ getWatchedFieldValues () {
562
+ return this.watched_values
563
+ }
564
+
565
+ updateHeaderText () {
566
+ if (this.header) {
567
+ const headerText = this.getHeaderText();
568
+ /* If the header has children, only update the text node's value */
569
+ if (this.header.children.length) {
570
+ for (let i = 0; i < this.header.childNodes.length; i++) {
571
+ if (this.header.childNodes[i].nodeType === 3) {
572
+ this.header.childNodes[i].nodeValue = this.cleanText(headerText);
573
+ break
574
+ }
575
+ }
576
+ /* Otherwise, just update the entire node */
577
+ } else {
578
+ if (window.DOMPurify) this.header.innerHTML = window.DOMPurify.sanitize(headerText);
579
+ else this.header.textContent = this.cleanText(headerText);
580
+ }
581
+ }
582
+ }
583
+
584
+ getHeaderText (titleOnly) {
585
+ if (this.header_text) return this.header_text
586
+ else if (titleOnly) return this.translateProperty(this.schema.title)
587
+ else return this.getTitle()
588
+ }
589
+
590
+ getPathDepth () {
591
+ return this.path.split('.').length
592
+ }
593
+
594
+ cleanText (txt) {
595
+ /* Clean out HTML tags from txt */
596
+ const tmp = document.createElement('div');
597
+ tmp.innerHTML = txt;
598
+ return (tmp.textContent || tmp.innerText)
599
+ }
600
+
601
+ onWatchedFieldChange () {
602
+ let vars;
603
+
604
+ if (this.header_template) {
605
+ vars = extend(this.getWatchedFieldValues(), {
606
+ key: this.key,
607
+ i: this.key,
608
+ i0: (this.key * 1),
609
+ i1: (this.key * 1 + 1),
610
+ title: this.getTitle()
611
+ });
612
+
613
+ // object properties
614
+ if (this.editors && Object.keys(this.editors).length) {
615
+ vars.properties = {};
616
+
617
+ Object.keys(this.editors).forEach((key) => {
618
+ const editor = this.editors[key];
619
+
620
+ if (editor.schema && editor.schema.enum && editor.schema.options && editor.schema.options.enum_titles) {
621
+ const enumIndex = editor.schema.enum.indexOf(editor.value);
622
+ const enumTitle = editor.options.enum_titles[enumIndex];
623
+ vars.properties[key] = {
624
+ enumTitle
625
+ };
626
+ }
627
+ });
628
+ }
629
+
630
+ const headerText = this.header_template(vars);
631
+
632
+ if (headerText !== this.header_text) {
633
+ this.header_text = headerText;
634
+ this.updateHeaderText();
635
+ this.notify();
636
+ /* this.fireChangeHeaderEvent(); */
637
+ }
638
+ }
639
+ if (this.link_watchers.length) {
640
+ vars = this.getWatchedFieldValues();
641
+ for (let i = 0; i < this.link_watchers.length; i++) {
642
+ this.link_watchers[i](vars);
643
+ }
644
+ }
645
+ }
646
+
647
+ setValue (value) {
648
+ value = this.applyConstFilter(value);
649
+ this.value = value;
650
+ }
651
+
652
+ applyConstFilter (value) {
653
+ if (this.enforceConstEnabled && typeof this.schema.const !== 'undefined') {
654
+ value = this.schema.const;
655
+ }
656
+
657
+ return value
658
+ }
659
+
660
+ getValue () {
661
+ if (!this.dependenciesFulfilled) {
662
+ return undefined
663
+ }
664
+ return this.value
665
+ }
666
+
667
+ refreshValue () {
668
+
669
+ }
670
+
671
+ getChildEditors () {
672
+ return false
673
+ }
674
+
675
+ destroy () {
676
+ this.unregister(this);
677
+ if (this.watched) {
678
+ Object.values(this.watched).forEach(adjustedPath => this.jsoneditor.unwatch(adjustedPath, this.watch_listener));
679
+ }
680
+
681
+ this.watched = null;
682
+ this.watched_values = null;
683
+ this.watch_listener = null;
684
+ this.header_text = null;
685
+ this.header_template = null;
686
+ this.value = null;
687
+ if (this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container);
688
+ this.container = null;
689
+ this.jsoneditor = null;
690
+ this.schema = null;
691
+ this.path = null;
692
+ this.key = null;
693
+ this.parent = null;
694
+ }
695
+
696
+ isDefaultRequired () {
697
+ return this.isRequired() || !!this.jsoneditor.options.use_default_values
698
+ }
699
+
700
+ getDefault () {
701
+ if (this.enforceConstEnabled && this.schema.const) {
702
+ return this.schema.const
703
+ }
704
+
705
+ if (typeof this.schema.default !== 'undefined') {
706
+ return this.schema.default
707
+ }
708
+
709
+ if (typeof this.schema.enum !== 'undefined') {
710
+ return this.schema.enum[0]
711
+ }
712
+
713
+ let type = this.schema.type || this.schema.oneOf;
714
+ if (type && Array.isArray(type)) type = type[0];
715
+ if (type && typeof type === 'object') type = type.type;
716
+ if (type && Array.isArray(type)) type = type[0];
717
+
718
+ if (typeof type === 'string') {
719
+ if (type === 'number') return this.isDefaultRequired() ? 0.0 : undefined
720
+ if (type === 'boolean') return this.isDefaultRequired() ? false : undefined
721
+ if (type === 'integer') return this.isDefaultRequired() ? 0 : undefined
722
+ if (type === 'string') return this.isDefaultRequired() ? '' : undefined
723
+ if (type === 'null') return null
724
+ if (type === 'object') return {}
725
+ if (type === 'array') return []
726
+ }
727
+
728
+ return undefined
729
+ }
730
+
731
+ getTitle () {
732
+ return this.translateProperty(this.schema.title || this.key || this.formname)
733
+ }
734
+
735
+ enable () {
736
+ this.disabled = false;
737
+ }
738
+
739
+ disable () {
740
+ this.disabled = true;
741
+ }
742
+
743
+ isEnabled () {
744
+ return !this.disabled
745
+ }
746
+
747
+ isRequired () {
748
+ if (typeof this.schema.required === 'boolean') return this.schema.required
749
+ else if (this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.includes(this.key)
750
+ else if (this.jsoneditor.options.required_by_default) return true
751
+ else return false
752
+ }
753
+
754
+ getDisplayText (arr) {
755
+ const disp = [];
756
+ const used = {};
757
+
758
+ /* Determine how many times each attribute name is used. */
759
+ /* This helps us pick the most distinct display text for the schemas. */
760
+ arr.forEach(el => {
761
+ if (el.title) {
762
+ used[el.title] = used[el.title] || 0;
763
+ used[el.title]++;
764
+ }
765
+ if (el.description) {
766
+ used[el.description] = used[el.description] || 0;
767
+ used[el.description]++;
768
+ }
769
+ if (el.format) {
770
+ used[el.format] = used[el.format] || 0;
771
+ used[el.format]++;
772
+ }
773
+ if (el.type) {
774
+ used[el.type] = used[el.type] || 0;
775
+ used[el.type]++;
776
+ }
777
+ });
778
+
779
+ /* Determine display text for each element of the array */
780
+ arr.forEach(el => {
781
+ let name;
782
+
783
+ /* If it's a simple string */
784
+ if (typeof el === 'string') name = el;
785
+ /* Object */
786
+ else if (el.title && used[el.title] <= 1) name = el.title;
787
+ else if (el.format && used[el.format] <= 1) name = el.format;
788
+ else if (el.type && used[el.type] <= 1) name = el.type;
789
+ else if (el.description && used[el.description] <= 1) name = el.description;
790
+ else if (el.title) name = el.title;
791
+ else if (el.format) name = el.format;
792
+ else if (el.type) name = el.type;
793
+ else if (el.description) name = el.description;
794
+ else if (JSON.stringify(el).length < 500) name = JSON.stringify(el);
795
+ else name = 'type';
796
+
797
+ disp.push(name);
798
+ });
799
+
800
+ /* Replace identical display text with "text 1", "text 2", etc. */
801
+ const inc = {};
802
+ disp.forEach((name, i) => {
803
+ inc[name] = inc[name] || 0;
804
+ inc[name]++;
805
+
806
+ if (used[name] > 1) disp[i] = `${name} ${inc[name]}`;
807
+ });
808
+
809
+ return disp
810
+ }
811
+
812
+ /* Replace space(s) with "-" to create valid id value */
813
+ getValidId (id) {
814
+ id = id === undefined ? '' : id.toString();
815
+ return id.replace(/\s+/g, '-')
816
+ }
817
+
818
+ setInputAttributes (inputAttribute, input) {
819
+ if (this.schema.options && this.schema.options.inputAttributes) {
820
+ const inputAttributes = this.schema.options.inputAttributes;
821
+ const protectedAttributes = ['name', 'type'].concat(inputAttribute);
822
+ const workingInput = input || this.input;
823
+ Object.keys(inputAttributes).forEach(key => {
824
+ if (!protectedAttributes.includes(key.toLowerCase())) {
825
+ workingInput.setAttribute(key, inputAttributes[key]);
826
+ }
827
+ });
828
+ }
829
+ }
830
+
831
+ setContainerAttributes () {
832
+ if (this.schema.options && this.schema.options.containerAttributes) {
833
+ const containerAttributes = this.schema.options.containerAttributes;
834
+ const protectedAttributes = ['data-schemapath', 'data-schematype', 'data-schemaid'];
835
+ Object.keys(containerAttributes).forEach(key => {
836
+ if (!protectedAttributes.includes(key.toLowerCase())) {
837
+ this.container.setAttribute(key, containerAttributes[key]);
838
+ }
839
+ });
840
+ }
841
+ }
842
+
843
+ expandCallbacks (scope, options) {
844
+ const callback = this.defaults.callbacks[scope];
845
+ Object.entries(options).forEach(([key, value]) => {
846
+ if (value === Object(value)) {
847
+ options[key] = this.expandCallbacks(scope, value);
848
+ } else if (typeof value === 'string' &&
849
+ typeof callback === 'object' &&
850
+ typeof callback[value] === 'function') {
851
+ options[key] = callback[value].bind(null, this);
852
+ }
853
+ });
854
+ return options
855
+ }
856
+
857
+ showValidationErrors (errors) {
858
+
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Generate band colors for the editor
864
+ * @param {Record<string,any>} schema - JSON schema
865
+ * @param {string} format - Format type ("bands" or "bands-arithmetic")
866
+ * @returns {string[]} Array of color strings
867
+ */
868
+ function generateBandColors(schema, format) {
869
+ const bands = format === "bands" ? schema.items?.enum : schema.enum || [];
870
+ const colors =
871
+ format === "bands"
872
+ ? schema.items?.options?.colors
873
+ : schema.options?.colors || [];
874
+ if (colors && colors.length === bands.length) {
875
+ return colors;
876
+ }
877
+
878
+ return bands.map(
879
+ () =>
880
+ "#" +
881
+ Math.floor(Math.random() * 16777215)
882
+ .toString(16)
883
+ .padStart(6, "0"),
884
+ );
885
+ }
886
+
887
+ /**
888
+ * Get color for a specific band
889
+ * @param {string} band - Band identifier
890
+ * @param {string[]} bands - Array of band identifiers
891
+ * @param {string[]} colors - Array of color strings
892
+ * @returns {string} Color string
893
+ */
894
+ function getBandColor(band, bands, colors) {
895
+ const index = bands.indexOf(band);
896
+ return index !== -1 ? colors[index] : "#000000";
897
+ }
898
+
899
+ /**
900
+ * Create a draggable band element.
901
+ * @param {string} enumValue
902
+ * @param {string} title
903
+ */
904
+ function createBandDiv(enumValue, title) {
905
+ const div = document.createElement("div");
906
+ div.dataset.band = enumValue;
907
+ div.textContent = title;
908
+ div.draggable = true;
909
+ div.ondragstart = (e) => {
910
+ e.dataTransfer?.setData("band", enumValue);
911
+ };
912
+ return div;
913
+ }
914
+
915
+ /**
916
+ * Add draggable band elements
917
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
918
+ * @param {Array<string>} bands - Array of band identifiers
919
+ * @param {Array<string>} bandTitles - Array of band titles
920
+ */
921
+ function addDraggableBands(editor, bands, bandTitles) {
922
+ bands.forEach((band, index) => {
923
+ const title = bandTitles[index];
924
+ const bandDiv = createBandDiv(band, title);
925
+
926
+ // createBandDiv already sets up drag functionality
927
+ editor.control?.appendChild(bandDiv);
928
+ });
929
+ }
930
+
931
+ /**
932
+ * Create unified styles for all slot types
933
+ * @param {string[]} bands - Array of band identifiers
934
+ * @param {string[]} colors - Array of color strings
935
+ * @returns {HTMLStyleElement} Style element with unified slot styles
936
+ */
937
+ function createSlotStyles(bands, colors) {
938
+ const style = document.createElement("style");
939
+ style.innerHTML = `
940
+ /* Base styles for all band elements */
941
+ [data-band] {
942
+ display: inline-flex;
943
+ border: 1px solid darkgrey;
944
+ border-radius: 50%;
945
+ height: 40px;
946
+ aspect-ratio: 1/1;
947
+ padding: 4px;
948
+ margin: 2px;
949
+ align-items: center;
950
+ justify-content: center;
951
+ cursor: move;
952
+ font-size: 10px;
953
+ }
954
+
955
+ /* Band color styles */
956
+ ${bands
957
+ .map(
958
+ (band) =>
959
+ `[data-band="${band}"] { background: ${getBandColor(
960
+ band,
961
+ bands,
962
+ colors,
963
+ )}; color: black; }`,
964
+ )
965
+ .join("\n")}
966
+
967
+ /* RGB slot styles */
968
+ [data-slot] {
969
+ display: inline-flex;
970
+ width: 50px;
971
+ height: 50px;
972
+ aspect-ratio: 1/1;
973
+ padding: 1px;
974
+ border: 2px solid #666;
975
+ background: #f0f0f0;
976
+ border-radius: 50%;
977
+ align-items: center;
978
+ justify-content: center;
979
+ cursor: pointer;
980
+ margin: 2px;
981
+ position: relative;
982
+ box-sizing: border-box;
983
+ }
984
+ [data-slot]:hover {
985
+ border-color: #333;
986
+ background: #f9f9f9;
987
+ }
988
+ [data-slot]::before {
989
+ content: attr(data-slot);
990
+ position: absolute;
991
+ font-size: 12px;
992
+ font-weight: bold;
993
+ color: #666;
994
+ z-index: 0;
995
+ }
996
+
997
+ /* container */
998
+ .slots-container {
999
+ font-family: monospace;
1000
+ font-size: 18px;
1001
+ padding: 16px;
1002
+ background: #f0f0f0;
1003
+ border: 1px solid #ccc;
1004
+ border-radius: 4px;
1005
+ margin: 8px 0;
1006
+ display: flex;
1007
+ align-items: center;
1008
+ justify-content: center;
1009
+ flex-wrap: wrap;
1010
+ gap: 4px;
1011
+ }
1012
+
1013
+ .formula-text {
1014
+ font-size: 18px;
1015
+ margin: 0 2px;
1016
+ }
1017
+ `;
1018
+ return style;
1019
+ }
1020
+
1021
+ /**
1022
+ * Create a unified slot element for RGB or arithmetic use
1023
+ * @param {string} identifier - RGB letter ("R", "G", "B") or variable name for arithmetic
1024
+ * @param {(e: DragEvent) => void} onDrop - Drop handler function
1025
+ * @returns {HTMLDivElement} Slot element
1026
+ */
1027
+ function createSlot(identifier, onDrop) {
1028
+ const slotDiv = document.createElement("div");
1029
+ // Use data-slot
1030
+ slotDiv.dataset.slot = identifier;
1031
+
1032
+ // Add drag & drop functionality
1033
+ slotDiv.ondrop = onDrop;
1034
+ slotDiv.ondragover = (e) => e.preventDefault();
1035
+
1036
+ return slotDiv;
1037
+ }
1038
+
1039
+ /**
1040
+ * Fill a slot with a band
1041
+ * @param {HTMLElement} slot - Slot element
1042
+ * @param {string} enumValue - Band value
1043
+ * @param {string} title - Band title
1044
+ */
1045
+ function fillSlotWithBand(slot, enumValue, title) {
1046
+ // clear existing band and add new one
1047
+ const existingBand = slot.querySelector("[data-band]");
1048
+ if (existingBand) {
1049
+ existingBand.remove();
1050
+ }
1051
+
1052
+ const bandDiv = createBandDiv(enumValue, title);
1053
+ slot.appendChild(bandDiv);
1054
+ }
1055
+
1056
+ /**
1057
+ * Build the traditional RGB bands interface
1058
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1059
+ * @param {Array<string>} colors - Array of color strings
1060
+ * @param {Array<string>} bands - Array of band identifiers
1061
+ * @param {Array<string>} bandTitles - Array of band titles
1062
+ */
1063
+ function buildRGBInterface(editor, colors, bands, bandTitles) {
1064
+ // Use unified styles instead of creating separate styles
1065
+ const style = createSlotStyles(bands, colors);
1066
+ editor.control?.appendChild(style);
1067
+
1068
+ // Add draggable bands
1069
+ addDraggableBands(editor, bands, bandTitles);
1070
+
1071
+ editor.control?.appendChild(document.createElement("hr"));
1072
+
1073
+ // Add RGB slots using unified slot system
1074
+ addRGBSlots(editor, bands, bandTitles);
1075
+ }
1076
+
1077
+ /**
1078
+ * Add RGB slots for traditional bands interface using unified slot system
1079
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1080
+ * @param {Array<string>} bands - Array of band identifiers
1081
+ * @param {Array<string>} bandTitles - Array of band titles
1082
+ */
1083
+ function addRGBSlots(editor, bands, bandTitles) {
1084
+ // Create a container for RGB slots
1085
+ const rgbContainer = document.createElement("div");
1086
+ rgbContainer.classList.add("slots-container");
1087
+ ["R", "G", "B"].forEach((slot, index) => {
1088
+ const onDrop = (/** @type {DragEvent} */ e) => {
1089
+ e.preventDefault();
1090
+ const enumValue = e.dataTransfer?.getData("band");
1091
+ if (!enumValue) return;
1092
+
1093
+ const enumIndex = bands.indexOf(enumValue);
1094
+ const title = bandTitles[enumIndex] || enumValue;
1095
+
1096
+ fillSlotWithBand(slotDiv, enumValue, title);
1097
+ // Get current value as array or create new one
1098
+ const currentValue = editor.getValue() || [];
1099
+ currentValue[index] = enumValue;
1100
+ editor.setValue(currentValue);
1101
+ editor.onChange(true);
1102
+ };
1103
+
1104
+ const slotDiv = createSlot(slot, onDrop);
1105
+ addRGBSlotStyle(slotDiv);
1106
+ rgbContainer.appendChild(slotDiv);
1107
+
1108
+ // Initialize with existing value
1109
+ setTimeout(() => {
1110
+ const currentValue = editor.getValue();
1111
+ if (currentValue?.[index]) {
1112
+ const enumValue = currentValue[index];
1113
+ const enumIndex = bands.indexOf(enumValue);
1114
+ const title = bandTitles[enumIndex] || enumValue;
1115
+ if (enumValue) {
1116
+ fillSlotWithBand(slotDiv, enumValue, title);
1117
+ }
1118
+ }
1119
+ });
1120
+ });
1121
+
1122
+ editor.control?.appendChild(rgbContainer);
1123
+ }
1124
+
1125
+ /**
1126
+ * Create specific styles for RGB slots
1127
+ * @param {HTMLElement} rgbSlot - Array of RGB slot elements
1128
+ */
1129
+ function addRGBSlotStyle(rgbSlot) {
1130
+ rgbSlot.style.border = "2px dashed";
1131
+ switch (rgbSlot.dataset.slot) {
1132
+ case "R": {
1133
+ rgbSlot.style.borderColor = "#F88";
1134
+ rgbSlot.style.background = "#FEE";
1135
+ break;
1136
+ }
1137
+ case "G": {
1138
+ rgbSlot.style.borderColor = "#8F8";
1139
+ rgbSlot.style.background = "#EFE";
1140
+ break;
1141
+ }
1142
+ case "B": {
1143
+ rgbSlot.style.borderColor = "#88F";
1144
+ rgbSlot.style.background = "#EEF";
1145
+ break;
1146
+ }
1147
+ }
1148
+ }
1149
+
1150
+ const MUSTACHE_REGEX = /\{\{([^}]+)\}\}/g;
1151
+ /**
1152
+ * Build the arithmetic expression interface
1153
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1154
+ * @param {Array<string>} colors - Array of color strings
1155
+ * @param {Array<string>} bands - Array of band identifiers
1156
+ * @param {Array<string>} bandTitles - Array of band titles
1157
+ */
1158
+ function buildArithmeticInterface(editor, colors, bands, bandTitles) {
1159
+ const formulaTemplate = editor.schema.formulaTemplate || "{{A}}";
1160
+
1161
+ const style = createSlotStyles(bands, colors);
1162
+ editor.control?.appendChild(style);
1163
+
1164
+ addDraggableBands(editor, bands, bandTitles);
1165
+
1166
+ editor.control?.appendChild(document.createElement("hr"));
1167
+
1168
+ // Add formula display with embedded slots after the bands
1169
+ addFormulaSlots(editor, formulaTemplate, bands, bandTitles);
1170
+ }
1171
+
1172
+ /**
1173
+ * Generate the final formula string with band values substituted
1174
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1175
+ * @returns {string} Formula string with substituted values
1176
+ */
1177
+ function generateFormulaString(editor) {
1178
+ /** @type {string} */
1179
+ const formulaTemplate = editor.schema.formulaTemplate || "{{A}}";
1180
+
1181
+ const variableValues = editor.variableValues || {};
1182
+
1183
+ return formulaTemplate.replace(MUSTACHE_REGEX, (match, variable) => {
1184
+ const bandValue = variableValues[variable.trim()];
1185
+ return bandValue || match; // Keep placeholder if no value assigned
1186
+ });
1187
+ }
1188
+
1189
+ /**
1190
+ * Add formula display with embedded circular slots
1191
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1192
+ * @param {string} template - Formula template string
1193
+ * @param {Array<string>} bands - Array of band identifiers
1194
+ * @param {Array<string>} bandTitles - Array of band titles
1195
+ */
1196
+ function addFormulaSlots(editor, template, bands, bandTitles) {
1197
+ const formulaContainer = document.createElement("div");
1198
+ formulaContainer.classList.add("slots-container");
1199
+
1200
+ // Initialize slots tracking
1201
+ editor.variableSlots = {};
1202
+
1203
+ // Split the template into parts and create elements
1204
+ const parts = template.split(/(\{\{[^}]+\}\})/);
1205
+
1206
+ parts.forEach((part) => {
1207
+ if (!part) {
1208
+ return;
1209
+ }
1210
+
1211
+ if (!part.match(MUSTACHE_REGEX)) {
1212
+ // This is formula text
1213
+ part = part.trim();
1214
+ if (part) {
1215
+ const textElement = document.createElement("span");
1216
+ textElement.classList.add("formula-text");
1217
+ textElement.textContent = part;
1218
+ formulaContainer.appendChild(textElement);
1219
+ }
1220
+ return;
1221
+ }
1222
+
1223
+ // This is a variable placeholder
1224
+ const variable = part.replace(/[{}]/g, "").trim();
1225
+ const slotElement = createSlot(variable, (e) => {
1226
+ e.preventDefault();
1227
+ const enumValue = e.dataTransfer?.getData("band");
1228
+ if (!enumValue) return;
1229
+
1230
+ const enumIndex = bands.indexOf(enumValue);
1231
+ const title = bandTitles[enumIndex] || enumValue;
1232
+
1233
+ editor.variableValues[variable] = enumValue;
1234
+
1235
+ // Update ALL slots for this variable using unified system
1236
+ updateAllSlotsForVariable(editor, variable, enumValue, title);
1237
+
1238
+ // final formula string as the value
1239
+ //@ts-expect-error todo
1240
+ editor.value = generateFormulaString(editor);
1241
+ editor.onChange(true);
1242
+ });
1243
+ formulaContainer.appendChild(slotElement);
1244
+
1245
+ // Track all slots for this variable
1246
+ if (!editor.variableSlots[variable]) {
1247
+ editor.variableSlots[variable] = [];
1248
+ }
1249
+ editor.variableSlots[variable].push(slotElement);
1250
+ });
1251
+
1252
+ editor.control?.appendChild(formulaContainer);
1253
+
1254
+ // Initialize slots with existing values after all slots are created
1255
+ setTimeout(() => {
1256
+ initializeSlots(editor);
1257
+ });
1258
+ }
1259
+
1260
+ /**
1261
+ * Initialize all slots with existing values
1262
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1263
+ */
1264
+ function initializeSlots(editor) {
1265
+ if (editor.variableValues && editor.variableSlots) {
1266
+ Object.keys(editor.variableValues).forEach((variable) => {
1267
+ const enumValue = editor.variableValues[variable];
1268
+ const bands = editor.bands || editor.schema.enum || [];
1269
+ const bandTitles =
1270
+ editor.bandTitles || editor.schema.options?.enum_titles || bands;
1271
+ const enumIndex = bands.indexOf(enumValue);
1272
+ const title = bandTitles[enumIndex] || enumValue;
1273
+ updateAllSlotsForVariable(editor, variable, enumValue, title);
1274
+ });
1275
+ }
1276
+ }
1277
+
1278
+ /**
1279
+ * Update all slots for a specific variable with band circle
1280
+ * @param {import("./index.js").BandsEditor} editor - The editor instance
1281
+ * @param {string} variable - Variable name
1282
+ * @param {string} enumValue - Band value
1283
+ * @param {string} title - Band title
1284
+ */
1285
+ function updateAllSlotsForVariable(editor, variable, enumValue, title) {
1286
+ if (editor.variableSlots && editor.variableSlots[variable]) {
1287
+ editor.variableSlots[variable].forEach((slot) => {
1288
+ fillSlotWithBand(slot, enumValue, title);
1289
+ });
1290
+ }
1291
+ }
1292
+
1293
+ /**
1294
+ * Custom band combination editor interface for eox-jsonform
1295
+ */
1296
+
1297
+ class BandsEditor extends AbstractEditor {
1298
+ /** @type {Record<string, HTMLElement[]>} */
1299
+ variableSlots = {};
1300
+ /** @type {Record<string, string>} */
1301
+ variableValues = {};
1302
+ /** @type {string[]} */
1303
+ bands = [];
1304
+ /** @type {string[]} */
1305
+ bandTitles = [];
1306
+ /** @type {string[]} */
1307
+ colors = [];
1308
+
1309
+ build() {
1310
+ super.build();
1311
+ // Determine the format type
1312
+ const format = this.schema.format || "bands";
1313
+
1314
+ this.bands =
1315
+ format === "bands" ? this.schema.items?.enum : this.schema.enum || [];
1316
+ this.bandTitles =
1317
+ format === "bands"
1318
+ ? this.schema.items?.options?.enum_titles
1319
+ : this.schema.options.enum_titles || this.bands;
1320
+ this.colors = generateBandColors(this.schema, format);
1321
+
1322
+ // control
1323
+ this.control = document.createElement("div");
1324
+ this.control.classList.add("form-control");
1325
+
1326
+ if (format === "bands") {
1327
+ buildRGBInterface(this, this.colors, this.bands, this.bandTitles);
1328
+ } else if (format === "bands-arithmetic") {
1329
+ buildArithmeticInterface(this, this.colors, this.bands, this.bandTitles);
1330
+ }
1331
+
1332
+ // label
1333
+ this.label = document.createElement("span");
1334
+ this.label.classList.add("je-header");
1335
+ this.label.textContent = this.schema.title ?? "";
1336
+
1337
+ // appends
1338
+ this.container?.appendChild(this.label);
1339
+ this.container?.appendChild(this.control);
1340
+ }
1341
+ }
1342
+
1343
+ const bandsEditorInterface = [
1344
+ {
1345
+ type: "array",
1346
+ format: "bands",
1347
+ func: BandsEditor,
1348
+ },
1349
+ {
1350
+ type: "string",
1351
+ format: "bands-arithmetic",
1352
+ func: BandsEditor,
1353
+ },
1354
+ ];
1355
+
1356
+ const _style_0 = "eox-layercontrol[data-v-219e33f9]{overflow:auto}";
1357
+
1358
+ const _hoisted_1 = { class: "d-flex flex-column" };
1359
+ const _hoisted_2 = ["for", ".customEditorInterfaces"];
1360
+ const _hoisted_3 = {
1361
+ key: 0,
1362
+ class: "mt-2 mb-2"
1363
+ };
1364
+
1365
+
1366
+ const _sfc_main = {
1367
+ __name: 'EodashLayerControl',
1368
+ props: {
1369
+ map: {
1370
+ /** @type {import("vue").PropType<"first" | "second">} */
1371
+ //@ts-expect-error todo
1372
+ type: String,
1373
+ default: "first",
1374
+ },
1375
+ tools: {
1376
+ type: Array,
1377
+ default: () => ["datetime", "info", "config", "legend", "opacity"],
1378
+ },
1379
+ title: {
1380
+ type: String || Boolean,
1381
+ default: "Layers",
1382
+ },
1383
+ cssVars: {
1384
+ type: Object,
1385
+ },
1386
+ },
1387
+ async setup(__props) {
1388
+
1389
+ let __temp, __restore;
1390
+
1391
+ if (!customElements.get("eox-layercontrol")) {
1392
+ (
1393
+ ([__temp,__restore] = withAsyncContext(() => import('@eox/layercontrol'))),
1394
+ await __temp,
1395
+ __restore()
1396
+ );
1397
+ }
1398
+ if (!customElements.get("eox-jsonform")) {
1399
+ (
1400
+ ([__temp,__restore] = withAsyncContext(() => import('@eox/jsonform'))),
1401
+ await __temp,
1402
+ __restore()
1403
+ );
1404
+ }
1405
+
1406
+ const props = __props;
1407
+
1408
+ const config = {
1409
+ tools: props.tools,
1410
+ style: props.cssVars,
1411
+ };
1412
+
1413
+ const { selectedCompareStac, selectedStac } = storeToRefs(useSTAcStore());
1414
+ const showControls = computed(() => {
1415
+ if (props.map === "second") {
1416
+ return mapCompareEl.value !== null && selectedCompareStac.value !== null;
1417
+ }
1418
+ return mapEl.value !== null && selectedStac.value !== null;
1419
+ });
1420
+
1421
+ const eodashCols =
1422
+ props.map === "second" ? eodashCompareCollections : eodashCollections;
1423
+ const mapElement = props.map === "second" ? mapCompareEl : mapEl;
1424
+
1425
+ /** @type { import("vue").Ref<HTMLElement & Record<string,any> | null>} */
1426
+ const eoxLayercontrol = ref(null);
1427
+
1428
+ /** @param {CustomEvent<{layer:import('ol/layer').Layer; datetime:string;}>} evt */
1429
+ const handleDatetimeUpdate = async (evt) => {
1430
+ const { layer, datetime } = evt.detail;
1431
+
1432
+ const ec = await getColFromLayer(eodashCols, layer);
1433
+
1434
+ /** @type {Record<string,any>[] | undefined} */
1435
+ let updatedLayers = [];
1436
+
1437
+ if (ec) {
1438
+ updatedLayers = await ec.updateLayerJson(
1439
+ datetime,
1440
+ layer.get("id"),
1441
+ props.map,
1442
+ );
1443
+ }
1444
+ /** @type {Record<String,any>[] | undefined} */
1445
+ const dataLayers = updatedLayers?.find(
1446
+ (l) => l?.properties?.id === "AnalysisGroup",
1447
+ )?.layers;
1448
+
1449
+ if (dataLayers?.length) {
1450
+ // Add expand to all analysis layers
1451
+ dataLayers?.forEach((dl) => {
1452
+ dl.properties.layerControlExpand = true;
1453
+ dl.properties.layerControlToolsExpand = true;
1454
+ });
1455
+ // assign layers to the map
1456
+ /** @type {HTMLElement & Record<string,any>} */
1457
+ (mapElement.value).layers = updatedLayers;
1458
+ }
1459
+ };
1460
+
1461
+ // ----- debounce logic
1462
+ /** @type {NodeJS.Timeout | undefined} */
1463
+ let timeout;
1464
+
1465
+ /**
1466
+ * @param {CustomEvent<{layer:import('ol/layer').Layer; datetime:string;}>} evt
1467
+ **/
1468
+ const debouncedHandleDateTime = (evt) => {
1469
+ clearTimeout(timeout);
1470
+ timeout = setTimeout(() => {
1471
+ handleDatetimeUpdate(evt);
1472
+ }, 500);
1473
+ };
1474
+ // ------
1475
+ /**
1476
+ *
1477
+ * @param {Event & {detail:{layer:import("ol/layer").Layer;jsonformValue:Record<string,any>}}} evt
1478
+ */
1479
+ const onLayerConfigChange = (evt) => {
1480
+ if (props.map === "second") {
1481
+ layerControlFormValueCompare.value = evt.detail.jsonformValue;
1482
+ } else {
1483
+ layerControlFormValue.value = evt.detail.jsonformValue;
1484
+ }
1485
+ };
1486
+
1487
+ return (_ctx, _cache) => {
1488
+ return (openBlock(), createElementBlock("span", _hoisted_1, [
1489
+ (showControls.value)
1490
+ ? (openBlock(), createElementBlock("eox-layercontrol", mergeProps({ key: unref(mapElement) }, config, {
1491
+ for: unref(mapElement),
1492
+ ".customEditorInterfaces": unref(bandsEditorInterface),
1493
+ "onDatetime:updated": debouncedHandleDateTime,
1494
+ toolsAsList: "true",
1495
+ ref_key: "eoxLayercontrol",
1496
+ ref: eoxLayercontrol,
1497
+ "on:layerConfig:change": onLayerConfigChange
1498
+ }), [
1499
+ renderSlot(_ctx.$slots, "layerstitle", {}, () => [
1500
+ createElementVNode("div", null, [
1501
+ (__props.title)
1502
+ ? (openBlock(), createElementBlock("p", _hoisted_3, [
1503
+ createElementVNode("strong", null, toDisplayString(__props.title), 1 /* TEXT */)
1504
+ ]))
1505
+ : createCommentVNode("v-if", true)
1506
+ ])
1507
+ ], true)
1508
+ ], 48 /* FULL_PROPS, NEED_HYDRATION */, _hoisted_2))
1509
+ : createCommentVNode("v-if", true)
1510
+ ]))
1511
+ }
1512
+ }
1513
+
1514
+ };
1515
+ const EodashLayerControl = /*#__PURE__*/_export_sfc(_sfc_main, [['styles',[_style_0]],['__scopeId',"data-v-219e33f9"]]);
1516
+
1517
+ export { EodashLayerControl as default };