Convert Section Plugin to Use Sandy
Summary: Convert section plugin to Sandy API. TODO - Fix layout issues - scrollbar occurs in small component (bottom layout) - scrollbar in wrong place (top layout) - text shrunk in bottom part of tree component - (?) move away from d3 - better typing for payload - move components to functional one - unit test Reviewed By: mweststrate Differential Revision: D22385993 fbshipit-source-id: 862d4b775caf2d9a7bcb37446299251965a5d6db
This commit is contained in:
committed by
Facebook GitHub Bot
parent
10f9a48540
commit
8ac0c4c6c4
@@ -16,7 +16,7 @@
|
|||||||
<PROJECT_ROOT>/desktop/babel-transformer/.*
|
<PROJECT_ROOT>/desktop/babel-transformer/.*
|
||||||
<PROJECT_ROOT>/desktop/plugins/fb/relaydevtools/relay-devtools/DevtoolsUI.js$
|
<PROJECT_ROOT>/desktop/plugins/fb/relaydevtools/relay-devtools/DevtoolsUI.js$
|
||||||
<PROJECT_ROOT>/website/.*
|
<PROJECT_ROOT>/website/.*
|
||||||
<PROJECT_ROOT>/desktop/plugins/sections/d3/d3.js$
|
<PROJECT_ROOT>/desktop/plugins/sections/src/d3/d3.js$
|
||||||
<PROJECT_ROOT>/react-native/ReactNativeFlipperExample/.*
|
<PROJECT_ROOT>/react-native/ReactNativeFlipperExample/.*
|
||||||
|
|
||||||
[libs]
|
[libs]
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type SectionComponentHierarchy = {|
|
|
||||||
type: string,
|
|
||||||
children: Array<SectionComponentHierarchy>,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type AddEventPayload = {|
|
|
||||||
id: string,
|
|
||||||
reason: string,
|
|
||||||
stack_trace: Array<string>,
|
|
||||||
skip_stack_trace_format?: boolean,
|
|
||||||
surface_key: string,
|
|
||||||
event_timestamp: number,
|
|
||||||
update_mode: number,
|
|
||||||
reentrant_count: number,
|
|
||||||
payload: ?Object,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type UpdateTreeGenerationHierarchyGenerationPayload = {|
|
|
||||||
hierarchy_generation_timestamp: number,
|
|
||||||
id: string,
|
|
||||||
reason: string,
|
|
||||||
tree?: Array<{
|
|
||||||
didTriggerStateUpdate?: boolean,
|
|
||||||
identifier: string,
|
|
||||||
isDirty?: boolean,
|
|
||||||
isReused?: boolean,
|
|
||||||
name: string,
|
|
||||||
parent: string | '',
|
|
||||||
inserted?: boolean,
|
|
||||||
removed?: boolean,
|
|
||||||
updated?: boolean,
|
|
||||||
unchanged?: boolean,
|
|
||||||
isSection?: boolean,
|
|
||||||
isDataModel?: boolean,
|
|
||||||
}>,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type UpdateTreeGenerationChangesetGenerationPayload = {|
|
|
||||||
timestamp: number,
|
|
||||||
tree_generation_id: string,
|
|
||||||
identifier: string,
|
|
||||||
type: string,
|
|
||||||
changesets: {
|
|
||||||
section_key: {
|
|
||||||
changesets: {
|
|
||||||
id: {
|
|
||||||
count: number,
|
|
||||||
index: number,
|
|
||||||
toIndex?: number,
|
|
||||||
type: string,
|
|
||||||
render_infos?: Array<String>,
|
|
||||||
prev_data?: Array<String>,
|
|
||||||
next_data?: Array<String>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type UpdateTreeGenerationChangesetApplicationPayload = {|
|
|
||||||
changeset: {
|
|
||||||
section_key: {
|
|
||||||
changesets: {
|
|
||||||
id: {
|
|
||||||
count: number,
|
|
||||||
index: number,
|
|
||||||
toIndex?: number,
|
|
||||||
type: string,
|
|
||||||
render_infos?: Array<String>,
|
|
||||||
prev_data?: Array<String>,
|
|
||||||
next_data?: Array<String>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
type: string,
|
|
||||||
identifier: string,
|
|
||||||
timestamp: number,
|
|
||||||
section_component_hierarchy: SectionComponentHierarchy,
|
|
||||||
tree_generation_id: string,
|
|
||||||
payload: ?Object,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type TreeGeneration = {|
|
|
||||||
...AddEventPayload,
|
|
||||||
...$Shape<UpdateTreeGenerationHierarchyGenerationPayload>,
|
|
||||||
...$Shape<UpdateTreeGenerationChangesetGenerationPayload>,
|
|
||||||
changeSets: Array<UpdateTreeGenerationChangesetApplicationPayload>,
|
|
||||||
|};
|
|
||||||
@@ -1,372 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {
|
|
||||||
TreeGeneration,
|
|
||||||
AddEventPayload,
|
|
||||||
UpdateTreeGenerationHierarchyGenerationPayload,
|
|
||||||
UpdateTreeGenerationChangesetGenerationPayload,
|
|
||||||
UpdateTreeGenerationChangesetApplicationPayload,
|
|
||||||
} from './Models.js';
|
|
||||||
|
|
||||||
import {FlipperPlugin} from 'flipper';
|
|
||||||
import React from 'react';
|
|
||||||
import Tree from './Tree.js';
|
|
||||||
import StackTrace from './StackTrace.js';
|
|
||||||
import EventTable from './EventsTable.js';
|
|
||||||
import DetailsPanel from './DetailsPanel.js';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Toolbar,
|
|
||||||
Glyph,
|
|
||||||
Sidebar,
|
|
||||||
FlexBox,
|
|
||||||
styled,
|
|
||||||
Button,
|
|
||||||
Spacer,
|
|
||||||
colors,
|
|
||||||
DetailSidebar,
|
|
||||||
SearchInput,
|
|
||||||
SearchBox,
|
|
||||||
SearchIcon,
|
|
||||||
} from 'flipper';
|
|
||||||
|
|
||||||
const Waiting = styled(FlexBox)((props) => ({
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
flexGrow: 1,
|
|
||||||
background: colors.light02,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
textAlign: 'center',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const InfoText = styled.div((props) => ({
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
fontWeight: '500',
|
|
||||||
color: colors.light30,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const InfoBox = styled.div((props) => ({
|
|
||||||
maxWidth: 400,
|
|
||||||
margin: 'auto',
|
|
||||||
textAlign: 'center',
|
|
||||||
}));
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload,
|
|
||||||
userSelectedGenerationId: ?string,
|
|
||||||
selectedTreeNode: ?Object,
|
|
||||||
searchString: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type PersistedState = {
|
|
||||||
generations: {
|
|
||||||
[id: string]: TreeGeneration,
|
|
||||||
},
|
|
||||||
focusedGenerationId: ?string,
|
|
||||||
recording: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class extends FlipperPlugin<State, *, PersistedState> {
|
|
||||||
static title = 'Sections';
|
|
||||||
static id = 'Sections';
|
|
||||||
static icon = 'tree';
|
|
||||||
|
|
||||||
static defaultPersistedState = {
|
|
||||||
generations: {},
|
|
||||||
focusedGenerationId: null,
|
|
||||||
recording: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
static persistedStateReducer = (
|
|
||||||
persistedState: PersistedState,
|
|
||||||
method: string,
|
|
||||||
payload: Object,
|
|
||||||
): $Shape<PersistedState> => {
|
|
||||||
if (!persistedState.recording) {
|
|
||||||
return persistedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addEvent = (data: AddEventPayload) => ({
|
|
||||||
...persistedState,
|
|
||||||
generations: {
|
|
||||||
...persistedState.generations,
|
|
||||||
[data.id]: {
|
|
||||||
...data,
|
|
||||||
changeSets: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
focusedGenerationId: persistedState.focusedGenerationId || data.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateTreeGenerationHierarchyGeneration = (
|
|
||||||
data: UpdateTreeGenerationHierarchyGenerationPayload,
|
|
||||||
) => ({
|
|
||||||
...persistedState,
|
|
||||||
generations: {
|
|
||||||
...persistedState.generations,
|
|
||||||
[data.id]: {
|
|
||||||
...persistedState.generations[data.id],
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateTreeGenerationChangeset = (
|
|
||||||
data:
|
|
||||||
| UpdateTreeGenerationChangesetGenerationPayload
|
|
||||||
| UpdateTreeGenerationChangesetApplicationPayload,
|
|
||||||
) => ({
|
|
||||||
...persistedState,
|
|
||||||
generations: {
|
|
||||||
...persistedState.generations,
|
|
||||||
[data.tree_generation_id]: {
|
|
||||||
...persistedState.generations[data.tree_generation_id],
|
|
||||||
changeSets: [
|
|
||||||
...persistedState.generations[data.tree_generation_id].changeSets,
|
|
||||||
data,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (method === 'addEvent') {
|
|
||||||
return addEvent(payload);
|
|
||||||
} else if (method === 'updateTreeGenerationHierarchyGeneration') {
|
|
||||||
return updateTreeGenerationHierarchyGeneration(payload);
|
|
||||||
} else if (
|
|
||||||
method === 'updateTreeGenerationChangesetApplication' ||
|
|
||||||
method === 'updateTreeGenerationChangesetGeneration'
|
|
||||||
) {
|
|
||||||
return updateTreeGenerationChangeset(payload);
|
|
||||||
} else {
|
|
||||||
return persistedState;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
focusedChangeSet: null,
|
|
||||||
userSelectedGenerationId: null,
|
|
||||||
selectedTreeNode: null,
|
|
||||||
searchString: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
onTreeGenerationFocused = (focusedGenerationId: ?string) => {
|
|
||||||
this.setState({
|
|
||||||
focusedChangeSet: null,
|
|
||||||
userSelectedGenerationId: focusedGenerationId,
|
|
||||||
selectedTreeNode: null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onFocusChangeSet = (
|
|
||||||
focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload,
|
|
||||||
) => {
|
|
||||||
this.setState({
|
|
||||||
focusedChangeSet,
|
|
||||||
selectedTreeNode: null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeClicked = (targetNode: any, evt: InputEvent) => {
|
|
||||||
if (targetNode.attributes.isSection) {
|
|
||||||
const sectionData = {};
|
|
||||||
sectionData['global_key'] = targetNode.attributes.identifier;
|
|
||||||
this.setState({
|
|
||||||
selectedTreeNode: {sectionData},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dataModel;
|
|
||||||
// Not all models can be parsed.
|
|
||||||
if (targetNode.attributes.isDataModel) {
|
|
||||||
try {
|
|
||||||
dataModel = JSON.parse(targetNode.attributes.identifier);
|
|
||||||
} catch (e) {
|
|
||||||
dataModel = targetNode.attributes.identifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
selectedTreeNode: {dataModel},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
renderTreeHierarchy = (generation: ?TreeGeneration) => {
|
|
||||||
if (generation && generation.tree && generation.tree.length > 0) {
|
|
||||||
// Display component tree hierarchy, if any
|
|
||||||
return (
|
|
||||||
<Tree data={generation.tree} nodeClickHandler={this.onNodeClicked} />
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
this.state.focusedChangeSet &&
|
|
||||||
this.state.focusedChangeSet.section_component_hierarchy
|
|
||||||
) {
|
|
||||||
// Display section component hierarchy for specific changeset
|
|
||||||
return (
|
|
||||||
<Tree
|
|
||||||
data={this.state.focusedChangeSet.section_component_hierarchy}
|
|
||||||
nodeClickHandler={this.onNodeClicked}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.renderWaiting();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderWaiting = () => (
|
|
||||||
<Waiting>
|
|
||||||
<InfoBox>
|
|
||||||
<Glyph
|
|
||||||
name="face-unhappy"
|
|
||||||
variant="outline"
|
|
||||||
size={24}
|
|
||||||
color={colors.light30}
|
|
||||||
/>
|
|
||||||
<InfoText>No data available...</InfoText>
|
|
||||||
</InfoBox>
|
|
||||||
</Waiting>
|
|
||||||
);
|
|
||||||
|
|
||||||
clear = () => {
|
|
||||||
this.props.setPersistedState({
|
|
||||||
...this.constructor.defaultPersistedState,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onChange = (e: any) => {
|
|
||||||
this.setState({searchString: e.target.value});
|
|
||||||
};
|
|
||||||
|
|
||||||
generationValues = () => {
|
|
||||||
const {generations} = this.props.persistedState;
|
|
||||||
const generationKeys = Object.keys(generations);
|
|
||||||
return (generationKeys.map(
|
|
||||||
(key) => generations[key],
|
|
||||||
): Array<TreeGeneration>);
|
|
||||||
};
|
|
||||||
|
|
||||||
matchesCurrentSearchString = (s: string) => {
|
|
||||||
return s.toLowerCase().includes(this.state.searchString.toLowerCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
matchingGenerationKeys = () => {
|
|
||||||
const matchingKeys: Array<string> = this.generationValues()
|
|
||||||
.filter((g) => {
|
|
||||||
if (g.payload) {
|
|
||||||
const componentClassName: ?string = g.payload['component_class_name'];
|
|
||||||
if (componentClassName) {
|
|
||||||
return this.matchesCurrentSearchString(componentClassName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return g.tree?.some((node) => {
|
|
||||||
return this.matchesCurrentSearchString(node.name);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.map((g) => {
|
|
||||||
return g.surface_key;
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Set<string>(matchingKeys);
|
|
||||||
};
|
|
||||||
|
|
||||||
filteredGenerations = () => {
|
|
||||||
if (this.state.searchString.length <= 0) {
|
|
||||||
return Object.values(this.props.persistedState.generations);
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingKeys = this.matchingGenerationKeys();
|
|
||||||
|
|
||||||
return (this.generationValues().filter((g) => {
|
|
||||||
return matchingKeys.has(g.surface_key);
|
|
||||||
}): Array<TreeGeneration>);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {generations} = this.props.persistedState;
|
|
||||||
if (Object.values(this.props.persistedState.generations).length === 0) {
|
|
||||||
return this.renderWaiting();
|
|
||||||
}
|
|
||||||
|
|
||||||
const focusedGenerationId =
|
|
||||||
this.state.userSelectedGenerationId ||
|
|
||||||
this.props.persistedState.focusedGenerationId;
|
|
||||||
|
|
||||||
const focusedTreeGeneration: ?TreeGeneration = focusedGenerationId
|
|
||||||
? generations[focusedGenerationId]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Toolbar>
|
|
||||||
<SearchBox tabIndex={-1}>
|
|
||||||
<SearchIcon
|
|
||||||
name="magnifying-glass"
|
|
||||||
color={colors.macOSTitleBarIcon}
|
|
||||||
size={16}
|
|
||||||
/>
|
|
||||||
<SearchInput
|
|
||||||
placeholder={'Search'}
|
|
||||||
onChange={this.onChange}
|
|
||||||
value={this.state.searchString}
|
|
||||||
/>
|
|
||||||
</SearchBox>
|
|
||||||
<Spacer />
|
|
||||||
{this.props.persistedState.recording ? (
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
this.props.setPersistedState({
|
|
||||||
recording: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
iconVariant="filled"
|
|
||||||
icon="stop-playback">
|
|
||||||
Stop
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button onClick={this.clear} icon="trash" iconVariant="outline">
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Toolbar>
|
|
||||||
<Sidebar position="top" minHeight={80} height={80}>
|
|
||||||
<EventTable
|
|
||||||
generations={this.filteredGenerations()}
|
|
||||||
focusedGenerationId={focusedGenerationId}
|
|
||||||
onClick={this.onTreeGenerationFocused}
|
|
||||||
/>
|
|
||||||
</Sidebar>
|
|
||||||
{this.renderTreeHierarchy(focusedTreeGeneration)}
|
|
||||||
{focusedTreeGeneration && (
|
|
||||||
<Sidebar position="bottom" minHeight={100} height={250}>
|
|
||||||
<StackTrace
|
|
||||||
data={focusedTreeGeneration.stack_trace}
|
|
||||||
skip_stack_trace_format={
|
|
||||||
focusedTreeGeneration.skip_stack_trace_format
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Sidebar>
|
|
||||||
)}
|
|
||||||
<DetailSidebar>
|
|
||||||
<DetailsPanel
|
|
||||||
eventUserInfo={focusedTreeGeneration?.payload}
|
|
||||||
changeSets={focusedTreeGeneration?.changeSets}
|
|
||||||
onFocusChangeSet={this.onFocusChangeSet}
|
|
||||||
focusedChangeSet={this.state.focusedChangeSet}
|
|
||||||
selectedNodeInfo={this.state.selectedTreeNode}
|
|
||||||
/>
|
|
||||||
</DetailSidebar>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
|
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
|
||||||
"name": "flipper-plugin-sections",
|
"name": "flipper-plugin-sections",
|
||||||
"id": "flipper-plugin-sections",
|
"id": "Sections",
|
||||||
|
"icon": "tree",
|
||||||
"title": "Sections",
|
"title": "Sections",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"email": "oncall+ios_componentkit@xmail.facebook.com",
|
"email": "oncall+ios_componentkit@xmail.facebook.com",
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"version": "0.51.0",
|
"version": "0.51.0",
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"flipperBundlerEntry": "index.js",
|
"flipperBundlerEntry": "src/index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"flipper-plugin"
|
"flipper-plugin"
|
||||||
@@ -18,6 +19,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-d3-tree": "^1.12.1"
|
"react-d3-tree": "^1.12.1"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"flipper": "0.51.0",
|
||||||
|
"flipper-plugin": "0.51.0"
|
||||||
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"react-d3-tree/d3": "file:./d3"
|
"react-d3-tree/d3": "file:./d3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {UpdateTreeGenerationChangesetApplicationPayload} from './Models.js';
|
import type {UpdateTreeGenerationChangesetApplicationPayload} from './Models';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -24,19 +24,28 @@ const NoContent = styled(FlexBox)({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
fontWeight: '500',
|
fontWeight: 500,
|
||||||
color: colors.light30,
|
color: colors.light30,
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {|
|
type Props = {
|
||||||
changeSets: ?Array<UpdateTreeGenerationChangesetApplicationPayload>,
|
changeSets:
|
||||||
eventUserInfo: ?Object,
|
| Array<UpdateTreeGenerationChangesetApplicationPayload>
|
||||||
focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload,
|
| null
|
||||||
|
| undefined;
|
||||||
|
eventUserInfo: any;
|
||||||
|
focusedChangeSet:
|
||||||
|
| UpdateTreeGenerationChangesetApplicationPayload
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
onFocusChangeSet: (
|
onFocusChangeSet: (
|
||||||
focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload,
|
focusedChangeSet:
|
||||||
) => void,
|
| UpdateTreeGenerationChangesetApplicationPayload
|
||||||
selectedNodeInfo: ?Object,
|
| null
|
||||||
|};
|
| undefined,
|
||||||
|
) => void;
|
||||||
|
selectedNodeInfo: any;
|
||||||
|
};
|
||||||
|
|
||||||
export default class DetailsPanel extends Component<Props> {
|
export default class DetailsPanel extends Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {TreeGeneration} from './Models.js';
|
import type {TreeGeneration} from './Models';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FlexColumn,
|
FlexColumn,
|
||||||
@@ -29,7 +29,7 @@ const Container = styled(FlexRow)({
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const SurfaceContainer = styled(FlexColumn)((props) => ({
|
const SurfaceContainer = styled(FlexColumn)((props: {scrolled: boolean}) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
'::after': {
|
'::after': {
|
||||||
display: props.scrolled ? 'block' : 'none',
|
display: props.scrolled ? 'block' : 'none',
|
||||||
@@ -50,7 +50,7 @@ const TimeContainer = styled(FlexColumn)({
|
|||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Row = styled(FlexRow)((props) => ({
|
const Row = styled(FlexRow)((props: {showTimeline?: boolean}) => ({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingBottom: 3,
|
paddingBottom: 3,
|
||||||
marginTop: 3,
|
marginTop: 3,
|
||||||
@@ -94,13 +94,13 @@ const Content = styled.div({
|
|||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
fontWeight: '500',
|
fontWeight: 500,
|
||||||
color: colors.light50,
|
color: colors.light50,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Record = styled.div(({highlighted}) => ({
|
const Record = styled.div((props: {highlighted: boolean}) => ({
|
||||||
border: `1px solid ${colors.light15}`,
|
border: `1px solid ${colors.light15}`,
|
||||||
boxShadow: highlighted
|
boxShadow: props.highlighted
|
||||||
? `inset 0 0 0 2px ${colors.macOSTitleBarIconSelected}`
|
? `inset 0 0 0 2px ${colors.macOSTitleBarIconSelected}`
|
||||||
: 'none',
|
: 'none',
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
@@ -130,14 +130,14 @@ const Icon = styled(Glyph)({
|
|||||||
top: 5,
|
top: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {|
|
type Props = {
|
||||||
generations: Array<TreeGeneration>,
|
generations: Array<TreeGeneration>;
|
||||||
focusedGenerationId: ?string,
|
focusedGenerationId: string | null | undefined;
|
||||||
onClick: (id: string) => mixed,
|
onClick: (id: string) => any;
|
||||||
|};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
scrolled: boolean,
|
scrolled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class extends Component<Props, State> {
|
export default class extends Component<Props, State> {
|
||||||
@@ -161,7 +161,7 @@ export default class extends Component<Props, State> {
|
|||||||
) {
|
) {
|
||||||
const node = document.querySelector(`[data-id="${focusedGenerationId}"]`);
|
const node = document.querySelector(`[data-id="${focusedGenerationId}"]`);
|
||||||
if (node) {
|
if (node) {
|
||||||
node.scrollIntoViewIfNeeded();
|
node.scrollIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,13 +195,13 @@ export default class extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onScroll = (e: SyntheticUIEvent<HTMLElement>) =>
|
onScroll = (e: React.UIEvent<HTMLDivElement>) =>
|
||||||
this.setState({scrolled: e.currentTarget.scrollLeft > 0});
|
this.setState({scrolled: e.currentTarget.scrollLeft > 0});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const surfaces = this.props.generations.reduce(
|
const surfaces: Set<string> = this.props.generations.reduce(
|
||||||
(acc, cv) => acc.add(cv.surface_key),
|
(acc, cv) => acc.add(cv.surface_key),
|
||||||
new Set(),
|
new Set<string>(),
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
97
desktop/plugins/sections/src/Models.tsx
Normal file
97
desktop/plugins/sections/src/Models.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SectionComponentHierarchy = {
|
||||||
|
type: string;
|
||||||
|
children: Array<SectionComponentHierarchy>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddEventPayload = {
|
||||||
|
id: string;
|
||||||
|
reason: string;
|
||||||
|
stack_trace: Array<string>;
|
||||||
|
skip_stack_trace_format?: boolean;
|
||||||
|
surface_key: string;
|
||||||
|
event_timestamp: number;
|
||||||
|
update_mode: number;
|
||||||
|
reentrant_count: number;
|
||||||
|
payload: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTreeGenerationHierarchyGenerationPayload = {
|
||||||
|
hierarchy_generation_timestamp: number;
|
||||||
|
id: string;
|
||||||
|
reason: string;
|
||||||
|
tree?: Array<{
|
||||||
|
didTriggerStateUpdate?: boolean;
|
||||||
|
identifier: string;
|
||||||
|
isDirty?: boolean;
|
||||||
|
isReused?: boolean;
|
||||||
|
name: string;
|
||||||
|
parent: string | '';
|
||||||
|
inserted?: boolean;
|
||||||
|
removed?: boolean;
|
||||||
|
updated?: boolean;
|
||||||
|
unchanged?: boolean;
|
||||||
|
isSection?: boolean;
|
||||||
|
isDataModel?: boolean;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTreeGenerationChangesetGenerationPayload = {
|
||||||
|
timestamp: number;
|
||||||
|
tree_generation_id: string;
|
||||||
|
identifier: string;
|
||||||
|
type: string;
|
||||||
|
changeset: {
|
||||||
|
section_key: {
|
||||||
|
changesets: {
|
||||||
|
id: {
|
||||||
|
count: number;
|
||||||
|
index: number;
|
||||||
|
toIndex?: number;
|
||||||
|
type: string;
|
||||||
|
render_infos?: Array<String>;
|
||||||
|
prev_data?: Array<String>;
|
||||||
|
next_data?: Array<String>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTreeGenerationChangesetApplicationPayload = {
|
||||||
|
changeset: {
|
||||||
|
section_key: {
|
||||||
|
changesets: {
|
||||||
|
id: {
|
||||||
|
count: number;
|
||||||
|
index: number;
|
||||||
|
toIndex?: number;
|
||||||
|
type: string;
|
||||||
|
render_infos?: Array<String>;
|
||||||
|
prev_data?: Array<String>;
|
||||||
|
next_data?: Array<String>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
type: string;
|
||||||
|
identifier: string;
|
||||||
|
timestamp: number;
|
||||||
|
section_component_hierarchy?: SectionComponentHierarchy;
|
||||||
|
tree_generation_id: string;
|
||||||
|
payload?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TreeGeneration = {
|
||||||
|
changeSets: Array<UpdateTreeGenerationChangesetApplicationPayload>;
|
||||||
|
} & AddEventPayload &
|
||||||
|
Partial<UpdateTreeGenerationHierarchyGenerationPayload> &
|
||||||
|
Partial<UpdateTreeGenerationChangesetGenerationPayload>;
|
||||||
@@ -14,13 +14,13 @@ const FacebookLibraries = ['Facebook'];
|
|||||||
|
|
||||||
const REGEX = /\d+\s+(?<library>(\s|\w|\.)+\w)\s+(?<address>0x\w+?)\s+(?<caller>.+) \+ (?<lineNumber>\d+)/;
|
const REGEX = /\d+\s+(?<library>(\s|\w|\.)+\w)\s+(?<address>0x\w+?)\s+(?<caller>.+) \+ (?<lineNumber>\d+)/;
|
||||||
|
|
||||||
function isSystemLibrary(libraryName: ?string): boolean {
|
function isSystemLibrary(libraryName: string | null | undefined): boolean {
|
||||||
return !FacebookLibraries.includes(libraryName);
|
return libraryName ? !FacebookLibraries.includes(libraryName) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: Array<string>,
|
data: Array<string>;
|
||||||
skipStackTraceFormat?: boolean,
|
skipStackTraceFormat?: boolean | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class extends React.Component<Props> {
|
export default class extends React.Component<Props> {
|
||||||
@@ -11,7 +11,7 @@ import type {SectionComponentHierarchy} from './Models';
|
|||||||
|
|
||||||
import {Glyph, PureComponent, styled, Toolbar, Spacer, colors} from 'flipper';
|
import {Glyph, PureComponent, styled, Toolbar, Spacer, colors} from 'flipper';
|
||||||
import {Tree} from 'react-d3-tree';
|
import {Tree} from 'react-d3-tree';
|
||||||
import {Fragment} from 'react';
|
import React, {Fragment} from 'react';
|
||||||
|
|
||||||
const Legend = styled.div((props) => ({
|
const Legend = styled.div((props) => ({
|
||||||
color: colors.dark50,
|
color: colors.dark50,
|
||||||
@@ -35,7 +35,7 @@ const Label = styled.div({
|
|||||||
left: 7,
|
left: 7,
|
||||||
maxWidth: 270,
|
maxWidth: 270,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
fontWeight: '500',
|
fontWeight: 500,
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
paddingLeft: 5,
|
paddingLeft: 5,
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
@@ -65,40 +65,43 @@ const IconButton = styled.div({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type TreeData = Array<{
|
type TreeData = Array<{
|
||||||
identifier: string,
|
identifier: string;
|
||||||
name: string,
|
name: string;
|
||||||
parent: string | '',
|
parent: string | '';
|
||||||
didTriggerStateUpdate?: boolean,
|
didTriggerStateUpdate?: boolean;
|
||||||
isReused?: boolean,
|
isReused?: boolean;
|
||||||
isDirty?: boolean,
|
isDirty?: boolean;
|
||||||
inserted?: boolean,
|
inserted?: boolean;
|
||||||
removed?: boolean,
|
removed?: boolean;
|
||||||
updated?: boolean,
|
updated?: boolean;
|
||||||
unchanged?: boolean,
|
unchanged?: boolean;
|
||||||
isSection?: boolean,
|
isSection?: boolean;
|
||||||
isDataModel?: boolean,
|
isDataModel?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: TreeData | SectionComponentHierarchy,
|
data: TreeData | SectionComponentHierarchy;
|
||||||
nodeClickHandler?: (node: any, evt: InputEvent) => void,
|
nodeClickHandler: (node: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
translate: {
|
translate: {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
},
|
};
|
||||||
tree: ?Object,
|
tree: Object | null | undefined;
|
||||||
zoom: number,
|
zoom: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NodeLabel extends PureComponent<Props, State> {
|
class NodeLabel extends PureComponent<
|
||||||
|
{onLabelClicked: (node: any) => void; nodeData?: any},
|
||||||
|
{collapsed: boolean}
|
||||||
|
> {
|
||||||
state = {
|
state = {
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
showNodeData = (e) => {
|
showNodeData = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onLabelClicked(this.props?.nodeData);
|
this.props.onLabelClicked(this.props?.nodeData);
|
||||||
};
|
};
|
||||||
@@ -158,7 +161,7 @@ export default class extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: n.name,
|
name: n.name,
|
||||||
children: [],
|
children: [] as Array<any>,
|
||||||
attributes: {...n},
|
attributes: {...n},
|
||||||
nodeSvgShape: {
|
nodeSvgShape: {
|
||||||
shapeProps: {
|
shapeProps: {
|
||||||
@@ -171,7 +174,7 @@ export default class extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const parentMap: Map<string, Array<Object>> = tree.reduce((acc, cv) => {
|
const parentMap: Map<string, Array<any>> = tree.reduce((acc, cv) => {
|
||||||
const {parent} = cv.attributes;
|
const {parent} = cv.attributes;
|
||||||
if (typeof parent !== 'string') {
|
if (typeof parent !== 'string') {
|
||||||
return acc;
|
return acc;
|
||||||
@@ -236,7 +239,7 @@ export default class extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onZoom = (e: SyntheticInputEvent<HTMLInputElement>) => {
|
onZoom = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({zoom: e.target.valueAsNumber});
|
this.setState({zoom: e.target.valueAsNumber});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,7 +247,7 @@ export default class extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Container
|
<Container
|
||||||
innerRef={(ref) => {
|
ref={(ref) => {
|
||||||
this.treeContainer = ref;
|
this.treeContainer = ref;
|
||||||
}}>
|
}}>
|
||||||
<style>
|
<style>
|
||||||
321
desktop/plugins/sections/src/index.tsx
Normal file
321
desktop/plugins/sections/src/index.tsx
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
TreeGeneration,
|
||||||
|
AddEventPayload,
|
||||||
|
UpdateTreeGenerationHierarchyGenerationPayload,
|
||||||
|
UpdateTreeGenerationChangesetGenerationPayload,
|
||||||
|
UpdateTreeGenerationChangesetApplicationPayload,
|
||||||
|
} from './Models';
|
||||||
|
|
||||||
|
import Tree from './Tree';
|
||||||
|
import StackTrace from './StackTrace';
|
||||||
|
import EventTable from './EventsTable';
|
||||||
|
import DetailsPanel from './DetailsPanel';
|
||||||
|
import React, {useState, useMemo} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Toolbar,
|
||||||
|
Glyph,
|
||||||
|
Sidebar,
|
||||||
|
FlexBox,
|
||||||
|
styled,
|
||||||
|
Button,
|
||||||
|
Spacer,
|
||||||
|
colors,
|
||||||
|
DetailSidebar,
|
||||||
|
SearchInput,
|
||||||
|
SearchBox,
|
||||||
|
SearchIcon,
|
||||||
|
Layout,
|
||||||
|
} from 'flipper';
|
||||||
|
|
||||||
|
import {FlipperClient, createState, usePlugin, useValue} from 'flipper-plugin';
|
||||||
|
|
||||||
|
const Waiting = styled(FlexBox)({
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
flexGrow: 1,
|
||||||
|
background: colors.light02,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
const InfoText = styled.div({
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: colors.light30,
|
||||||
|
});
|
||||||
|
|
||||||
|
const InfoBox = styled.div({
|
||||||
|
maxWidth: 400,
|
||||||
|
margin: 'auto',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
type Events = {
|
||||||
|
addEvent: AddEventPayload;
|
||||||
|
updateTreeGenerationHierarchyGeneration: UpdateTreeGenerationHierarchyGenerationPayload;
|
||||||
|
updateTreeGenerationChangesetApplication: UpdateTreeGenerationChangesetApplicationPayload;
|
||||||
|
updateTreeGenerationChangesetGeneration: UpdateTreeGenerationChangesetGenerationPayload;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function plugin(client: FlipperClient<Events, {}>) {
|
||||||
|
const generations = createState<{[id: string]: TreeGeneration}>(
|
||||||
|
{},
|
||||||
|
{persist: 'generations'},
|
||||||
|
);
|
||||||
|
const focusedGenerationId = createState<string | null>(null);
|
||||||
|
const recording = createState<boolean>(true);
|
||||||
|
|
||||||
|
client.onMessage('addEvent', (data) => {
|
||||||
|
if (!recording.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
generations.update((draft) => {
|
||||||
|
draft[data.id] = {...data, changeSets: []};
|
||||||
|
});
|
||||||
|
focusedGenerationId.set(focusedGenerationId.get() || data.id);
|
||||||
|
});
|
||||||
|
client.onMessage('updateTreeGenerationHierarchyGeneration', (data) => {
|
||||||
|
generations.update((draft) => {
|
||||||
|
draft[data.id] = {...draft[data.id], ...data};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
function updateTreeGenerationChangeset(
|
||||||
|
data:
|
||||||
|
| UpdateTreeGenerationChangesetGenerationPayload
|
||||||
|
| UpdateTreeGenerationChangesetApplicationPayload,
|
||||||
|
) {
|
||||||
|
generations.update((draft) => {
|
||||||
|
draft[data.tree_generation_id].changeSets.push(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
client.onMessage(
|
||||||
|
'updateTreeGenerationChangesetApplication',
|
||||||
|
updateTreeGenerationChangeset,
|
||||||
|
);
|
||||||
|
client.onMessage(
|
||||||
|
'updateTreeGenerationChangesetGeneration',
|
||||||
|
updateTreeGenerationChangeset,
|
||||||
|
);
|
||||||
|
|
||||||
|
function setRecording(value: boolean) {
|
||||||
|
recording.set(value);
|
||||||
|
}
|
||||||
|
function clear() {
|
||||||
|
generations.set({});
|
||||||
|
focusedGenerationId.set(null);
|
||||||
|
recording.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {generations, focusedGenerationId, recording, setRecording, clear};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Component() {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
|
const generations = useValue(instance.generations);
|
||||||
|
const focusedGenerationId = useValue(instance.focusedGenerationId);
|
||||||
|
const recording = useValue(instance.recording);
|
||||||
|
|
||||||
|
const [userSelectedGenerationId, setUserSelectedGenerationId] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
const [searchString, setSearchString] = useState<string>('');
|
||||||
|
const [focusedChangeSet, setFocusedChangeSet] = useState<
|
||||||
|
UpdateTreeGenerationChangesetApplicationPayload | null | undefined
|
||||||
|
>(null);
|
||||||
|
const [selectedTreeNode, setSelectedTreeNode] = useState<any>();
|
||||||
|
|
||||||
|
const focusedTreeGeneration: TreeGeneration | null = useMemo(() => {
|
||||||
|
const id = userSelectedGenerationId || focusedGenerationId;
|
||||||
|
if (id === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return generations[id];
|
||||||
|
}, [userSelectedGenerationId, focusedGenerationId, generations]);
|
||||||
|
const filteredGenerations: Array<TreeGeneration> = useMemo(() => {
|
||||||
|
const generationValues = Object.values(generations);
|
||||||
|
if (searchString.length <= 0) {
|
||||||
|
return generationValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchesCurrentSearchString = (s: string): boolean => {
|
||||||
|
return s.toLowerCase().includes(searchString.toLowerCase());
|
||||||
|
};
|
||||||
|
const matchingKeys: Array<string> = generationValues
|
||||||
|
.filter((g) => {
|
||||||
|
if (g.payload) {
|
||||||
|
const componentClassName: string | null | undefined =
|
||||||
|
g.payload.component_class_name;
|
||||||
|
if (componentClassName) {
|
||||||
|
return matchesCurrentSearchString(componentClassName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return g.tree?.some((node) => {
|
||||||
|
return matchesCurrentSearchString(node.name);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.map((g) => {
|
||||||
|
return g.surface_key;
|
||||||
|
});
|
||||||
|
|
||||||
|
return generationValues.filter((g) => matchingKeys.includes(g.surface_key));
|
||||||
|
}, [generations, searchString]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Right>
|
||||||
|
<Layout.Top>
|
||||||
|
<Toolbar>
|
||||||
|
<SearchBox tabIndex={-1}>
|
||||||
|
<SearchIcon
|
||||||
|
name="magnifying-glass"
|
||||||
|
color={colors.macOSTitleBarIcon}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
<SearchInput
|
||||||
|
placeholder={'Search'}
|
||||||
|
onChange={(e) => setSearchString(e.target.value)}
|
||||||
|
value={searchString}
|
||||||
|
/>
|
||||||
|
</SearchBox>
|
||||||
|
<Spacer />
|
||||||
|
{recording ? (
|
||||||
|
<Button
|
||||||
|
onClick={() => instance.setRecording(false)}
|
||||||
|
iconVariant="filled"
|
||||||
|
icon="stop-playback">
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button onClick={instance.clear} icon="trash" iconVariant="outline">
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
<Layout.Top scrollable={false}>
|
||||||
|
<Sidebar position="top" minHeight={80} height={80}>
|
||||||
|
<EventTable
|
||||||
|
generations={filteredGenerations}
|
||||||
|
focusedGenerationId={
|
||||||
|
userSelectedGenerationId || focusedGenerationId
|
||||||
|
}
|
||||||
|
onClick={(id: string | null) => {
|
||||||
|
setFocusedChangeSet(null);
|
||||||
|
setUserSelectedGenerationId(id);
|
||||||
|
setSelectedTreeNode(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Sidebar>
|
||||||
|
<Layout.Top>
|
||||||
|
<Sidebar position="top" minHeight={80} height={80}>
|
||||||
|
<TreeHierarchy
|
||||||
|
generation={focusedTreeGeneration}
|
||||||
|
focusedChangeSet={focusedChangeSet}
|
||||||
|
setSelectedTreeNode={setSelectedTreeNode}
|
||||||
|
/>
|
||||||
|
</Sidebar>
|
||||||
|
{focusedTreeGeneration && (
|
||||||
|
<StackTrace
|
||||||
|
data={focusedTreeGeneration.stack_trace}
|
||||||
|
skipStackTraceFormat={
|
||||||
|
focusedTreeGeneration.skip_stack_trace_format
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Layout.Top>
|
||||||
|
</Layout.Top>
|
||||||
|
</Layout.Top>
|
||||||
|
<DetailSidebar>
|
||||||
|
<DetailsPanel
|
||||||
|
eventUserInfo={focusedTreeGeneration?.payload}
|
||||||
|
changeSets={focusedTreeGeneration?.changeSets}
|
||||||
|
onFocusChangeSet={(
|
||||||
|
focusedChangeSet:
|
||||||
|
| UpdateTreeGenerationChangesetApplicationPayload
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
) => {
|
||||||
|
setFocusedChangeSet(focusedChangeSet);
|
||||||
|
setSelectedTreeNode(null);
|
||||||
|
}}
|
||||||
|
focusedChangeSet={focusedChangeSet}
|
||||||
|
selectedNodeInfo={selectedTreeNode}
|
||||||
|
/>
|
||||||
|
</DetailSidebar>
|
||||||
|
</Layout.Right>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TreeHierarchy({
|
||||||
|
generation,
|
||||||
|
focusedChangeSet,
|
||||||
|
setSelectedTreeNode,
|
||||||
|
}: {
|
||||||
|
generation: TreeGeneration | null;
|
||||||
|
focusedChangeSet:
|
||||||
|
| UpdateTreeGenerationChangesetApplicationPayload
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
setSelectedTreeNode: (node: any) => void;
|
||||||
|
}) {
|
||||||
|
const onNodeClicked = useMemo(
|
||||||
|
() => (targetNode: any) => {
|
||||||
|
if (targetNode.attributes.isSection) {
|
||||||
|
const sectionData: any = {};
|
||||||
|
sectionData.global_key = targetNode.attributes.identifier;
|
||||||
|
setSelectedTreeNode({sectionData});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataModel;
|
||||||
|
// Not all models can be parsed.
|
||||||
|
if (targetNode.attributes.isDataModel) {
|
||||||
|
try {
|
||||||
|
dataModel = JSON.parse(targetNode.attributes.identifier);
|
||||||
|
} catch (e) {
|
||||||
|
dataModel = targetNode.attributes.identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedTreeNode({dataModel});
|
||||||
|
},
|
||||||
|
[setSelectedTreeNode],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (generation && generation.tree && generation.tree.length > 0) {
|
||||||
|
// Display component tree hierarchy, if any
|
||||||
|
return <Tree data={generation.tree} nodeClickHandler={onNodeClicked} />;
|
||||||
|
} else if (focusedChangeSet && focusedChangeSet.section_component_hierarchy) {
|
||||||
|
// Display section component hierarchy for specific changeset
|
||||||
|
return (
|
||||||
|
<Tree
|
||||||
|
data={focusedChangeSet.section_component_hierarchy}
|
||||||
|
nodeClickHandler={onNodeClicked}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Waiting>
|
||||||
|
<InfoBox>
|
||||||
|
<Glyph
|
||||||
|
name="face-unhappy"
|
||||||
|
variant="outline"
|
||||||
|
size={24}
|
||||||
|
color={colors.light30}
|
||||||
|
/>
|
||||||
|
<InfoText>No data available...</InfoText>
|
||||||
|
</InfoBox>
|
||||||
|
</Waiting>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user