Yarn workspaces

Summary:
1) moved "sonar/desktop/src" to "sonar/desktop/app/src", so "app" is now a separate package containing the core Flipper app code
2) Configured yarn workspaces with the root in "sonar/desktop": app, static, pkg, doctor, headless-tests. Plugins are not included for now, I plan to do this later.

Reviewed By: jknoxville

Differential Revision: D20535782

fbshipit-source-id: 600b2301960f37c7d72166e0d04eba462bec9fc1
This commit is contained in:
Anton Nikolaev
2020-03-20 13:31:37 -07:00
committed by Facebook GitHub Bot
parent 676d7bbd24
commit 863f89351e
340 changed files with 1635 additions and 294 deletions

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Component} from 'react';
import {Elements, DecorateRow} from './elements';
import {ContextMenuExtension} from 'flipper';
import React from 'react';
export type ElementID = string;
export type ElementSearchResultSet = {
query: string;
matches: Set<ElementID>;
};
export type ElementData = {
[name: string]: {
[key: string]:
| string
| number
| boolean
| {
__type__: string;
value: any;
};
};
};
export type ElementAttribute = {
name: string;
value: string;
};
export type ElementExtraInfo = {
linkedNode?: string; // id of linked node in opposite tree
expandWithParent?: boolean;
};
export type Element = {
id: ElementID;
name: string;
expanded: boolean;
children: Array<ElementID>;
attributes: Array<ElementAttribute>;
data: ElementData;
decoration: string;
extraInfo: ElementExtraInfo;
};
export default class ElementsInspector extends Component<{
onElementExpanded: (key: ElementID, deep: boolean) => void;
onElementSelected: (key: ElementID) => void;
onElementHovered:
| ((key: ElementID | undefined | null) => any)
| undefined
| null;
onValueChanged: ((path: Array<string>, val: any) => any) | undefined | null;
selected: ElementID | undefined | null;
focused?: ElementID | undefined | null;
searchResults?: ElementSearchResultSet | undefined | null;
root: ElementID | undefined | null;
elements: {[key: string]: Element};
useAppSidebar?: boolean;
alternateRowColor?: boolean;
contextMenuExtensions?: Array<ContextMenuExtension>;
decorateRow?: DecorateRow;
}> {
static defaultProps = {
alternateRowColor: true,
};
render() {
const {
selected,
focused,
elements,
root,
onElementExpanded,
onElementSelected,
onElementHovered,
searchResults,
alternateRowColor,
contextMenuExtensions,
decorateRow,
} = this.props;
return (
<Elements
onElementExpanded={onElementExpanded}
onElementSelected={onElementSelected}
onElementHovered={onElementHovered}
selected={selected}
focused={focused}
searchResults={searchResults}
root={root}
elements={elements}
alternateRowColor={alternateRowColor}
contextMenuExtensions={contextMenuExtensions}
decorateRow={decorateRow}
/>
);
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React from 'react';
import ReactDOM from 'react-dom';
import {Element, styled} from '../../../ui';
export function VisualizerPortal(props: {
container: HTMLElement;
highlightedElement: string | null;
elements: {[key: string]: Element};
screenshotURL: string;
screenDimensions: {width: number; height: number};
}) {
const element: Element | null | '' =
props.highlightedElement && props.elements[props.highlightedElement];
const position =
element &&
typeof element.data.View?.positionOnScreenX == 'number' &&
typeof element.data.View?.positionOnScreenY == 'number' &&
typeof element.data.View.width === 'object' &&
element.data.View.width.value != null &&
typeof element.data.View.height === 'object' &&
element.data.View.height.value != null
? {
x: element.data.View.positionOnScreenX,
y: element.data.View.positionOnScreenY,
width: element.data.View.width.value,
height: element.data.View.height.value,
}
: null;
return ReactDOM.createPortal(
<Visualizer
screenDimensions={props.screenDimensions}
element={position}
imageURL={props.screenshotURL}
/>,
props.container,
);
}
const VisualizerContainer = styled.div({
position: 'relative',
top: 0,
left: 0,
width: '100%',
height: '100%',
userSelect: 'none',
});
const DeviceImage = styled.img<{
width?: number | string;
height?: number | string;
}>(({width, height}) => ({
width,
height,
userSelect: 'none',
}));
/**
* Component that displays a static picture of a device
* and renders "highlighted" rectangles over arbitrary points on it.
* Used for emulating the layout plugin when a device isn't connected.
*/
function Visualizer(props: {
screenDimensions: {width: number; height: number};
element: {x: number; y: number; width: number; height: number} | null;
imageURL: string;
}) {
const containerRef: React.Ref<HTMLDivElement> = React.createRef();
const imageRef: React.Ref<HTMLImageElement> = React.createRef();
let w: number = 0;
let h: number = 0;
const [scale, updateScale] = React.useState(1);
React.useLayoutEffect(() => {
w = containerRef.current?.offsetWidth || 0;
h = containerRef.current?.offsetHeight || 0;
const xScale = props.screenDimensions.width / w;
const yScale = props.screenDimensions.height / h;
updateScale(Math.max(xScale, yScale));
imageRef.current?.setAttribute('draggable', 'false');
});
return (
<VisualizerContainer ref={containerRef}>
<DeviceImage
ref={imageRef}
src={props.imageURL}
width={props.screenDimensions.width / scale}
height={props.screenDimensions.height / scale}
/>
{props.element && (
<div
style={{
position: 'absolute',
left: props.element.x / scale,
top: props.element.y / scale,
width: props.element.width / scale,
height: props.element.height / scale,
backgroundColor: '#637dff',
opacity: 0.7,
userSelect: 'none',
}}></div>
)}
/>
</VisualizerContainer>
);
}

View File

@@ -0,0 +1,667 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {ElementID, Element, ElementSearchResultSet} from './ElementsInspector';
import {reportInteraction} from '../../../utils/InteractionTracker';
import ContextMenu from '../ContextMenu';
import {PureComponent, ReactElement} from 'react';
import FlexRow from '../FlexRow';
import FlexColumn from '../FlexColumn';
import Glyph from '../Glyph';
import {colors} from '../colors';
import Text from '../Text';
import styled from '@emotion/styled';
import {clipboard, MenuItemConstructorOptions} from 'electron';
import React, {MouseEvent, KeyboardEvent} from 'react';
const ROW_HEIGHT = 23;
const backgroundColor = (props: {
selected: boolean;
focused: boolean;
isQueryMatch: boolean;
even: boolean;
}) => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.isQueryMatch) {
return colors.purpleLight;
} else if (props.focused) {
return '#00CF52';
} else if (props.even) {
return colors.light02;
} else {
return '';
}
};
const backgroundColorHover = (props: {selected: boolean; focused: boolean}) => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return '#00CF52';
} else {
return '#EBF1FB';
}
};
const ElementsRowContainer = styled(ContextMenu)<any>(props => ({
flexDirection: 'row',
alignItems: 'center',
backgroundColor: backgroundColor(props),
color: props.selected || props.focused ? colors.white : colors.grapeDark3,
flexShrink: 0,
flexWrap: 'nowrap',
height: ROW_HEIGHT,
minWidth: '100%',
paddingLeft: (props.level - 1) * 12,
paddingRight: 20,
position: 'relative',
'& *': {
color: props.selected || props.focused ? `${colors.white} !important` : '',
},
'&:hover': {
backgroundColor: backgroundColorHover(props),
},
}));
ElementsRowContainer.displayName = 'Elements:ElementsRowContainer';
const ElementsRowDecoration = styled(FlexRow)({
flexShrink: 0,
justifyContent: 'flex-end',
alignItems: 'center',
marginRight: 4,
position: 'relative',
width: 16,
top: -1,
});
ElementsRowDecoration.displayName = 'Elements:ElementsRowDecoration';
const ElementsLine = styled.div<{childrenCount: number}>(props => ({
backgroundColor: colors.light20,
height: props.childrenCount * ROW_HEIGHT - 4,
position: 'absolute',
right: 3,
top: ROW_HEIGHT - 3,
zIndex: 2,
width: 2,
borderRadius: '999em',
}));
ElementsLine.displayName = 'Elements:ElementsLine';
const DecorationImage = styled.img({
height: 12,
marginRight: 5,
width: 12,
});
DecorationImage.displayName = 'Elements:DecorationImage';
const NoShrinkText = styled(Text)({
flexShrink: 0,
flexWrap: 'nowrap',
overflow: 'hidden',
userSelect: 'none',
fontWeight: 400,
});
NoShrinkText.displayName = 'Elements:NoShrinkText';
const ElementsRowAttributeContainer = styled(NoShrinkText)({
color: colors.dark80,
fontWeight: 300,
marginLeft: 5,
});
ElementsRowAttributeContainer.displayName =
'Elements:ElementsRowAttributeContainer';
const ElementsRowAttributeKey = styled.span({
color: colors.tomato,
});
ElementsRowAttributeKey.displayName = 'Elements:ElementsRowAttributeKey';
const ElementsRowAttributeValue = styled.span({
color: colors.slateDark3,
});
ElementsRowAttributeValue.displayName = 'Elements:ElementsRowAttributeValue';
class PartialHighlight extends PureComponent<{
selected: boolean;
highlighted: string | undefined | null;
content: string;
}> {
static HighlightedText = styled.span<{selected: boolean}>(props => ({
backgroundColor: colors.lemon,
color: props.selected ? `${colors.grapeDark3} !important` : 'auto',
}));
render() {
const {highlighted, content, selected} = this.props;
if (
content &&
highlighted != null &&
highlighted != '' &&
content.toLowerCase().includes(highlighted.toLowerCase())
) {
const highlightStart = content
.toLowerCase()
.indexOf(highlighted.toLowerCase());
const highlightEnd = highlightStart + highlighted.length;
const before = content.substring(0, highlightStart);
const match = content.substring(highlightStart, highlightEnd);
const after = content.substring(highlightEnd);
return (
<span>
{before}
<PartialHighlight.HighlightedText selected={selected}>
{match}
</PartialHighlight.HighlightedText>
{after}
</span>
);
} else {
return <span>{content}</span>;
}
}
}
class ElementsRowAttribute extends PureComponent<{
name: string;
value: string;
matchingSearchQuery: string | undefined | null;
selected: boolean;
}> {
render() {
const {name, value, matchingSearchQuery, selected} = this.props;
return (
<ElementsRowAttributeContainer code={true}>
<ElementsRowAttributeKey>{name}</ElementsRowAttributeKey>=
<ElementsRowAttributeValue>
<PartialHighlight
content={value}
highlighted={
name === 'id' || name === 'addr' ? matchingSearchQuery : ''
}
selected={selected}
/>
</ElementsRowAttributeValue>
</ElementsRowAttributeContainer>
);
}
}
type FlatElement = {
key: ElementID;
element: Element;
level: number;
};
type FlatElements = Array<FlatElement>;
type ElementsRowProps = {
id: ElementID;
level: number;
selected: boolean;
focused: boolean;
matchingSearchQuery: string | undefined | null;
isQueryMatch: boolean;
element: Element;
even: boolean;
onElementSelected: (key: ElementID) => void;
onElementExpanded: (key: ElementID, deep: boolean) => void;
childrenCount: number;
onElementHovered:
| ((key: ElementID | undefined | null) => void)
| undefined
| null;
style?: Object;
contextMenuExtensions: Array<ContextMenuExtension>;
decorateRow?: DecorateRow;
};
type ElementsRowState = {
hovered: boolean;
};
class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
constructor(props: ElementsRowProps, context: Object) {
super(props, context);
this.state = {hovered: false};
this.interaction = reportInteraction('ElementsRow', props.element.name);
}
interaction: (name: string, data: any) => void;
getContextMenu = (): Array<MenuItemConstructorOptions> => {
const {props} = this;
let items: Array<MenuItemConstructorOptions> = [
{
type: 'separator',
},
{
label: 'Copy',
click: () => {
const attrs = props.element.attributes.reduce(
(acc, val) => acc + ` ${val.name}=${val.value}`,
'',
);
clipboard.writeText(`${props.element.name}${attrs}`);
},
},
{
label: props.element.expanded ? 'Collapse' : 'Expand',
click: () => {
this.props.onElementExpanded(this.props.id, false);
},
},
{
label: props.element.expanded
? 'Collapse all child elements'
: 'Expand all child elements',
click: () => {
this.props.onElementExpanded(this.props.id, true);
},
},
];
items = items.concat(
props.element.attributes.map(o => {
return {
label: `Copy ${o.name}`,
click: () => {
clipboard.writeText(o.value);
},
};
}),
);
for (const extension of props.contextMenuExtensions) {
items.push({
label: extension.label,
click: () => extension.click(this.props.id),
});
}
return items;
};
onClick = () => {
this.props.onElementSelected(this.props.id);
this.interaction('selected', {level: this.props.level});
};
onDoubleClick = (event: MouseEvent<any>) => {
this.props.onElementExpanded(this.props.id, event.altKey);
};
onMouseEnter = () => {
this.setState({hovered: true});
if (this.props.onElementHovered) {
this.props.onElementHovered(this.props.id);
}
};
onMouseLeave = () => {
this.setState({hovered: false});
if (this.props.onElementHovered) {
this.props.onElementHovered(null);
}
};
render() {
const {
element,
id,
level,
selected,
focused,
style,
even,
matchingSearchQuery,
decorateRow,
} = this.props;
const hasChildren = element.children && element.children.length > 0;
let arrow;
if (hasChildren) {
arrow = (
<span onClick={this.onDoubleClick} role="button" tabIndex={-1}>
<Glyph
size={8}
name={element.expanded ? 'chevron-down' : 'chevron-right'}
color={selected || focused ? 'white' : colors.light80}
/>
</span>
);
}
const attributes = element.attributes
? element.attributes.map(attr => (
<ElementsRowAttribute
key={attr.name}
name={attr.name}
value={attr.value}
matchingSearchQuery={matchingSearchQuery}
selected={selected}
/>
))
: [];
const decoration = decorateRow
? decorateRow(element)
: (() => {
switch (element.decoration) {
case 'litho':
return <DecorationImage src="icons/litho-logo.png" />;
case 'componentkit':
return <DecorationImage src="icons/componentkit-logo.png" />;
case 'accessibility':
return <DecorationImage src="icons/accessibility.png" />;
default:
return null;
}
})();
// when we hover over or select an expanded element with children, we show a line from the
// bottom of the element to the next sibling
let line;
const shouldShowLine =
(selected || this.state.hovered) && hasChildren && element.expanded;
if (shouldShowLine) {
line = <ElementsLine childrenCount={this.props.childrenCount} />;
}
return (
<ElementsRowContainer
buildItems={this.getContextMenu}
key={id}
level={level}
selected={selected}
focused={focused}
matchingSearchQuery={matchingSearchQuery}
even={even}
onClick={this.onClick}
onDoubleClick={this.onDoubleClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
isQueryMatch={this.props.isQueryMatch}
style={style}>
<ElementsRowDecoration>
{line}
{arrow}
</ElementsRowDecoration>
<NoShrinkText code={true}>
{decoration}
<PartialHighlight
content={element.name}
highlighted={matchingSearchQuery}
selected={selected}
/>
</NoShrinkText>
{attributes}
</ElementsRowContainer>
);
}
}
function containsKeyInSearchResults(
searchResults: ElementSearchResultSet | undefined | null,
key: ElementID,
) {
return searchResults != undefined && searchResults.matches.has(key);
}
const ElementsContainer = styled(FlexColumn)({
backgroundColor: colors.white,
minHeight: '100%',
minWidth: '100%',
overflow: 'auto',
});
ElementsContainer.displayName = 'Elements:ElementsContainer';
const ElementsBox = styled(FlexColumn)({
alignItems: 'flex-start',
flex: 1,
overflow: 'auto',
});
ElementsBox.displayName = 'Elements:ElementsBox';
export type DecorateRow = (e: Element) => ReactElement<any> | undefined | null;
type ElementsProps = {
root: ElementID | undefined | null;
selected: ElementID | undefined | null;
focused?: ElementID | undefined | null;
searchResults: ElementSearchResultSet | undefined | null;
elements: {[key: string]: Element};
onElementSelected: (key: ElementID) => void;
onElementExpanded: (key: ElementID, deep: boolean) => void;
onElementHovered:
| ((key: ElementID | undefined | null) => void)
| undefined
| null;
alternateRowColor?: boolean;
contextMenuExtensions?: Array<ContextMenuExtension>;
decorateRow?: DecorateRow;
};
type ElementsState = {
flatKeys: Array<ElementID>;
flatElements: FlatElements;
maxDepth: number;
};
export type ContextMenuExtension = {
label: string;
click: (element: ElementID) => any;
};
export class Elements extends PureComponent<ElementsProps, ElementsState> {
static defaultProps = {
alternateRowColor: true,
};
constructor(props: ElementsProps, context: Object) {
super(props, context);
this.state = {
flatElements: [],
flatKeys: [],
maxDepth: 0,
};
}
static getDerivedStateFromProps(props: ElementsProps) {
const flatElements: FlatElements = [];
const flatKeys: Array<ElementID> = [];
let maxDepth = 0;
function seed(key: ElementID, level: number) {
const element = props.elements[key];
if (!element) {
return;
}
maxDepth = Math.max(maxDepth, level);
flatElements.push({
element,
key,
level,
});
flatKeys.push(key);
if (
element.children != null &&
element.children.length > 0 &&
element.expanded
) {
for (const key of element.children) {
seed(key, level + 1);
}
}
}
if (props.root != null) {
seed(props.root, 1);
}
return {flatElements, flatKeys, maxDepth};
}
selectElement = (key: ElementID) => {
this.props.onElementSelected(key);
};
onKeyDown = (e: KeyboardEvent<any>) => {
const {selected} = this.props;
if (selected == null) {
return;
}
const {props} = this;
const {flatElements, flatKeys} = this.state;
const selectedIndex = flatKeys.indexOf(selected);
if (selectedIndex < 0) {
return;
}
const selectedElement = props.elements[selected];
if (!selectedElement) {
return;
}
if (
e.key === 'c' &&
((e.metaKey && process.platform === 'darwin') ||
(e.ctrlKey && process.platform !== 'darwin'))
) {
e.preventDefault();
clipboard.writeText(selectedElement.name);
return;
}
if (e.key === 'ArrowUp') {
if (selectedIndex === 0 || flatKeys.length === 1) {
return;
}
e.preventDefault();
this.selectElement(flatKeys[selectedIndex - 1]);
}
if (e.key === 'ArrowDown') {
if (selectedIndex === flatKeys.length - 1) {
return;
}
e.preventDefault();
this.selectElement(flatKeys[selectedIndex + 1]);
}
if (e.key === 'ArrowLeft') {
e.preventDefault();
if (selectedElement.expanded) {
// unexpand
props.onElementExpanded(selected, false);
} else {
// jump to parent
let parentKey;
const targetLevel = flatElements[selectedIndex].level - 1;
for (let i = selectedIndex; i >= 0; i--) {
const {level} = flatElements[i];
if (level === targetLevel) {
parentKey = flatKeys[i];
break;
}
}
if (parentKey) {
this.selectElement(parentKey);
}
}
}
if (e.key === 'ArrowRight' && selectedElement.children.length > 0) {
e.preventDefault();
if (selectedElement.expanded) {
// go to first child
this.selectElement(selectedElement.children[0]);
} else {
// expand
props.onElementExpanded(selected, false);
}
}
};
buildRow = (row: FlatElement, index: number) => {
const {
onElementExpanded,
onElementHovered,
onElementSelected,
selected,
focused,
searchResults,
contextMenuExtensions,
decorateRow,
} = this.props;
const {flatElements} = this.state;
let childrenCount = 0;
for (let i = index + 1; i < flatElements.length; i++) {
const child = flatElements[i];
if (child.level <= row.level) {
break;
} else {
childrenCount++;
}
}
let isEven = false;
if (this.props.alternateRowColor) {
isEven = index % 2 === 0;
}
return (
<ElementsRow
level={row.level}
id={row.key}
key={row.key}
even={isEven}
onElementExpanded={onElementExpanded}
onElementHovered={(key: string | null | undefined) => {
onElementHovered && onElementHovered(key);
}}
onElementSelected={onElementSelected}
selected={selected === row.key}
focused={focused === row.key}
matchingSearchQuery={
searchResults && containsKeyInSearchResults(searchResults, row.key)
? searchResults.query
: null
}
isQueryMatch={containsKeyInSearchResults(searchResults, row.key)}
element={row.element}
childrenCount={childrenCount}
contextMenuExtensions={contextMenuExtensions || []}
decorateRow={decorateRow}
/>
);
};
render() {
return (
<ElementsBox>
<ElementsContainer onKeyDown={this.onKeyDown} tabIndex={0}>
{this.state.flatElements.map(this.buildRow)}
</ElementsContainer>
</ElementsBox>
);
}
}

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {Element} from './ElementsInspector';
import {PluginClient} from '../../../plugin';
import Client from '../../../Client';
import {Logger} from '../../../fb-interfaces/Logger';
import Panel from '../Panel';
import ManagedDataInspector from '../data-inspector/ManagedDataInspector';
import {Component} from 'react';
import {Console} from '../console';
import GK from '../../../fb-stubs/GK';
import React from 'react';
import deepEqual from 'deep-equal';
type OnValueChanged = (path: Array<string>, val: any) => void;
type InspectorSidebarSectionProps = {
data: any;
id: string;
onValueChanged: OnValueChanged | undefined | null;
tooltips?: Object;
};
class InspectorSidebarSection extends Component<InspectorSidebarSectionProps> {
setValue = (path: Array<string>, value: any) => {
if (this.props.onValueChanged) {
this.props.onValueChanged([this.props.id, ...path], value);
}
};
shouldComponentUpdate(nextProps: InspectorSidebarSectionProps) {
return (
!deepEqual(nextProps, this.props) ||
this.props.id !== nextProps.id ||
this.props.onValueChanged !== nextProps.onValueChanged
);
}
extractValue = (val: any) => {
if (val && val.__type__) {
return {
mutable: Boolean(val.__mutable__),
type: val.__type__ === 'auto' ? typeof val.value : val.__type__,
value: val.value,
};
} else {
return {
mutable: typeof val === 'object',
type: typeof val,
value: val,
};
}
};
render() {
const {id} = this.props;
return (
<Panel heading={id} floating={false} grow={false}>
<ManagedDataInspector
data={this.props.data}
setValue={this.props.onValueChanged ? this.setValue : undefined}
extractValue={this.extractValue}
expandRoot={true}
collapsed={true}
tooltips={this.props.tooltips}
/>
</Panel>
);
}
}
type Props = {
element: Element | undefined | null;
tooltips?: Object;
onValueChanged: OnValueChanged | undefined | null;
client: PluginClient;
realClient: Client;
logger: Logger;
extensions?: Array<Function>;
};
type State = {
isConsoleEnabled: boolean;
};
export class InspectorSidebar extends Component<Props, State> {
state = {
isConsoleEnabled: false,
};
constructor(props: Props) {
super(props);
this.checkIfConsoleIsEnabled();
}
componentDidUpdate(prevProps: Props) {
if (prevProps.client !== this.props.client) {
this.checkIfConsoleIsEnabled();
}
}
checkIfConsoleIsEnabled() {
this.props.client
.call('isConsoleEnabled')
.then((result: {isEnabled: boolean}) => {
this.setState({isConsoleEnabled: result.isEnabled});
});
}
render() {
const {element, extensions} = this.props;
if (!element || !element.data) {
return null;
}
const sections: Array<any> =
(extensions &&
extensions.map(ext =>
ext(
this.props.client,
this.props.realClient,
element.id,
this.props.logger,
),
)) ||
[];
for (const key in element.data) {
if (key === 'Extra Sections') {
for (const extraSection in element.data[key]) {
let data:
| string
| number
| boolean
| {__type__: string; value: any}
| null = element.data[key][extraSection];
// data might be sent as stringified JSON, we want to parse it for a nicer persentation.
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
// data was not a valid JSON, type is required to be an object
console.error(
`ElementsInspector unable to parse extra section: ${extraSection}`,
);
data = null;
}
}
sections.push(
<InspectorSidebarSection
tooltips={this.props.tooltips}
key={extraSection}
id={extraSection}
data={data}
onValueChanged={this.props.onValueChanged}
/>,
);
}
} else {
sections.push(
<InspectorSidebarSection
tooltips={this.props.tooltips}
key={key}
id={key}
data={element.data[key]}
onValueChanged={this.props.onValueChanged}
/>,
);
}
}
if (GK.get('sonar_show_console_plugin') && this.state.isConsoleEnabled) {
sections.push(
<Panel heading="JS Console" floating={false} grow={false}>
<Console client={this.props.client} getContext={() => element.id} />
</Panel>,
);
}
return sections;
}
}