diff options
Diffstat (limited to 'devtools/client/shared/components/reps/reps/grip-map.js')
-rw-r--r-- | devtools/client/shared/components/reps/reps/grip-map.js | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/devtools/client/shared/components/reps/reps/grip-map.js b/devtools/client/shared/components/reps/reps/grip-map.js new file mode 100644 index 0000000000..d0157137ef --- /dev/null +++ b/devtools/client/shared/components/reps/reps/grip-map.js @@ -0,0 +1,234 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); + const { span } = require("devtools/client/shared/vendor/react-dom-factories"); + + const { + lengthBubble, + } = require("devtools/client/shared/components/reps/shared/grip-length-bubble"); + const { + interleave, + wrapRender, + ellipsisElement, + } = require("devtools/client/shared/components/reps/reps/rep-utils"); + const PropRep = require("devtools/client/shared/components/reps/reps/prop-rep"); + const { + MODE, + } = require("devtools/client/shared/components/reps/reps/constants"); + + /** + * Renders an map. A map is represented by a list of its + * entries enclosed in curly brackets. + */ + + GripMap.propTypes = { + object: PropTypes.object, + mode: PropTypes.oneOf(Object.values(MODE)), + isInterestingEntry: PropTypes.func, + onDOMNodeMouseOver: PropTypes.func, + onDOMNodeMouseOut: PropTypes.func, + onInspectIconClick: PropTypes.func, + title: PropTypes.string, + shouldRenderTooltip: PropTypes.bool, + }; + + function GripMap(props) { + const { mode, object, shouldRenderTooltip } = props; + + const config = { + "data-link-actor-id": object.actor, + className: "objectBox objectBox-object", + title: shouldRenderTooltip ? getTooltip(object, props) : null, + }; + + const title = getTitle(props, object); + const isEmpty = getLength(object) === 0; + + if (isEmpty || mode === MODE.TINY) { + return span(config, title); + } + + const propsArray = safeEntriesIterator( + props, + object, + maxLengthMap.get(mode) + ); + + return span( + config, + title, + span( + { + className: "objectLeftBrace", + }, + " { " + ), + ...interleave(propsArray, ", "), + span( + { + className: "objectRightBrace", + }, + " }" + ) + ); + } + + function getTitle(props, object) { + const title = + props.title || (object && object.class ? object.class : "Map"); + return span( + { + className: "objectTitle", + }, + title, + lengthBubble({ + object, + mode: props.mode, + maxLengthMap, + getLength, + showZeroLength: true, + }) + ); + } + + function getTooltip(object, props) { + const tooltip = + props.title || (object && object.class ? object.class : "Map"); + return `${tooltip}(${getLength(object)})`; + } + + function safeEntriesIterator(props, object, max) { + max = typeof max === "undefined" ? 3 : max; + try { + return entriesIterator(props, object, max); + } catch (err) { + console.error(err); + } + return []; + } + + function entriesIterator(props, object, max) { + // Entry filter. Show only interesting entries to the user. + const isInterestingEntry = + props.isInterestingEntry || + ((type, value) => { + return ( + type == "boolean" || + type == "number" || + (type == "string" && !!value.length) + ); + }); + + const mapEntries = + object.preview && object.preview.entries ? object.preview.entries : []; + + let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry); + if (indexes.length < max && indexes.length < mapEntries.length) { + // There are not enough entries yet, so we add uninteresting entries. + indexes = indexes.concat( + getEntriesIndexes( + mapEntries, + max - indexes.length, + (t, value, name) => { + return !isInterestingEntry(t, value, name); + } + ) + ); + } + + const entries = getEntries(props, mapEntries, indexes); + if (entries.length < getLength(object)) { + // There are some undisplayed entries. Then display "…". + entries.push(ellipsisElement); + } + + return entries; + } + + /** + * Get entries ordered by index. + * + * @param {Object} props Component props. + * @param {Array} entries Entries array. + * @param {Array} indexes Indexes of entries. + * @return {Array} Array of PropRep. + */ + function getEntries(props, entries, indexes) { + const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props; + + // Make indexes ordered by ascending. + indexes.sort(function (a, b) { + return a - b; + }); + + return indexes.map((index, i) => { + const [key, entryValue] = entries[index]; + const value = + entryValue.value !== undefined ? entryValue.value : entryValue; + + return PropRep({ + name: key && key.getGrip ? key.getGrip() : key, + equal: " \u2192 ", + object: value && value.getGrip ? value.getGrip() : value, + mode: MODE.TINY, + onDOMNodeMouseOver, + onDOMNodeMouseOut, + onInspectIconClick, + }); + }); + } + + /** + * Get the indexes of entries in the map. + * + * @param {Array} entries Entries array. + * @param {Number} max The maximum length of indexes array. + * @param {Function} filter Filter the entry you want. + * @return {Array} Indexes of filtered entries in the map. + */ + function getEntriesIndexes(entries, max, filter) { + return entries.reduce((indexes, [key, entry], i) => { + if (indexes.length < max) { + const value = entry && entry.value !== undefined ? entry.value : entry; + // Type is specified in grip's "class" field and for primitive + // values use typeof. + const type = ( + value && value.class ? value.class : typeof value + ).toLowerCase(); + + if (filter(type, value, key)) { + indexes.push(i); + } + } + + return indexes; + }, []); + } + + function getLength(grip) { + return grip.preview.size || 0; + } + + function supportsObject(grip) { + return grip?.preview?.kind == "MapLike"; + } + + const maxLengthMap = new Map(); + maxLengthMap.set(MODE.SHORT, 3); + maxLengthMap.set(MODE.LONG, 10); + + // Exports from this module + module.exports = { + rep: wrapRender(GripMap), + supportsObject, + maxLengthMap, + getLength, + }; +}); |