Group app wide ui state into one object
Summary: Should be a bit easier to see what UI state we are holding at the plugin instance level Reviewed By: lblasa Differential Revision: D41498272 fbshipit-source-id: 6d88086766efd9c39f71be7e2ce32c5058494c96
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7fc64adfd4
commit
6bb541a33f
@@ -40,17 +40,17 @@ export function Tree(props: {
|
|||||||
onSelectNode: (id: Id) => void;
|
onSelectNode: (id: Id) => void;
|
||||||
}) {
|
}) {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const expandedItems = useValue(instance.treeState).expandedNodes;
|
const expandedItems = useValue(instance.uiState.treeState).expandedNodes;
|
||||||
const focused = useValue(instance.focusedNode);
|
const focused = useValue(instance.uiState.focusedNode);
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
() => toComplexTree(focused || props.rootId, props.nodes),
|
() => toComplexTree(focused || props.rootId, props.nodes),
|
||||||
[focused, props.nodes, props.rootId],
|
[focused, props.nodes, props.rootId],
|
||||||
);
|
);
|
||||||
const hoveredNodes = useValue(instance.hoveredNodes);
|
const hoveredNodes = useValue(instance.uiState.hoveredNodes);
|
||||||
const treeEnvRef = useRef<TreeEnvironmentRef>();
|
const treeEnvRef = useRef<TreeEnvironmentRef>();
|
||||||
|
|
||||||
const searchTerm = useValue(instance.searchTerm);
|
const searchTerm = useValue(instance.uiState.searchTerm);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//this makes the keyboard arrow controls work always, even when using the visualiser
|
//this makes the keyboard arrow controls work always, even when using the visualiser
|
||||||
@@ -75,15 +75,15 @@ export function Tree(props: {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onFocusItem={(item) => {
|
onFocusItem={(item) => {
|
||||||
instance.hoveredNodes.set([item.index]);
|
instance.uiState.hoveredNodes.set([item.index]);
|
||||||
}}
|
}}
|
||||||
onExpandItem={(item) => {
|
onExpandItem={(item) => {
|
||||||
instance.treeState.update((draft) => {
|
instance.uiState.treeState.update((draft) => {
|
||||||
draft.expandedNodes.push(item.index);
|
draft.expandedNodes.push(item.index);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onCollapseItem={(item) =>
|
onCollapseItem={(item) =>
|
||||||
instance.treeState.update((draft) => {
|
instance.uiState.treeState.update((draft) => {
|
||||||
draft.expandedNodes = draft.expandedNodes.filter(
|
draft.expandedNodes = draft.expandedNodes.filter(
|
||||||
(expandedItemIndex) => expandedItemIndex !== item.index,
|
(expandedItemIndex) => expandedItemIndex !== item.index,
|
||||||
);
|
);
|
||||||
@@ -109,8 +109,8 @@ export function Tree(props: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onMouseOver: () => {
|
onMouseOver: () => {
|
||||||
if (!instance.isContextMenuOpen.get()) {
|
if (!instance.uiState.isContextMenuOpen.get()) {
|
||||||
instance.hoveredNodes.set([item.index]);
|
instance.uiState.hoveredNodes.set([item.index]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -200,21 +200,21 @@ type ContextMenuProps = {node: UINode; id: Id; title: string};
|
|||||||
|
|
||||||
const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
|
const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const focusedNode = instance.focusedNode.get();
|
const focusedNode = instance.uiState.focusedNode.get();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
onVisibleChange={(visible) => {
|
onVisibleChange={(visible) => {
|
||||||
instance.isContextMenuOpen.set(visible);
|
instance.uiState.isContextMenuOpen.set(visible);
|
||||||
}}
|
}}
|
||||||
overlay={() => (
|
overlay={() => (
|
||||||
<Menu>
|
<Menu>
|
||||||
{focusedNode !== head(instance.hoveredNodes.get()) && (
|
{focusedNode !== head(instance.uiState.hoveredNodes.get()) && (
|
||||||
<UIDebuggerMenuItem
|
<UIDebuggerMenuItem
|
||||||
key="focus"
|
key="focus"
|
||||||
text={`Focus ${title}`}
|
text={`Focus ${title}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
instance.focusedNode.set(id);
|
instance.uiState.focusedNode.set(id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -224,7 +224,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({id, title, children}) => {
|
|||||||
key="remove-focus"
|
key="remove-focus"
|
||||||
text="Remove focus"
|
text="Remove focus"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
instance.focusedNode.set(undefined);
|
instance.uiState.focusedNode.set(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const Visualization2D: React.FC<
|
|||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
const snapshot = useValue(instance.snapshot);
|
const snapshot = useValue(instance.snapshot);
|
||||||
const focusedNodeId = useValue(instance.focusedNode);
|
const focusedNodeId = useValue(instance.uiState.focusedNode);
|
||||||
|
|
||||||
const focusState = useMemo(() => {
|
const focusState = useMemo(() => {
|
||||||
const rootNode = toNestedNode(rootId, nodes);
|
const rootNode = toNestedNode(rootId, nodes);
|
||||||
@@ -40,7 +40,7 @@ export const Visualization2D: React.FC<
|
|||||||
const mouseListener = throttle((ev: MouseEvent) => {
|
const mouseListener = throttle((ev: MouseEvent) => {
|
||||||
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
const domRect = rootNodeRef.current?.getBoundingClientRect();
|
||||||
|
|
||||||
if (!focusState || !domRect || instance.isContextMenuOpen.get()) {
|
if (!focusState || !domRect || instance.uiState.isContextMenuOpen.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rawMouse = {x: ev.clientX, y: ev.clientY};
|
const rawMouse = {x: ev.clientX, y: ev.clientY};
|
||||||
@@ -63,9 +63,9 @@ export const Visualization2D: React.FC<
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
hitNodes.length > 0 &&
|
hitNodes.length > 0 &&
|
||||||
!isEqual(hitNodes, instance.hoveredNodes.get())
|
!isEqual(hitNodes, instance.uiState.hoveredNodes.get())
|
||||||
) {
|
) {
|
||||||
instance.hoveredNodes.set(hitNodes);
|
instance.uiState.hoveredNodes.set(hitNodes);
|
||||||
}
|
}
|
||||||
}, MouseThrottle);
|
}, MouseThrottle);
|
||||||
window.addEventListener('mousemove', mouseListener);
|
window.addEventListener('mousemove', mouseListener);
|
||||||
@@ -73,7 +73,12 @@ export const Visualization2D: React.FC<
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('mousemove', mouseListener);
|
window.removeEventListener('mousemove', mouseListener);
|
||||||
};
|
};
|
||||||
}, [instance.hoveredNodes, focusState, nodes, instance.isContextMenuOpen]);
|
}, [
|
||||||
|
instance.uiState.hoveredNodes,
|
||||||
|
focusState,
|
||||||
|
nodes,
|
||||||
|
instance.uiState.isContextMenuOpen,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!focusState) {
|
if (!focusState) {
|
||||||
return null;
|
return null;
|
||||||
@@ -94,8 +99,8 @@ export const Visualization2D: React.FC<
|
|||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
//the context menu triggers this callback but we dont want to remove hover effect
|
//the context menu triggers this callback but we dont want to remove hover effect
|
||||||
if (!instance.isContextMenuOpen.get()) {
|
if (!instance.uiState.isContextMenuOpen.get()) {
|
||||||
instance.hoveredNodes.set([]);
|
instance.uiState.hoveredNodes.set([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -169,11 +174,11 @@ function Visualization2DNode({
|
|||||||
setIsHovered(head(newValue) === node.id);
|
setIsHovered(head(newValue) === node.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
instance.hoveredNodes.subscribe(listener);
|
instance.uiState.hoveredNodes.subscribe(listener);
|
||||||
return () => {
|
return () => {
|
||||||
instance.hoveredNodes.unsubscribe(listener);
|
instance.uiState.hoveredNodes.unsubscribe(listener);
|
||||||
};
|
};
|
||||||
}, [instance.hoveredNodes, node.id]);
|
}, [instance.uiState.hoveredNodes, node.id]);
|
||||||
|
|
||||||
const isSelected = selectedNode === node.id;
|
const isSelected = selectedNode === node.id;
|
||||||
|
|
||||||
@@ -224,7 +229,7 @@ function Visualization2DNode({
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const hoveredNodes = instance.hoveredNodes.get();
|
const hoveredNodes = instance.uiState.hoveredNodes.get();
|
||||||
if (hoveredNodes[0] === selectedNode) {
|
if (hoveredNodes[0] === selectedNode) {
|
||||||
onSelectNode(undefined);
|
onSelectNode(undefined);
|
||||||
} else {
|
} else {
|
||||||
@@ -241,15 +246,15 @@ function Visualization2DNode({
|
|||||||
const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
|
const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
const focusedNodeId = useValue(instance.focusedNode);
|
const focusedNodeId = useValue(instance.uiState.focusedNode);
|
||||||
const hoveredNodeId = head(useValue(instance.hoveredNodes));
|
const hoveredNodeId = head(useValue(instance.uiState.hoveredNodes));
|
||||||
const nodes = useValue(instance.nodes);
|
const nodes = useValue(instance.nodes);
|
||||||
const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null;
|
const hoveredNode = hoveredNodeId ? nodes.get(hoveredNodeId) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
onVisibleChange={(open) => {
|
onVisibleChange={(open) => {
|
||||||
instance.isContextMenuOpen.set(open);
|
instance.uiState.isContextMenuOpen.set(open);
|
||||||
}}
|
}}
|
||||||
trigger={['contextMenu']}
|
trigger={['contextMenu']}
|
||||||
overlay={() => {
|
overlay={() => {
|
||||||
@@ -260,7 +265,7 @@ const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
|
|||||||
key="focus"
|
key="focus"
|
||||||
text={`Focus ${hoveredNode?.name}`}
|
text={`Focus ${hoveredNode?.name}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
instance.focusedNode.set(hoveredNode?.id);
|
instance.uiState.focusedNode.set(hoveredNode?.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -269,7 +274,7 @@ const ContextMenu: React.FC<{nodes: Map<Id, UINode>}> = ({children}) => {
|
|||||||
key="remove-focus"
|
key="remove-focus"
|
||||||
text="Remove focus"
|
text="Remove focus"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
instance.focusedNode.set(undefined);
|
instance.uiState.focusedNode.set(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function Component() {
|
|||||||
|
|
||||||
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
|
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
|
||||||
|
|
||||||
const searchTerm = useValue(instance.searchTerm);
|
const searchTerm = useValue(instance.uiState.searchTerm);
|
||||||
const {ctrlPressed} = useKeyboardModifiers();
|
const {ctrlPressed} = useKeyboardModifiers();
|
||||||
|
|
||||||
function renderSidebar(
|
function renderSidebar(
|
||||||
@@ -55,7 +55,7 @@ export function Component() {
|
|||||||
<Layout.Container grow pad="medium" gap="small">
|
<Layout.Container grow pad="medium" gap="small">
|
||||||
<Input
|
<Input
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => instance.searchTerm.set(e.target.value)}
|
onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Layout.ScrollContainer>
|
<Layout.ScrollContainer>
|
||||||
<Tree
|
<Tree
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const UIDebuggerMenuItem: React.FC<{
|
|||||||
}> = ({text, onClick}) => {
|
}> = ({text, onClick}) => {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
|
|
||||||
const isMenuOpen = useValue(instance.isContextMenuOpen);
|
const isMenuOpen = useValue(instance.uiState.isContextMenuOpen);
|
||||||
/**
|
/**
|
||||||
* The menu is not a controlled component and seems to be a bit slow to close when user clicks on it.
|
* The menu is not a controlled component and seems to be a bit slow to close when user clicks on it.
|
||||||
* React may rerender the menu before it has time to close resulting in seeing an incorrect context menu for a frame.
|
* React may rerender the menu before it has time to close resulting in seeing an incorrect context menu for a frame.
|
||||||
@@ -37,7 +37,7 @@ export const UIDebuggerMenuItem: React.FC<{
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClick();
|
onClick();
|
||||||
instance.isContextMenuOpen.set(false);
|
instance.uiState.isContextMenuOpen.set(false);
|
||||||
}}>
|
}}>
|
||||||
{text}
|
{text}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import './node_modules/react-complex-tree/lib/style.css';
|
|||||||
export function plugin(client: PluginClient<Events>) {
|
export function plugin(client: PluginClient<Events>) {
|
||||||
const rootId = createState<Id | undefined>(undefined);
|
const rootId = createState<Id | undefined>(undefined);
|
||||||
const metadata = createState<Map<MetadataId, Metadata>>(new Map());
|
const metadata = createState<Map<MetadataId, Metadata>>(new Map());
|
||||||
const searchTerm = createState<string>('');
|
|
||||||
|
|
||||||
client.onMessage('init', (event) => {
|
client.onMessage('init', (event) => {
|
||||||
rootId.set(event.rootId);
|
rootId.set(event.rootId);
|
||||||
@@ -48,21 +47,23 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
perfEvents.append(event);
|
perfEvents.append(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
//used to disabled hover effects which cause rerenders and mess up the existing context menu
|
|
||||||
const isContextMenuOpen = createState<boolean>(false);
|
|
||||||
|
|
||||||
const focusedNode = createState<Id | undefined>(undefined);
|
|
||||||
|
|
||||||
const nodes = createState<Map<Id, UINode>>(new Map());
|
const nodes = createState<Map<Id, UINode>>(new Map());
|
||||||
const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
const snapshot = createState<{nodeId: Id; base64Image: Snapshot} | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const treeState = createState<TreeState>({expandedNodes: []});
|
const uiState = {
|
||||||
|
//used to disabled hover effects which cause rerenders and mess up the existing context menu
|
||||||
|
isContextMenuOpen: createState<boolean>(false),
|
||||||
|
|
||||||
//The reason for the array as that user could be hovering multiple overlapping nodes at once in the visualiser.
|
//The reason for the array as that user could be hovering multiple overlapping nodes at once in the visualiser.
|
||||||
//The nodes are sorted by area since you most likely want to select the smallest node under your cursor
|
//The nodes are sorted by area since you most likely want to select the smallest node under your cursor
|
||||||
const hoveredNodes = createState<Id[]>([]);
|
hoveredNodes: createState<Id[]>([]),
|
||||||
|
|
||||||
|
searchTerm: createState<string>(''),
|
||||||
|
focusedNode: createState<Id | undefined>(undefined),
|
||||||
|
treeState: createState<TreeState>({expandedNodes: []}),
|
||||||
|
};
|
||||||
|
|
||||||
client.onMessage('coordinateUpdate', (event) => {
|
client.onMessage('coordinateUpdate', (event) => {
|
||||||
nodes.update((draft) => {
|
nodes.update((draft) => {
|
||||||
@@ -88,7 +89,7 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
treeState.update((draft) => {
|
uiState.treeState.update((draft) => {
|
||||||
for (const node of event.nodes) {
|
for (const node of event.nodes) {
|
||||||
if (!seenNodes.has(node.id)) {
|
if (!seenNodes.has(node.id)) {
|
||||||
draft.expandedNodes.push(node.id);
|
draft.expandedNodes.push(node.id);
|
||||||
@@ -111,15 +112,11 @@ export function plugin(client: PluginClient<Events>) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
rootId,
|
rootId,
|
||||||
isContextMenuOpen,
|
uiState,
|
||||||
nodes,
|
nodes,
|
||||||
metadata,
|
metadata,
|
||||||
focusedNode,
|
|
||||||
snapshot,
|
snapshot,
|
||||||
hoveredNodes,
|
|
||||||
perfEvents,
|
perfEvents,
|
||||||
treeState,
|
|
||||||
searchTerm,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user