diff --git a/.flowconfig b/.flowconfig index 1f10c0274..5fd16f0b6 100644 --- a/.flowconfig +++ b/.flowconfig @@ -16,7 +16,7 @@ /desktop/babel-transformer/.* /desktop/plugins/fb/relaydevtools/relay-devtools/DevtoolsUI.js$ /website/.* -/desktop/plugins/sections/d3/d3.js$ +/desktop/plugins/sections/src/d3/d3.js$ /react-native/ReactNativeFlipperExample/.* [libs] diff --git a/desktop/plugins/sections/Models.js b/desktop/plugins/sections/Models.js deleted file mode 100644 index 9cb04b565..000000000 --- a/desktop/plugins/sections/Models.js +++ /dev/null @@ -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, -|}; - -export type AddEventPayload = {| - id: string, - reason: string, - stack_trace: Array, - 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, - prev_data?: Array, - next_data?: Array, - }, - }, - }, - }, -|}; - -export type UpdateTreeGenerationChangesetApplicationPayload = {| - changeset: { - section_key: { - changesets: { - id: { - count: number, - index: number, - toIndex?: number, - type: string, - render_infos?: Array, - prev_data?: Array, - next_data?: Array, - }, - }, - }, - }, - type: string, - identifier: string, - timestamp: number, - section_component_hierarchy: SectionComponentHierarchy, - tree_generation_id: string, - payload: ?Object, -|}; - -export type TreeGeneration = {| - ...AddEventPayload, - ...$Shape, - ...$Shape, - changeSets: Array, -|}; diff --git a/desktop/plugins/sections/index.js b/desktop/plugins/sections/index.js deleted file mode 100644 index a5f93df49..000000000 --- a/desktop/plugins/sections/index.js +++ /dev/null @@ -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 { - 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 => { - 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 ( - - ); - } else if ( - this.state.focusedChangeSet && - this.state.focusedChangeSet.section_component_hierarchy - ) { - // Display section component hierarchy for specific changeset - return ( - - ); - } else { - return this.renderWaiting(); - } - }; - - renderWaiting = () => ( - - - - No data available... - - - ); - - 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); - }; - - matchesCurrentSearchString = (s: string) => { - return s.toLowerCase().includes(this.state.searchString.toLowerCase()); - }; - - matchingGenerationKeys = () => { - const matchingKeys: Array = 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(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); - }; - - 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 ( - - - - - - - - {this.props.persistedState.recording ? ( - - ) : ( - - )} - - - - - {this.renderTreeHierarchy(focusedTreeGeneration)} - {focusedTreeGeneration && ( - - - - )} - - - - - ); - } -} diff --git a/desktop/plugins/sections/package.json b/desktop/plugins/sections/package.json index e4a6e1f42..a20e9e23a 100644 --- a/desktop/plugins/sections/package.json +++ b/desktop/plugins/sections/package.json @@ -1,7 +1,8 @@ { "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json", "name": "flipper-plugin-sections", - "id": "flipper-plugin-sections", + "id": "Sections", + "icon": "tree", "title": "Sections", "bugs": { "email": "oncall+ios_componentkit@xmail.facebook.com", @@ -9,7 +10,7 @@ }, "version": "0.51.0", "main": "dist/bundle.js", - "flipperBundlerEntry": "index.js", + "flipperBundlerEntry": "src/index.tsx", "license": "MIT", "keywords": [ "flipper-plugin" @@ -18,6 +19,10 @@ "dependencies": { "react-d3-tree": "^1.12.1" }, + "peerDependencies": { + "flipper": "0.51.0", + "flipper-plugin": "0.51.0" + }, "resolutions": { "react-d3-tree/d3": "file:./d3" } diff --git a/desktop/plugins/sections/DetailsPanel.js b/desktop/plugins/sections/src/DetailsPanel.tsx similarity index 86% rename from desktop/plugins/sections/DetailsPanel.js rename to desktop/plugins/sections/src/DetailsPanel.tsx index ba0760d1c..8c8f9a366 100644 --- a/desktop/plugins/sections/DetailsPanel.js +++ b/desktop/plugins/sections/src/DetailsPanel.tsx @@ -7,7 +7,7 @@ * @format */ -import type {UpdateTreeGenerationChangesetApplicationPayload} from './Models.js'; +import type {UpdateTreeGenerationChangesetApplicationPayload} from './Models'; import React from 'react'; import { @@ -24,19 +24,28 @@ const NoContent = styled(FlexBox)({ alignItems: 'center', justifyContent: 'center', flexGrow: 1, - fontWeight: '500', + fontWeight: 500, color: colors.light30, }); -type Props = {| - changeSets: ?Array, - eventUserInfo: ?Object, - focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload, +type Props = { + changeSets: + | Array + | null + | undefined; + eventUserInfo: any; + focusedChangeSet: + | UpdateTreeGenerationChangesetApplicationPayload + | null + | undefined; onFocusChangeSet: ( - focusedChangeSet: ?UpdateTreeGenerationChangesetApplicationPayload, - ) => void, - selectedNodeInfo: ?Object, -|}; + focusedChangeSet: + | UpdateTreeGenerationChangesetApplicationPayload + | null + | undefined, + ) => void; + selectedNodeInfo: any; +}; export default class DetailsPanel extends Component { render() { diff --git a/desktop/plugins/sections/EventsTable.js b/desktop/plugins/sections/src/EventsTable.tsx similarity index 89% rename from desktop/plugins/sections/EventsTable.js rename to desktop/plugins/sections/src/EventsTable.tsx index 6c5758919..157ac624c 100644 --- a/desktop/plugins/sections/EventsTable.js +++ b/desktop/plugins/sections/src/EventsTable.tsx @@ -7,7 +7,7 @@ * @format */ -import type {TreeGeneration} from './Models.js'; +import type {TreeGeneration} from './Models'; import { FlexColumn, @@ -29,7 +29,7 @@ const Container = styled(FlexRow)({ flexGrow: 1, }); -const SurfaceContainer = styled(FlexColumn)((props) => ({ +const SurfaceContainer = styled(FlexColumn)((props: {scrolled: boolean}) => ({ position: 'relative', '::after': { display: props.scrolled ? 'block' : 'none', @@ -50,7 +50,7 @@ const TimeContainer = styled(FlexColumn)({ flexShrink: 1, }); -const Row = styled(FlexRow)((props) => ({ +const Row = styled(FlexRow)((props: {showTimeline?: boolean}) => ({ alignItems: 'center', paddingBottom: 3, marginTop: 3, @@ -94,13 +94,13 @@ const Content = styled.div({ fontSize: 11, textAlign: 'center', textTransform: 'uppercase', - fontWeight: '500', + fontWeight: 500, color: colors.light50, }); -const Record = styled.div(({highlighted}) => ({ +const Record = styled.div((props: {highlighted: boolean}) => ({ border: `1px solid ${colors.light15}`, - boxShadow: highlighted + boxShadow: props.highlighted ? `inset 0 0 0 2px ${colors.macOSTitleBarIconSelected}` : 'none', borderRadius: 5, @@ -130,14 +130,14 @@ const Icon = styled(Glyph)({ top: 5, }); -type Props = {| - generations: Array, - focusedGenerationId: ?string, - onClick: (id: string) => mixed, -|}; +type Props = { + generations: Array; + focusedGenerationId: string | null | undefined; + onClick: (id: string) => any; +}; type State = { - scrolled: boolean, + scrolled: boolean; }; export default class extends Component { @@ -161,7 +161,7 @@ export default class extends Component { ) { const node = document.querySelector(`[data-id="${focusedGenerationId}"]`); if (node) { - node.scrollIntoViewIfNeeded(); + node.scrollIntoView(); } } } @@ -195,13 +195,13 @@ export default class extends Component { } }; - onScroll = (e: SyntheticUIEvent) => + onScroll = (e: React.UIEvent) => this.setState({scrolled: e.currentTarget.scrollLeft > 0}); render() { - const surfaces = this.props.generations.reduce( + const surfaces: Set = this.props.generations.reduce( (acc, cv) => acc.add(cv.surface_key), - new Set(), + new Set(), ); return ( diff --git a/desktop/plugins/sections/src/Models.tsx b/desktop/plugins/sections/src/Models.tsx new file mode 100644 index 000000000..6dabe7178 --- /dev/null +++ b/desktop/plugins/sections/src/Models.tsx @@ -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; +}; + +export type AddEventPayload = { + id: string; + reason: string; + stack_trace: Array; + 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; + prev_data?: Array; + next_data?: Array; + }; + }; + }; + }; +}; + +export type UpdateTreeGenerationChangesetApplicationPayload = { + changeset: { + section_key: { + changesets: { + id: { + count: number; + index: number; + toIndex?: number; + type: string; + render_infos?: Array; + prev_data?: Array; + next_data?: Array; + }; + }; + }; + }; + type: string; + identifier: string; + timestamp: number; + section_component_hierarchy?: SectionComponentHierarchy; + tree_generation_id: string; + payload?: any; +}; + +export type TreeGeneration = { + changeSets: Array; +} & AddEventPayload & + Partial & + Partial; diff --git a/desktop/plugins/sections/StackTrace.js b/desktop/plugins/sections/src/StackTrace.tsx similarity index 87% rename from desktop/plugins/sections/StackTrace.js rename to desktop/plugins/sections/src/StackTrace.tsx index 95d60242c..88aa47a6b 100644 --- a/desktop/plugins/sections/StackTrace.js +++ b/desktop/plugins/sections/src/StackTrace.tsx @@ -14,13 +14,13 @@ const FacebookLibraries = ['Facebook']; const REGEX = /\d+\s+(?(\s|\w|\.)+\w)\s+(?
0x\w+?)\s+(?.+) \+ (?\d+)/; -function isSystemLibrary(libraryName: ?string): boolean { - return !FacebookLibraries.includes(libraryName); +function isSystemLibrary(libraryName: string | null | undefined): boolean { + return libraryName ? !FacebookLibraries.includes(libraryName) : false; } type Props = { - data: Array, - skipStackTraceFormat?: boolean, + data: Array; + skipStackTraceFormat?: boolean | undefined; }; export default class extends React.Component { diff --git a/desktop/plugins/sections/Tree.js b/desktop/plugins/sections/src/Tree.tsx similarity index 88% rename from desktop/plugins/sections/Tree.js rename to desktop/plugins/sections/src/Tree.tsx index d47c353d8..ddef0f8c6 100644 --- a/desktop/plugins/sections/Tree.js +++ b/desktop/plugins/sections/src/Tree.tsx @@ -11,7 +11,7 @@ import type {SectionComponentHierarchy} from './Models'; import {Glyph, PureComponent, styled, Toolbar, Spacer, colors} from 'flipper'; import {Tree} from 'react-d3-tree'; -import {Fragment} from 'react'; +import React, {Fragment} from 'react'; const Legend = styled.div((props) => ({ color: colors.dark50, @@ -35,7 +35,7 @@ const Label = styled.div({ left: 7, maxWidth: 270, overflow: 'hidden', - fontWeight: '500', + fontWeight: 500, textOverflow: 'ellipsis', paddingLeft: 5, paddingRight: 5, @@ -65,40 +65,43 @@ const IconButton = styled.div({ }); type TreeData = Array<{ - identifier: string, - name: string, - parent: string | '', - didTriggerStateUpdate?: boolean, - isReused?: boolean, - isDirty?: boolean, - inserted?: boolean, - removed?: boolean, - updated?: boolean, - unchanged?: boolean, - isSection?: boolean, - isDataModel?: boolean, + identifier: string; + name: string; + parent: string | ''; + didTriggerStateUpdate?: boolean; + isReused?: boolean; + isDirty?: boolean; + inserted?: boolean; + removed?: boolean; + updated?: boolean; + unchanged?: boolean; + isSection?: boolean; + isDataModel?: boolean; }>; type Props = { - data: TreeData | SectionComponentHierarchy, - nodeClickHandler?: (node: any, evt: InputEvent) => void, + data: TreeData | SectionComponentHierarchy; + nodeClickHandler: (node: any) => void; }; type State = { translate: { - x: number, - y: number, - }, - tree: ?Object, - zoom: number, + x: number; + y: number; + }; + tree: Object | null | undefined; + zoom: number; }; -class NodeLabel extends PureComponent { +class NodeLabel extends PureComponent< + {onLabelClicked: (node: any) => void; nodeData?: any}, + {collapsed: boolean} +> { state = { collapsed: false, }; - showNodeData = (e) => { + showNodeData = (e: React.MouseEvent) => { e.stopPropagation(); this.props.onLabelClicked(this.props?.nodeData); }; @@ -158,7 +161,7 @@ export default class extends PureComponent { return { name: n.name, - children: [], + children: [] as Array, attributes: {...n}, nodeSvgShape: { shapeProps: { @@ -171,7 +174,7 @@ export default class extends PureComponent { }; }); - const parentMap: Map> = tree.reduce((acc, cv) => { + const parentMap: Map> = tree.reduce((acc, cv) => { const {parent} = cv.attributes; if (typeof parent !== 'string') { return acc; @@ -236,7 +239,7 @@ export default class extends PureComponent { } } - onZoom = (e: SyntheticInputEvent) => { + onZoom = (e: React.ChangeEvent) => { this.setState({zoom: e.target.valueAsNumber}); }; @@ -244,7 +247,7 @@ export default class extends PureComponent { return ( { + ref={(ref) => { this.treeContainer = ref; }}>