@jinntec/fore 1.5.0 → 1.6.0

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 (71) hide show
  1. package/dist/fore-dev.js +2 -36
  2. package/dist/fore-dev.js.map +1 -1
  3. package/dist/fore.js +2 -30
  4. package/dist/fore.js.map +1 -1
  5. package/index.js +12 -0
  6. package/package.json +9 -5
  7. package/resources/fore.css +178 -92
  8. package/src/DependencyNotifyingDomFacade.js +1 -2
  9. package/src/ForeElementMixin.js +31 -5
  10. package/src/actions/abstract-action.js +379 -297
  11. package/src/actions/fx-action.js +0 -1
  12. package/src/actions/fx-append.js +1 -2
  13. package/src/actions/fx-confirm.js +12 -0
  14. package/src/actions/fx-copy.js +0 -1
  15. package/src/actions/fx-delete.js +31 -9
  16. package/src/actions/fx-dispatch.js +19 -5
  17. package/src/actions/fx-hide.js +19 -0
  18. package/src/actions/fx-insert.js +72 -8
  19. package/src/actions/fx-load.js +253 -0
  20. package/src/actions/fx-message.js +22 -7
  21. package/src/actions/fx-refresh.js +11 -1
  22. package/src/actions/fx-reload.js +12 -2
  23. package/src/actions/fx-replace.js +5 -4
  24. package/src/actions/fx-reset.js +48 -0
  25. package/src/actions/fx-return.js +0 -1
  26. package/src/actions/fx-send.js +40 -2
  27. package/src/actions/fx-setfocus.js +25 -7
  28. package/src/actions/fx-setvalue.js +32 -4
  29. package/src/actions/fx-show.js +14 -2
  30. package/src/actions/fx-toggle.js +0 -1
  31. package/src/actions/fx-update.js +9 -0
  32. package/src/events.js +0 -1
  33. package/src/fore.js +118 -63
  34. package/src/functions/common-function.js +28 -0
  35. package/src/fx-bind.js +7 -7
  36. package/src/fx-fore.js +152 -55
  37. package/src/fx-instance.js +55 -17
  38. package/src/fx-model.js +31 -33
  39. package/src/fx-submission.js +50 -47
  40. package/src/getInScopeContext.js +8 -10
  41. package/src/lab/fore-component.js +90 -0
  42. package/src/lab/instance-inspector.js +56 -0
  43. package/src/lab/template.html +16 -0
  44. package/src/relevance.js +27 -1
  45. package/src/tools/adi.js +1056 -0
  46. package/src/tools/fx-action-log.js +662 -0
  47. package/src/tools/fx-devtools.js +444 -0
  48. package/src/tools/fx-dom-inspector.js +609 -0
  49. package/src/tools/fx-json-instance.js +435 -0
  50. package/src/tools/fx-log-item.js +133 -0
  51. package/src/tools/fx-log-settings.js +474 -0
  52. package/src/tools/fx-minimap.js +194 -0
  53. package/src/tools/helpers.js +132 -0
  54. package/src/ui/abstract-control.js +41 -3
  55. package/src/ui/fx-action-log.js +358 -0
  56. package/src/ui/fx-alert.js +0 -1
  57. package/src/ui/fx-container.js +14 -3
  58. package/src/ui/fx-control.js +553 -474
  59. package/src/ui/fx-dialog.js +2 -0
  60. package/src/ui/fx-dom-inspector.js +1255 -0
  61. package/src/ui/fx-group.js +3 -4
  62. package/src/ui/fx-hint.js +2 -4
  63. package/src/ui/fx-inspector.js +5 -6
  64. package/src/ui/fx-items.js +55 -14
  65. package/src/ui/fx-output.js +36 -17
  66. package/src/ui/fx-repeat-attributes.js +10 -43
  67. package/src/ui/fx-repeat.js +5 -7
  68. package/src/ui/fx-switch.js +14 -3
  69. package/src/ui/fx-trigger.js +13 -1
  70. package/src/xpath-evaluation.js +109 -26
  71. package/src/xpath-util.js +55 -1
@@ -0,0 +1,1056 @@
1
+ import {
2
+ addClass,
3
+ removeClass,
4
+ isEmptyTextNode,
5
+ containsOnlyText,
6
+ newElement,
7
+ drawOptions,
8
+ pauseEvent,
9
+ drawAttrRow
10
+ } from './helpers.js';
11
+
12
+ import {Fore} from '../fore.js';
13
+
14
+ function isAttributeShown(name) {
15
+ return name === 'id' || name === 'ref' || name === 'event';
16
+ }
17
+
18
+ class ADI {
19
+ constructor(rootElement, instance) {
20
+ this.sourceNodeByInspectorNodeLookup = new Map();
21
+
22
+ this.uiView = null;
23
+ this.menuView = null;
24
+ this.domView = null;
25
+ this.attrView = null;
26
+ this.optsView = null;
27
+ /**
28
+ * The current active element. Note this is the element in the DOM view
29
+ */
30
+ this.activeElement = null;
31
+ this.vertResizing = false;
32
+ this.horizResizing = false;
33
+ this.pathScrolling = null;
34
+ this.elemLookup = false;
35
+ this.styleBackup = '';
36
+ this.xPos = 0;
37
+ this.delegatedEvents = [];
38
+
39
+ this.options = {
40
+ align: 'right', // NOTE: left is not supported in this version
41
+ split: 50,
42
+ minSplit: 30,
43
+ visible: true,
44
+ saving: false,
45
+ transparent: true,
46
+ omitEmptyText: true,
47
+ makeVisible: true,
48
+ foldText: true,
49
+ nodeTypes: [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.COMMENT_NODE, Node.DOCUMENT_NODE],
50
+ };
51
+
52
+ if (instance === '#document') {
53
+ this.instanceId = '#document';
54
+ this.document = window.document;
55
+ this.isInstanceViewer = false;
56
+ } else {
57
+ this.instanceId = instance.id;
58
+ if (!instance || instance.localName !== 'fx-instance') {
59
+ console.error('No instance found!');
60
+ }
61
+ this.document = instance.getInstanceData();
62
+ this.isInstanceViewer = true;
63
+ this.options.foldText = false;
64
+ }
65
+ this.drawUI(rootElement);
66
+ this.registerEvents();
67
+
68
+ // We're updating here, but we're doing that again later, when the UI is read (the 'ready' event fires)
69
+ this.drawDOM(this.document, this.domView.querySelector('.adi-tree-view'), true);
70
+ document.addEventListener('execute-action', e => this.processExecuteAction);
71
+ }
72
+
73
+ // Returns selected element or null
74
+ getSelected() {
75
+ if (!this.activeElement) {
76
+ return null;
77
+ }
78
+
79
+ let elem = document;
80
+
81
+ elem = this.sourceNodeByInspectorNodeLookup.get(this.activeElement);
82
+ /*
83
+ document.dispatchEvent(
84
+ new CustomEvent('path-touched', {
85
+ composed: true,
86
+ bubbles: true,
87
+ detail: {path: elem.modelItem.path},
88
+ }),
89
+ );
90
+ */
91
+
92
+ return elem;
93
+ }
94
+
95
+ // Loads user defined options stored in HTML5 storage (if available)
96
+ loadOptions() {
97
+ let userOptions = {};
98
+
99
+ userOptions = JSON.parse(window.localStorage.getItem('ADI.options')) || {};
100
+
101
+ // merge with defaults
102
+ for (const opt of Object.keys(userOptions)) {
103
+ this.options[opt] = userOptions[opt];
104
+ }
105
+ }
106
+
107
+ // Saves user defined options into the HTML5 storage (if available)
108
+ saveOptions() {
109
+ if (this.options.saving) {
110
+ window.localStorage.setItem('ADI.options', JSON.stringify(this.options));
111
+ }
112
+ }
113
+
114
+ // Checks if a node has some child nodes and if at least on of them is of a supported type
115
+ hasRequiredNodes(node) {
116
+ if (typeof node !== 'object') {
117
+ throw new Error(
118
+ `hasRequiredNodes: Expected argument node of type object, ${typeof node} given.`,
119
+ );
120
+ }
121
+
122
+ if (node.hasChildNodes()) {
123
+ for (let i = 0, len = node.childNodes.length; i < len; i += 1) {
124
+ const child = node.childNodes[i];
125
+ if (this.options.nodeTypes.includes(child.nodeType)) {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+
131
+
132
+ return false;
133
+ }
134
+
135
+ // Creates a starting markup for a new DOM tree view node
136
+ newTreeNode(sourceNode) {
137
+ if (typeof sourceNode !== 'object') {
138
+ throw new Error(`newTreeNode: Expected argument node of type object, ${typeof sourceNode} given.`);
139
+ }
140
+
141
+ const withChildren = this.hasRequiredNodes(sourceNode);
142
+ let omit = false;
143
+ let adiNode = sourceNode.nodeName.startsWith('FX-') ? `adi-node ${sourceNode.nodeName.toLowerCase()}`:'';
144
+ if(sourceNode.nodeName.startsWith('FX-')){
145
+ adiNode = `adi-node ${sourceNode.nodeName.toLowerCase()}`;
146
+ adiNode += Fore.isActionElement(sourceNode.nodeName) ? ' action':'';
147
+ }
148
+ const elem = newElement('li', {
149
+ class: adiNode,
150
+ });
151
+
152
+ // do not show ADI DOM nodes in the DOM view
153
+ if (sourceNode === this.uiView) {
154
+ return null;
155
+ }
156
+
157
+ // generate UI for elements with children
158
+ if (withChildren) {
159
+ elem.appendChild(newElement('span', {class: 'adi-trigger'}));
160
+ }
161
+
162
+ // we can omit empty text nodes if allowed in options
163
+ if (this.options.omitEmptyText && sourceNode.nodeType === Node.TEXT_NODE) {
164
+ omit = isEmptyTextNode(sourceNode);
165
+ }
166
+
167
+ if (!omit) {
168
+ const tagStart = newElement('span');
169
+
170
+ this.sourceNodeByInspectorNodeLookup.set(tagStart, sourceNode);
171
+ this.sourceNodeByInspectorNodeLookup.set(sourceNode, tagStart);
172
+
173
+ let tagEnd = null;
174
+
175
+ if (containsOnlyText(sourceNode)) {
176
+ if (sourceNode.nodeType === Node.COMMENT_NODE) {
177
+ addClass(tagStart, 'adi-comment-node');
178
+ if (typeof tagStart.innerText === 'string') {
179
+ tagStart.innerText = `<!-- ${sourceNode.textContent} -->`;
180
+ }
181
+ } else {
182
+ addClass(tagStart, 'adi-text-node');
183
+ tagStart.textContent = sourceNode.textContent;
184
+ }
185
+ } else {
186
+ addClass(tagStart, 'adi-normal-node');
187
+ if (sourceNode.nodeType !== Node.DOCUMENT_NODE) {
188
+ // tagStart.textContent = '<' + node.nodeName.toLowerCase() + '>';
189
+
190
+ if (sourceNode.nodeName === 'FX-BIND') {
191
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute(
192
+ 'ref',
193
+ )}">`;
194
+ } else if (sourceNode.nodeName === 'FX-INSERT') {
195
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`;
196
+ } else if (sourceNode.nodeName === 'FX-INSTANCE') {
197
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} id="${sourceNode.id}">`;
198
+ } else if (sourceNode.nodeName === 'FX-CONTROL') {
199
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`;
200
+ } else if (sourceNode.nodeName === 'FX-SEND') {
201
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} submission="${sourceNode.getAttribute('submission')}">`;
202
+ } else if (sourceNode.nodeName === 'FX-SETVALUE') {
203
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`;
204
+ } else if (sourceNode.nodeName === 'FX-SUBMISSION') {
205
+ tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} id="${sourceNode.getAttribute('id')}">`;
206
+ } else {
207
+ const attrString = Array.from(sourceNode.attributes)
208
+ .filter(
209
+ attr => this.isInstanceViewer ? true : isAttributeShown(attr.name))
210
+ .map(attr => `${attr.name}="${attr.value}"`).join(' ');
211
+ tagStart.textContent = `<${
212
+ sourceNode.nodeName.toLowerCase()
213
+ }${
214
+ attrString ? (` ${attrString}`) : ''
215
+ }>`;
216
+ }
217
+
218
+ if (withChildren) {
219
+ tagEnd = newElement('span');
220
+ addClass(tagEnd, 'adi-end-node');
221
+ tagEnd.textContent = `</${sourceNode.nodeName.toLowerCase()}>`;
222
+ }
223
+ } else {
224
+ tagStart.textContent = sourceNode.nodeName.toLowerCase();
225
+ }
226
+
227
+ }
228
+
229
+ elem.appendChild(tagStart);
230
+ /*
231
+ const icon = document.createElement('span');
232
+ icon.textContent = 'x';
233
+ icon.classList.add(('icon'))
234
+ const icon2 = document.createElement('span');
235
+ icon2.textContent = '<-';
236
+ icon2.classList.add(('icon'))
237
+ elem.appendChild(icon);
238
+ elem.appendChild(icon2);
239
+ */
240
+ if (sourceNode.nodeName.startsWith('FX-')) {
241
+ tagStart.classList.add('fore-node');
242
+ tagStart.classList.add(sourceNode.nodeName.toLowerCase());
243
+ }
244
+
245
+ if (tagEnd) {
246
+ elem.appendChild(tagEnd);
247
+
248
+ if (sourceNode.nodeName.startsWith('FX-')) {
249
+ tagEnd.classList.add('fore-node');
250
+ }
251
+ }
252
+
253
+ return elem;
254
+ }
255
+ return null;
256
+ }
257
+
258
+ // Renders the DOM Tree view
259
+ drawDOM(root, elem, isRoot) {
260
+ if (typeof root !== 'object') {
261
+ throw new Error(`drawDOM: Expected argument root of type object, ${typeof root} given.`);
262
+ }
263
+
264
+ let newNode = null;
265
+ let isOpen = true;
266
+
267
+ const adiNode = elem.nodeName.startsWith('FX-') ? `adi-node ${node.nodeName.toLowerCase()}`:'';
268
+
269
+ if (isRoot && this.options.nodeTypes.indexOf(root.nodeType) !== -1) {
270
+ elem.innerHTML = '';
271
+ newNode = this.newTreeNode(root);
272
+
273
+ if (this.hasRequiredNodes(root)) {
274
+
275
+ newNode.appendChild(newElement('ul', {'data-open': true, class:adiNode}));
276
+
277
+ addClass(newNode.querySelector('.adi-trigger'), 'opened');
278
+ }
279
+
280
+ elem.appendChild(newNode);
281
+ elem = elem.querySelector('ul');
282
+ }
283
+
284
+ // recursive DOM traversal
285
+ for (let i = 0, len = root.childNodes.length; i < len; i += 1) {
286
+ const node = root.childNodes[i];
287
+ const withChildren = this.hasRequiredNodes(node);
288
+
289
+ if (this.options.nodeTypes.indexOf(node.nodeType) !== -1) {
290
+ newNode = this.newTreeNode(node);
291
+
292
+ if (newNode) {
293
+ if (withChildren) {
294
+ if (this.options.foldText) {
295
+ isOpen = !containsOnlyText(node, true);
296
+ } else {
297
+ isOpen = true;
298
+ }
299
+
300
+ if (node.nodeName === 'HEAD') isOpen = false;
301
+ if (node.nodeName === 'SELECT') isOpen = false;
302
+ if (node.nodeName === 'FX-INSTANCE') isOpen = false;
303
+ /*
304
+ if(this.options.closedElements.includes(node.nodeName.toLowerCase())){
305
+ isOpen = false;
306
+ }
307
+ */
308
+
309
+ if (node.nodeType === Node.DOCUMENT_NODE) {
310
+ newNode.appendChild(newElement('ul', {'data-open': isOpen, class:adiNode}));
311
+ } else {
312
+ newNode.insertBefore(newElement('ul', {'data-open': isOpen,class:adiNode}), newNode.lastChild);
313
+ }
314
+
315
+ addClass(newNode.querySelector('.adi-trigger'), isOpen ? 'opened' : 'closed');
316
+ }
317
+
318
+ elem.appendChild(newNode);
319
+
320
+ if (this.getSelected() === node) {
321
+ const span = newNode.querySelector('span.adi-normal-node');
322
+ span?.classList?.add('adi-active-node');
323
+ this.activeElement = span;
324
+ newNode.scrollIntoView({block:'nearest', behavior: 'instant'});
325
+ }
326
+
327
+
328
+ if (withChildren) {
329
+ this.drawDOM(node, newNode.querySelector('ul'), false);
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ // Show/hide the options view
337
+ toggleOptions() {
338
+ if (this.optsView.className.indexOf('adi-hidden') !== -1) {
339
+ removeClass(this.optsView, 'adi-hidden');
340
+ } else {
341
+ addClass(this.optsView, 'adi-hidden');
342
+ this.attrView.querySelector('.adi-content').innerHTML = '';
343
+ this.refreshUI();
344
+ this.drawDOM(document, this.domView.querySelector('.adi-tree-view'), true);
345
+ if (this.options.saving) {
346
+ this.saveOptions();
347
+ } else {
348
+ window.localStorage.removeItem('ADI.options');
349
+ }
350
+ }
351
+ }
352
+
353
+ // Renders the UI
354
+ drawUI(rootElement) {
355
+ this.uiView = newElement('div', {
356
+ id: 'adi-wrapper',
357
+ class: this.options.transparent ? 'transparent' : '',
358
+ });
359
+ this.domView = newElement('div', {id: 'adi-dom-view'});
360
+ const domViewContent = newElement('div', {class: 'adi-content', id: 'detailsView'});
361
+ // this.attrView.appendChild(newElement('fx-fore', {src: './lab/inspector-view.html'}));
362
+
363
+
364
+ // const horizSplit = newElement('div', {id: 'adi-horiz-split'});
365
+ const domTree = newElement('ul', {class: 'adi-tree-view'});
366
+ const domPathWrap = newElement('div', {class: 'adi-path-wrap'});
367
+ const domPathScrollLeft = newElement('span', {class: 'adi-path-left'});
368
+ const domPathScrollRight = newElement('span', {class: 'adi-path-right'});
369
+ this.menuView = newElement('div', {id: 'adi-panel'});
370
+ const naviButtons = newElement('div', {class: 'adi-menu-wrap'});
371
+ const naviConfig = newElement('a', {class: 'adi-menu-config', title: 'Settings'});
372
+ const naviLookup = newElement('a', {class: 'adi-menu-lookup', title: 'Lookup tool'});
373
+
374
+ // this.horizSplit = horizSplit;
375
+
376
+ this.optsView = drawOptions();
377
+
378
+ // put UI together
379
+ domViewContent.appendChild(domTree);
380
+ this.domView.appendChild(this.menuView);
381
+ this.domView.appendChild(domViewContent);
382
+
383
+ domPathWrap.appendChild(domPathScrollLeft);
384
+ domPathWrap.appendChild(domPathScrollRight);
385
+ naviButtons.appendChild(naviLookup);
386
+ naviButtons.appendChild(naviConfig);
387
+ this.menuView.appendChild(domPathWrap);
388
+ this.menuView.appendChild(naviButtons);
389
+ // this.uiView.appendChild(this.menuView);
390
+ this.uiView.appendChild(this.optsView);
391
+ this.uiView.appendChild(this.domView);
392
+ if (!this.isInstanceViewer) {
393
+ this.attrView = newElement('div', {id: 'adi-attr-view'});
394
+ const attrViewContent = newElement('div', {class: 'adi-content'});
395
+ this.attrView.appendChild(attrViewContent);
396
+ this.uiView.appendChild(this.attrView);
397
+ }
398
+
399
+ // this.uiView.appendChild(horizSplit);
400
+ // wrapper.appendChild(naviWrap);
401
+
402
+ // cache UI object and append to the DOM
403
+
404
+ rootElement.appendChild(this.uiView);
405
+ // document.querySelector('#inspector').appendChild(wrapper);
406
+
407
+ this.refreshUI(true);
408
+ }
409
+
410
+ // Refreshes the global UI
411
+ refreshUI(refreshOpts) {
412
+ if (this.uiView === null) {
413
+ return;
414
+ }
415
+
416
+ // load options if requested (e.g. before the first UI refresh)
417
+ if (refreshOpts) {
418
+ this.loadOptions();
419
+ }
420
+
421
+ // Options view refresh
422
+ if (refreshOpts) {
423
+ this.optsView.querySelector('[data-opt="transparent"]').checked = this.options.transparent;
424
+ this.optsView.querySelector('[data-opt="saving"]').checked = this.options.saving;
425
+ this.optsView.querySelector(
426
+ '[data-opt="omitEmptyText"]',
427
+ ).checked = this.options.omitEmptyText;
428
+ this.optsView.querySelector('[data-opt="makeVisible"]').checked = this.options.makeVisible;
429
+ this.optsView.querySelector('[data-opt="foldText"]').checked = this.options.foldText;
430
+ this.optsView.querySelector('[data-opt="nodeTypes-3"]').checked =
431
+ this.options.nodeTypes.indexOf(3) !== -1;
432
+ this.optsView.querySelector('[data-opt="nodeTypes-8"]').checked =
433
+ this.options.nodeTypes.indexOf(8) !== -1;
434
+ // this.optsView.querySelector('[data-opt="nodeTypes-1"]').checked = this.options.nodeTypes.indexOf(1) !== -1;
435
+ // this.optsView.querySelector('[data-opt="nodeTypes-9"]').checked = this.options.nodeTypes.indexOf(9) !== -1;
436
+ }
437
+
438
+ // UI appearance refresh
439
+ this.uiView.className = this.options.transparent ? 'transparent' : '';
440
+ // this.uiView.style.display = this.options.visible ? 'grid' : 'none';
441
+ // this.domView.style.height = `${this.options.split}%`;
442
+ // this.attrView.style.height = `${100 - this.options.split}%`;
443
+ this.domView.querySelector('.adi-content').style.height = `${this.domView.clientHeight}px`;
444
+ if (!this.isInstanceViewer) {
445
+ this.attrView.querySelector('.adi-content').style.height = `${this.attrView.clientHeight -
446
+ this.menuView.clientHeight}px`
447
+ }
448
+
449
+ addClass(this.uiView, this.options.align);
450
+ }
451
+
452
+ // UI visibility toggle handler
453
+ toggleVisibilityUI() {
454
+ if (this.uiView === null) {
455
+ return;
456
+ }
457
+
458
+ this.uiView.style.display = this.options.visible ? 'none' : 'block';
459
+ this.options.visible = !this.options.visible;
460
+ this.saveOptions();
461
+ }
462
+
463
+ // Renders the attribute view
464
+ drawAttrs(elem) {
465
+ if (this.isInstanceViewer) {
466
+ return;
467
+ }
468
+ const content = this.attrView.querySelector('.adi-content');
469
+ // prepare attributes
470
+ content.innerHTML = '';
471
+
472
+ const header = document.createElement('header');
473
+ header.innerText = 'Attributes';
474
+ content.appendChild(header);
475
+
476
+ // todo: hook element-def.json in here
477
+ /*
478
+ if (elem.nodeName.startsWith('FX-')) {
479
+ console.log('got a fore element');
480
+ const {properties} = elem.constructor;
481
+ Object.keys(properties).forEach(propertyName => {
482
+
483
+ const property = properties[propertyName];
484
+ if (!property || property.hidden) {
485
+ return;
486
+ }
487
+ const row = content.appendChild(newElement('span', {class: 'adi-attr'}));
488
+
489
+ switch (property.type) {
490
+ case 'referencedNode': {
491
+ row.innerHTML = `<label>${propertyName}: <button>${elem[propertyName]?.nodeName}</button></label>`;
492
+ const button = row.querySelector('button');
493
+ button.addEventListener(
494
+ 'click', () => this.handleLookup({detail: {target: elem[propertyName]}})
495
+ );
496
+ break;
497
+ }
498
+ case Number: {
499
+ row.innerHTML = `<label>${propertyName}: <input type="number" data-attr="${propertyName}" value="${elem[propertyName]}"></label>`;
500
+ break;
501
+ }
502
+ case String: {
503
+ if (property.valueSpace) {
504
+ row.innerHTML = `<label>${propertyName}: <select data-attr="${propertyName}" value="${elem[propertyName]}">${property.valueSpace.map(value => `<option>${value}</option>`)}</label>`;
505
+ break;
506
+
507
+ }
508
+ row.innerHTML = `<label>${propertyName}: <input type="text" data-attr="${propertyName}" value="${elem[propertyName]}"></label>`;
509
+ break;
510
+ }
511
+ case Boolean: {
512
+ if (property.valueSpace) {
513
+ row.innerHTML = `<label>${propertyName}: <input type="checkbox" data-attr="${propertyName}" value="${elem[propertyName]}"></input></label>`;
514
+
515
+ }
516
+ break;
517
+ }
518
+ case Object: {
519
+ try {
520
+ row.innerHTML = `<label>${propertyName}: <code>${JSON.stringify(elem[propertyName])}</code></label>`;
521
+ } catch (err) {
522
+ row.innerHTML = `<label>${propertyName}: <code>Unserializable</code></label>`;
523
+ }
524
+ break;
525
+ }
526
+ case Map: {
527
+ try {
528
+ row.innerHTML = `<label>${propertyName}: <code>${JSON.stringify(elem[propertyName])}</code></label>`;
529
+ } catch (err) {
530
+ row.innerHTML = `<label>${propertyName}: <code>Unserializable</code></label>`;
531
+ }
532
+ break;
533
+ }
534
+ default: {
535
+ row.innerHTML = `<label>${propertyName}: Unknown type ${property.type}</label>`;
536
+ }
537
+ }
538
+ });
539
+ } else {
540
+ */
541
+ [...elem.attributes].forEach(attr => {
542
+ if(attr.name !== 'style'){
543
+ content.appendChild(drawAttrRow(attr.name, attr.value));
544
+ }
545
+ })
546
+ // }
547
+ }
548
+
549
+ // Handles attribute changes
550
+ changeAttribute(e) {
551
+ const target = e ? e.target : window.event.srcElement;
552
+ const attr = target.getAttribute('data-attr');
553
+ const val = target.value;
554
+ const elem = this.getSelected();
555
+
556
+ // remove attribute if the new value is empty
557
+ if (val === '') {
558
+ elem.removeAttribute(attr);
559
+ } else {
560
+ elem.setAttribute(attr, val);
561
+ }
562
+ }
563
+
564
+ // Handles option changes
565
+ changeOption(e) {
566
+ const target = e ? e.target : window.event.srcElement;
567
+ const data = target.getAttribute('data-opt');
568
+ const val = target.checked;
569
+
570
+ if (data.indexOf('nodeTypes') !== -1) {
571
+ const type = parseInt(data.match(/\d+/)[0], 10);
572
+
573
+ if (val) {
574
+ this.options.nodeTypes.push(type);
575
+ } else {
576
+ this.options.nodeTypes.splice(this.options.nodeTypes.indexOf(type), 1);
577
+ }
578
+ } else {
579
+ this.options[data] = val;
580
+ }
581
+ }
582
+
583
+ // Key events processing
584
+ processKey(e) {
585
+ e = e || window.event;
586
+ const code = e.keyCode || e.which;
587
+
588
+ switch (code) {
589
+ case 272: // ctrl + alt + d
590
+ this.toggleVisibilityUI();
591
+ break;
592
+ default:
593
+ break;
594
+ }
595
+ }
596
+
597
+ // Vertical splitter resize handler
598
+ verticalResize(e) {
599
+ if (!this.vertResizing) {
600
+ return;
601
+ }
602
+
603
+ e = e || window.event;
604
+ document.documentElement.style.cursor = 'e-resize';
605
+ const nWidth = this.options.width + this.xPos - e.clientX;
606
+
607
+ if (nWidth >= this.options.minWidth) {
608
+ this.options.width = nWidth;
609
+ this.xPos = e.clientX;
610
+ this.refreshUI();
611
+ this.saveOptions();
612
+ }
613
+
614
+ }
615
+
616
+ // Horizontal splitter resize handler
617
+ horizontalResize(e) {
618
+ if (!this.horizResizing) {
619
+ return;
620
+ }
621
+
622
+ e = e || window.event;
623
+ document.documentElement.style.cursor = 'n-resize';
624
+ const nSplit = Math.floor((e.clientY / this.uiView.clientHeight) * 100);
625
+
626
+ if (nSplit >= this.options.minSplit && nSplit <= 100 - this.options.minSplit) {
627
+ this.options.split = nSplit;
628
+ this.refreshUI();
629
+ this.saveOptions();
630
+ }
631
+ }
632
+
633
+ processExecuteAction(e) {
634
+ this.refreshUI();
635
+ }
636
+
637
+ // Handles active element selection
638
+ handleActive(e) {
639
+ let target = e ? e.detail?.target || e.target : window.event.srcElement;
640
+ const active = this.domView.querySelector('.adi-active-node');
641
+
642
+ if (active) {
643
+ removeClass(active, 'adi-active-node');
644
+ }
645
+
646
+ // clicked on normal-node or end-node?
647
+ if(!target || target.nodeType === Node.DOCUMENT_NODE) return;
648
+ if (target && target.classList && target.classList.contains('adi-end-node')) {
649
+ target = target.parentNode.querySelector('.adi-normal-node');
650
+ }
651
+
652
+ this.activeElement = target;
653
+ addClass(target, 'adi-active-node');
654
+
655
+ /*
656
+ e.target.dispatchEvent(
657
+ new CustomEvent('handle-active', {
658
+ composed: true,
659
+ bubbles: true,
660
+ detail: {active: this.activeElement, selected: this.getSelected()},
661
+ }),
662
+ );
663
+ */
664
+
665
+ // make it visible (scroll)
666
+ if (this.options.makeVisible) {
667
+ const wrap = this.domView.querySelector('.adi-content');
668
+ wrap.scrollIntoView({block:'center', behavior: 'instant'})
669
+ }
670
+ const selected = this.getSelected();
671
+ this.drawAttrs(selected);
672
+
673
+ if (selected && typeof selected.getModelItem === 'function' && selected.getModelItem()?.node) {
674
+ let selectedElement = selected.modelItem.node;
675
+ if (selectedElement?.nodeType === Node.ATTRIBUTE_NODE) {
676
+ selectedElement = selectedElement.ownerElement;
677
+ }
678
+ window.document.dispatchEvent(new CustomEvent('log-active-element', {detail: {target: selectedElement}}));
679
+ }
680
+ // window.document.dispatchEvent(new CustomEvent('log-active-element', {detail: {target: selected}}));
681
+ }
682
+
683
+
684
+ // Highlights an element on page
685
+ highlightElement(event) {
686
+ // console.log('highlight',e);
687
+ let sourceNode = event ? event.target : window.event.srcElement;
688
+
689
+ if (sourceNode.classList.contains('adi-end-node')) {
690
+ sourceNode = sourceNode.parentNode.querySelector('.adi-normal-node');
691
+ }
692
+
693
+ const inspectorNode = this.sourceNodeByInspectorNodeLookup.get(sourceNode);
694
+
695
+ if (!inspectorNode || inspectorNode.ownerDocument !== window.document) {
696
+ // Not in HTML: ignore
697
+ return;
698
+ }
699
+
700
+ if (inspectorNode) {
701
+ if (event.type === 'mouseover') {
702
+ this.styleBackup = inspectorNode.getAttribute('style') || '';
703
+ inspectorNode.setAttribute('style', `outline: 2px solid blue; ${this.styleBackup}`);
704
+ } else if (this.styleBackup === '') {
705
+ inspectorNode.removeAttribute('style');
706
+ } else {
707
+ inspectorNode.setAttribute('style', this.styleBackup);
708
+ }
709
+ }
710
+ }
711
+
712
+ // Handles element lookup on page
713
+ handleLookup(e) {
714
+ const target = e ? e.detail?.target || e.target : window.event.srcElement;
715
+
716
+ if (!this.document.contains(target)) {
717
+ // Targetted at somewhere else!!!
718
+ return;
719
+ }
720
+ if (target.nodeType === Node.DOCUMENT_NODE) {
721
+ // Targetted at the document node. Nothing to highlight
722
+ return;
723
+ }
724
+
725
+ if (target.className.indexOf('adi-menu-lookup') !== -1) {
726
+ // enable/disable interactive lookup
727
+ if (this.elemLookup) {
728
+ removeClass(target, 'adi-active');
729
+ this.elemLookup = false;
730
+ this.removeEvent(document.body, 'mouseover', this.handleLookup, true);
731
+ this.removeEvent(document.body, 'mouseout', this.handleLookup, true);
732
+ this.removeEvent(document.body, 'click', this.handleLookup, true);
733
+ return;
734
+ }
735
+ addClass(target, 'adi-active');
736
+ this.elemLookup = true;
737
+ this.addEventDelegate(document.body,
738
+ 'mouseover',
739
+ this.handleLookup,
740
+ false,
741
+ '*',
742
+ true,
743
+ 'adi-wrapper',
744
+ );
745
+ this.addEventDelegate(document.body,
746
+ 'mouseout',
747
+ this.handleLookup,
748
+ false,
749
+ '*',
750
+ true,
751
+ 'adi-wrapper',
752
+ );
753
+ this.addEventDelegate(document.body, 'click', this.handleLookup, false, '*', true, 'adi-wrapper');
754
+ return;
755
+ }
756
+ // handle lookup events
757
+ if (e.type === 'mouseover') {
758
+ this.styleBackup = target.getAttribute('style') || '';
759
+ target.setAttribute('style', `outline: 1px dashed red; ${this.styleBackup}`);
760
+ return;
761
+ }
762
+ if (e.type === 'mouseout') {
763
+ target.setAttribute('style', this.styleBackup);
764
+ return;
765
+ }
766
+ this.elemLookup = false;
767
+ removeClass(this.menuView.querySelector('.adi-menu-lookup'), 'adi-active');
768
+ target.setAttribute('style', this.styleBackup);
769
+ this.removeEvent(document.body, 'mouseover', this.handleLookup, true);
770
+ this.removeEvent(document.body, 'mouseout', this.handleLookup, true);
771
+ this.removeEvent(document.body, 'click', this.handleLookup, true);
772
+ pauseEvent(e);
773
+
774
+ // find corresponding node in the DOM view
775
+ const active = this.sourceNodeByInspectorNodeLookup.get(target);
776
+
777
+ // activate it
778
+ if(!active) return;
779
+ if (active) {
780
+ active.click();
781
+ }
782
+
783
+ // open the whole path in DOM view
784
+ if (!active.parentNode) return;
785
+ let node = active.parentNode;
786
+ let tmp;
787
+
788
+ if (node.querySelector('ul')) {
789
+ node.querySelector('ul').setAttribute('data-open', 'true');
790
+ }
791
+ while (node !== this.domView.querySelector('.adi-content')) {
792
+ if (node.className.indexOf('adi-node') !== -1) {
793
+ tmp = node.querySelector('.adi-trigger');
794
+ if (tmp) {
795
+ removeClass(tmp, 'closed');
796
+ addClass(tmp, 'opened');
797
+ }
798
+
799
+ node = node.parentNode; // ul node
800
+ node.setAttribute('data-open', 'true');
801
+ }
802
+
803
+ node = node.parentNode;
804
+ }
805
+
806
+ // make it visible (scroll)
807
+ if (this.options.makeVisible) {
808
+ active.scrollIntoView({behavior: "instant", block: "nearest", inline: "nearest"});
809
+ }
810
+ target.scrollIntoView({behavior: "instant", block: "nearest", inline: "nearest"});
811
+ }
812
+
813
+ // Simple cross-browser event handler that enables simple event delegation.
814
+ // Note that the selector must be a string and no nesting is supported.
815
+ // Selector is expected to be in one of formats listed below and works for all children
816
+ // in the particular element.
817
+ // Store parameter enables storing the reference to custom event handler.
818
+ // Exclude parameter will exclude the particular element and all of its children, this works
819
+ // only for id selectors.
820
+ // Selector formats: tag name ("div"), class name (".my-class"), id ("#my-id") and any ("*").
821
+
822
+ addEventDelegate(elem, evt, fn, capture, selector, store, exclude) {
823
+ // custom event handler is registered
824
+ const handler = e => {
825
+ // check if target corresponds to the selector
826
+ const target = e ? e.target : window.event.srcElement;
827
+ const sel = selector.substr(1);
828
+ let delegate = false;
829
+
830
+ if (exclude) {
831
+ let node = target;
832
+
833
+ while (node !== document) {
834
+ if (node.id === exclude) {
835
+ return;
836
+ }
837
+
838
+ node = node.parentNode;
839
+ }
840
+ }
841
+
842
+ // should the event be delegated?
843
+ if (selector.indexOf('#') === 0) {
844
+ // ID
845
+ delegate = target.id === sel;
846
+ } else if (selector.indexOf('.') === 0) {
847
+ // class
848
+ delegate = target.className.indexOf(sel) !== -1;
849
+ } else if (selector === '*') {
850
+ // any
851
+ delegate = true;
852
+ } else {
853
+ // tag name
854
+ delegate = target.nodeName.toLowerCase() === selector;
855
+ }
856
+
857
+ // delegate the event handling
858
+ if (delegate) {
859
+ fn(e);
860
+ }
861
+ };
862
+ // save the reference
863
+ if (store) {
864
+ this.delegatedEvents.push({
865
+ handle: handler,
866
+ elem,
867
+ fn,
868
+ evt,
869
+ });
870
+ }
871
+
872
+ elem.addEventListener(evt, handler, capture);
873
+ }
874
+
875
+ // Simple cross-browser event removing
876
+ removeEvent(elem, evt, fn, wasDelegated) {
877
+ if (typeof elem !== 'object') {
878
+ throw new Error(`addEvent: Expected argument elem of type object, ${typeof elem} given.`);
879
+ }
880
+
881
+ // try to find stored delegated event
882
+ let stored = null;
883
+ if (wasDelegated) {
884
+ for (let i = 0, len = this.delegatedEvents.length; i < len; i += 1) {
885
+ stored = this.delegatedEvents[i];
886
+ if (stored.elem === elem && stored.evt === evt && stored.fn === fn) {
887
+ fn = stored.handle;
888
+ this.delegatedEvents.splice(i, 1);
889
+ break;
890
+ }
891
+ }
892
+ }
893
+
894
+ // elem.detachEvent(`on${evt}`, fn);
895
+ }
896
+
897
+ // Event registration
898
+ registerEvents() {
899
+ // events for splitters
900
+ /*
901
+ this.horizSplit.addEventListener(
902
+ 'mousedown',
903
+ e => {
904
+ e = e || window.event;
905
+ pauseEvent(e);
906
+ this.horizResizing = true;
907
+ },
908
+ false,
909
+ );
910
+ */
911
+
912
+ const redrawUi = () => {
913
+ if (this.instanceId !== '#document') {
914
+ const instance = window.document.querySelector(`#${this.instanceId}`);
915
+ this.document = instance.getInstanceData();
916
+ }
917
+ this.drawDOM(this.document, this.domView.querySelector('.adi-tree-view'), true);
918
+ };
919
+
920
+ // Update UI when something with instances changed
921
+ document.addEventListener('instance-loaded', redrawUi);
922
+ // Update UI when some value changes
923
+ document.addEventListener('value-changed', redrawUi);
924
+ // Update UI when we're done loading and all repeats are done
925
+ document.addEventListener('ready', redrawUi);
926
+
927
+ document.addEventListener(
928
+ 'mouseup',
929
+ () => {
930
+ document.documentElement.style.cursor = 'default';
931
+ this.vertResizing = false;
932
+ this.horizResizing = false;
933
+ },
934
+ false,
935
+ );
936
+
937
+ document.addEventListener('mousemove', event => this.verticalResize(event), false);
938
+ document.addEventListener('mousemove', event => this.horizontalResize(event), false)
939
+ ;
940
+ // window resize
941
+ window.addEventListener('resize', event => this.refreshUI(event), false);
942
+
943
+ // keypress events
944
+ document.addEventListener('keypress', event => this.processKey(event), false);
945
+
946
+ // fore action events
947
+ document.addEventListener('log-active-element', event => this.handleLookup(event), false);
948
+
949
+ // Dom view folding handler
950
+ const handleFolding = e => {
951
+ const target = e ? e.target : window.event.srcElement;
952
+ const ul = target.parentNode.querySelector('ul');
953
+
954
+ if (ul.getAttribute('data-open') === 'true') {
955
+ removeClass(target, 'opened');
956
+ addClass(target, 'closed');
957
+ ul.setAttribute('data-open', 'false');
958
+ } else {
959
+ removeClass(target, 'closed');
960
+ addClass(target, 'opened');
961
+ ul.setAttribute('data-open', 'true');
962
+ }
963
+ };
964
+
965
+ // dom tree view folding
966
+ this.addEventDelegate(this.domView, 'click', handleFolding, false, '.adi-trigger');
967
+
968
+ // active element
969
+ this.addEventDelegate(this.domView, 'click', (event) => this.handleActive(event), false, '.adi-normal-node');
970
+ this.addEventDelegate(this.domView, 'click', (event) => this.handleActive(event), false, '.adi-end-node');
971
+
972
+ // matching tag highlighting
973
+ this.addEventDelegate(
974
+ this.domView,
975
+ 'mouseover',
976
+ e => {
977
+ const target = e ? e.target : window.event.srcElement;
978
+ addClass(target.parentNode.querySelector('.adi-normal-node'), 'hover');
979
+ },
980
+ false,
981
+ '.adi-end-node',
982
+ );
983
+ this.addEventDelegate(
984
+ this.domView,
985
+ 'mouseout',
986
+ e => {
987
+ const target = e ? e.target : window.event.srcElement;
988
+ removeClass(target.parentNode.querySelector('.adi-normal-node'), 'hover');
989
+ },
990
+ false,
991
+ '.adi-end-node',
992
+ );
993
+
994
+ // page element highlighting
995
+ this.addEventDelegate(this.domView, 'mouseover', (event) => this.highlightElement(event), false, '.adi-end-node');
996
+ this.addEventDelegate(
997
+ this.domView,
998
+ 'mouseover',
999
+ (event) => this.highlightElement(event),
1000
+ false,
1001
+ '.adi-normal-node',
1002
+ );
1003
+ this.addEventDelegate(this.domView, 'mouseout', (event) => this.highlightElement(event), false, '.adi-end-node');
1004
+ this.addEventDelegate(
1005
+ this.domView,
1006
+ 'mouseout',
1007
+ (event) => this.highlightElement(event),
1008
+ false,
1009
+ '.adi-normal-node',
1010
+ );
1011
+
1012
+ // element lookup
1013
+ this.menuView.querySelector('.adi-menu-lookup').addEventListener(
1014
+ 'click',
1015
+ event => this.handleLookup(event),
1016
+ false,
1017
+ );
1018
+
1019
+ document.addEventListener('handle-active', (e) => {
1020
+ if (e.detail.selected === this.getSelected()) {
1021
+ // We caused this. ignore
1022
+ return;
1023
+ }
1024
+ const {selected} = e.detail;
1025
+ const target = this.sourceNodeByInspectorNodeLookup.get(selected);
1026
+ // make it visible (scroll)
1027
+ if (this.options.makeVisible) {
1028
+ const wrap = this.domView.querySelector('.adi-content');
1029
+ if (target.offsetTop >= wrap.clientHeight || target.offsetTop <= wrap.scrollTop) {
1030
+ wrap.scrollTop = target.offsetTop - Math.floor(wrap.clientHeight / 2);
1031
+ }
1032
+ }
1033
+
1034
+ this.drawAttrs(this.getSelected());
1035
+ });
1036
+
1037
+ document.addEventListener('execute-action',(e) => this.processExecuteAction(event),{capture:true});
1038
+
1039
+ // options events
1040
+ this.addEventDelegate(this.optsView, 'change', (event) => this.changeOption(event), false, 'input');
1041
+ this.addEventDelegate(this.optsView, 'click', (event) => this.toggleOptions(event), false, '.adi-opt-close');
1042
+ this.menuView.querySelector('.adi-menu-config').addEventListener(
1043
+ 'click',
1044
+ (event) => this.toggleOptions(event),
1045
+ false,
1046
+ );
1047
+
1048
+ // attributes events
1049
+ if(!this.isInstanceViewer) {
1050
+ this.addEventDelegate(this.attrView, 'change', this.changeAttribute, false, 'input');
1051
+ }
1052
+ }
1053
+ }
1054
+
1055
+ export default ADI;
1056
+