From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../src/components/search/SearchPanel.js | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 devtools/client/netmonitor/src/components/search/SearchPanel.js (limited to 'devtools/client/netmonitor/src/components/search/SearchPanel.js') diff --git a/devtools/client/netmonitor/src/components/search/SearchPanel.js b/devtools/client/netmonitor/src/components/search/SearchPanel.js new file mode 100644 index 0000000000..11301a7d9c --- /dev/null +++ b/devtools/client/netmonitor/src/components/search/SearchPanel.js @@ -0,0 +1,264 @@ +/* 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"; + +const { + Component, + createRef, + createFactory, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { div, span } = dom; +const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js"); +const { + PANELS, +} = require("resource://devtools/client/netmonitor/src/constants.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + connect, +} = require("resource://devtools/client/shared/redux/visibility-handler-connect.js"); +const TreeViewClass = require("resource://devtools/client/shared/components/tree/TreeView.js"); +const TreeView = createFactory(TreeViewClass); +const LabelCell = createFactory( + require("resource://devtools/client/shared/components/tree/LabelCell.js") +); +const { + SearchProvider, +} = require("resource://devtools/client/netmonitor/src/components/search/search-provider.js"); +const Toolbar = createFactory( + require("resource://devtools/client/netmonitor/src/components/search/Toolbar.js") +); +const StatusBar = createFactory( + require("resource://devtools/client/netmonitor/src/components/search/StatusBar.js") +); +const { + limitTooltipLength, +} = require("resource://devtools/client/netmonitor/src/utils/tooltips.js"); +// There are two levels in the search panel tree hierarchy: +// 0: Resource - represents the source request object +// 1: Search Result - represents a match coming from the parent resource +const RESOURCE_LEVEL = 0; +const SEARCH_RESULT_LEVEL = 1; + +/** + * This component is responsible for rendering all search results + * coming from the current search. + */ +class SearchPanel extends Component { + static get propTypes() { + return { + clearSearchResults: PropTypes.func.isRequired, + openSearch: PropTypes.func.isRequired, + closeSearch: PropTypes.func.isRequired, + search: PropTypes.func.isRequired, + caseSensitive: PropTypes.bool, + connector: PropTypes.object.isRequired, + addSearchQuery: PropTypes.func.isRequired, + query: PropTypes.string.isRequired, + results: PropTypes.array, + navigate: PropTypes.func.isRequired, + isDisplaying: PropTypes.bool.isRequired, + }; + } + + constructor(props) { + super(props); + + this.searchboxRef = createRef(); + this.renderValue = this.renderValue.bind(this); + this.renderLabel = this.renderLabel.bind(this); + this.onClickTreeRow = this.onClickTreeRow.bind(this); + this.provider = SearchProvider; + } + + componentDidMount() { + if (this.searchboxRef) { + this.searchboxRef.current.focus(); + } + } + + componentDidUpdate(prevProps) { + if (this.props.isDisplaying && !prevProps.isDisplaying) { + this.searchboxRef.current.focus(); + } + } + + onClickTreeRow(path, event, member) { + if (member.object.parentResource) { + this.props.navigate(member.object); + } + } + + /** + * Custom TreeView label rendering. The search result + * value isn't rendered in separate column, but in the + * same column as the label (to save space). + */ + renderLabel(props) { + const { member } = props; + const level = member.level || 0; + const className = level == RESOURCE_LEVEL ? "resourceCell" : "resultCell"; + + // Customize label rendering by adding a suffix/value + const renderSuffix = () => { + return dom.span( + { + className, + }, + " ", + this.renderValue(props) + ); + }; + + return LabelCell({ + ...props, + title: + member.level == 1 + ? limitTooltipLength(member.object.value) + : this.provider.getResourceTooltipLabel(member.object), + renderSuffix, + }); + } + + renderTree() { + const { results } = this.props; + return TreeView({ + object: results, + provider: this.provider, + expandableStrings: false, + renderLabelCell: this.renderLabel, + columns: [], + onClickRow: this.onClickTreeRow, + }); + } + + /** + * Custom tree value rendering. This method is responsible for + * rendering highlighted query string within the search result + * result tree. + */ + renderValue(props) { + const { member } = props; + const { query, caseSensitive } = this.props; + + // Handle only second level (zero based) that displays + // the search result. Find the query string inside the + // search result value (`props.object`) and render it + // within a span element with proper class name. + // level 0 = resource name + if (member.level === SEARCH_RESULT_LEVEL) { + const { object } = member; + + // Handles multiple matches in a string + if (object.startIndex && object.startIndex.length > 1) { + let indexStart = 0; + const allMatches = object.startIndex.map((match, index) => { + if (index === 0) { + indexStart = match - 50; + } + + const highlightedMatch = [ + span( + { key: "match-" + match }, + object.value.substring(indexStart, match - query.length) + ), + span( + { + className: "query-match", + key: "match-" + match + "-highlight", + }, + object.value.substring(match - query.length, match) + ), + ]; + + indexStart = match; + + return highlightedMatch; + }); + + return span( + { + title: limitTooltipLength(object.value), + }, + allMatches + ); + } + + const indexStart = caseSensitive + ? object.value.indexOf(query) + : object.value.toLowerCase().indexOf(query.toLowerCase()); + const indexEnd = indexStart + query.length; + + // Handles a match in a string + if (indexStart >= 0) { + return span( + { title: limitTooltipLength(object.value) }, + span({}, object.value.substring(0, indexStart)), + span( + { className: "query-match" }, + object.value.substring(indexStart, indexStart + query.length) + ), + span({}, object.value.substring(indexEnd, object.value.length)) + ); + } + + // Default for key:value matches where query might not + // be present in the value, but found in the key. + return span( + { title: limitTooltipLength(object.value) }, + span({}, object.value) + ); + } + + return this.provider.getValue(member.object); + } + + render() { + const { + openSearch, + closeSearch, + clearSearchResults, + connector, + addSearchQuery, + search, + } = this.props; + return div( + { className: "search-panel", style: { width: "100%" } }, + Toolbar({ + searchboxRef: this.searchboxRef, + openSearch, + closeSearch, + clearSearchResults, + addSearchQuery, + search, + connector, + }), + div( + { className: "search-panel-content", style: { width: "100%" } }, + this.renderTree() + ), + StatusBar() + ); + } +} + +module.exports = connect( + state => ({ + query: state.search.query, + caseSensitive: state.search.caseSensitive, + results: state.search.results, + ongoingSearch: state.search.ongoingSearch, + isDisplaying: state.ui.selectedActionBarTabId === PANELS.SEARCH, + status: state.search.status, + }), + dispatch => ({ + closeSearch: () => dispatch(Actions.closeSearch()), + openSearch: () => dispatch(Actions.openSearch()), + search: () => dispatch(Actions.search()), + clearSearchResults: () => dispatch(Actions.clearSearchResults()), + addSearchQuery: query => dispatch(Actions.addSearchQuery(query)), + navigate: searchResult => dispatch(Actions.navigate(searchResult)), + }) +)(SearchPanel); -- cgit v1.2.3