1// Copyright 2021 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {App} from '../index.mjs'
6
7import {FocusEvent, SelectRelatedEvent} from './events.mjs';
8import {DOM, entriesEquals, ExpandableText, V8CustomElement} from './helper.mjs';
9
10DOM.defineCustomElement('view/property-link-table',
11                        template =>
12                            class PropertyLinkTable extends V8CustomElement {
13  _object;
14  _propertyDict;
15  _instanceLinkButtons = false;
16
17  _showHandler = this._handleShow.bind(this);
18  _showSourcePositionHandler = this._handleShowSourcePosition.bind(this);
19  _showRelatedHandler = this._handleShowRelated.bind(this);
20  _arrayValueSelectHandler = this._handleArrayValueSelect.bind(this);
21
22  constructor() {
23    super(template);
24  }
25
26  set instanceLinkButtons(newValue) {
27    this._instanceLinkButtons = newValue;
28  }
29
30  set propertyDict(propertyDict) {
31    if (entriesEquals(this._propertyDict, propertyDict)) return;
32    if (typeof propertyDict !== 'object') {
33      throw new Error(
34          `Invalid property dict, expected object: ${propertyDict}`);
35    }
36    this._propertyDict = propertyDict;
37    this.requestUpdate();
38  }
39
40  _update() {
41    this._fragment = new DocumentFragment();
42    this._table = DOM.table();
43    for (let key in this._propertyDict) {
44      const value = this._propertyDict[key];
45      this._addKeyValue(key, value);
46    }
47
48    const tableDiv = DOM.div('properties');
49    tableDiv.appendChild(this._table);
50    this._fragment.appendChild(tableDiv);
51    this._createFooter();
52
53    const newContent = DOM.div();
54    newContent.appendChild(this._fragment);
55    this.$('#content').replaceWith(newContent);
56    newContent.id = 'content';
57    this._fragment = undefined;
58  }
59
60  _addKeyValue(key, value) {
61    if (key == 'title') {
62      this._addTitle(value);
63      return;
64    }
65    if (key == '__this__') {
66      this._object = value;
67      return;
68    }
69    const row = this._table.insertRow();
70    row.insertCell().innerText = key;
71    const cell = row.insertCell();
72    if (value == undefined) return;
73    if (Array.isArray(value)) {
74      cell.appendChild(this._addArrayValue(value));
75      return;
76    } else if (App.isClickable(value)) {
77      cell.className = 'clickable';
78      cell.onclick = this._showHandler;
79      cell.data = value;
80    }
81    if (value.isCode) {
82      cell.classList.add('code');
83    }
84    new ExpandableText(cell, value.toString());
85  }
86
87  _addArrayValue(array) {
88    if (array.length == 0) {
89      return DOM.text('empty');
90    } else if (array.length > 200) {
91      return DOM.text(`${array.length} items`);
92    }
93    const select = DOM.element('select');
94    select.onchange = this._arrayValueSelectHandler;
95    for (let value of array) {
96      const option = DOM.element('option');
97      option.innerText = value === undefined ? '' : value.toString();
98      option.data = value;
99      select.add(option);
100    }
101    return select;
102  }
103
104  _addTitle(value) {
105    const title = DOM.element('h3');
106    title.innerText = value;
107    this._fragment.appendChild(title);
108  }
109
110  _createFooter() {
111    if (this._object === undefined) return;
112    if (!this._instanceLinkButtons) return;
113    const footer = DOM.div('footer');
114    let showButton = footer.appendChild(DOM.button('Show', this._showHandler));
115    showButton.data = this._object;
116    if (this._object.sourcePosition) {
117      let showSourcePositionButton = footer.appendChild(
118          DOM.button('Source Position', this._showSourcePositionHandler));
119      showSourcePositionButton.data = this._object;
120    }
121    let showRelatedButton = footer.appendChild(
122        DOM.button('Show Related', this._showRelatedHandler));
123    showRelatedButton.data = this._object;
124    this._fragment.appendChild(footer);
125  }
126
127  _handleArrayValueSelect(event) {
128    const logEntry = event.currentTarget.selectedOptions[0].data;
129    this.dispatchEvent(new FocusEvent(logEntry));
130  }
131
132  _handleShow(event) {
133    this.dispatchEvent(new FocusEvent(event.currentTarget.data));
134  }
135
136  _handleShowSourcePosition(event) {
137    this.dispatchEvent(new FocusEvent(event.currentTarget.data.sourcePosition));
138  }
139
140  _handleShowRelated(event) {
141    this.dispatchEvent(new SelectRelatedEvent(event.currentTarget.data));
142  }
143});
144