Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElementSelection: New element selection context to support selecting elements #97029

Merged
merged 3 commits into from
Nov 28, 2024

Conversation

torkelo
Copy link
Member

@torkelo torkelo commented Nov 26, 2024

A quick proof of concept of an element selection solution.

We are exploring an edit pane in dashboards that shows actions & options for any element or elements (multiple) selected. See first POC here: #96895

In that POC, I used a very hacky global onClick handler and element attributes to mark elements as selectable and selected. However, because it relied on DOM element manipulation to add the selected state class, the selection state is visually lost whenever react re-renders. This PR is exploring a more robust react approach to element selection via a context.

Context has

  • Current selected elements
  • Select an element (or multiple via shift key)
  • Enable/disable selection (needed in dashboards when we go into edit mode or leave edit mode)

Point of feedback

  • Is there a different / better way to do this?
  • How to clear selection, Currently I have a top level click handler and when an element is selected propagation is stopped. Another way would be to have a timer so that we do not clear selection if the selection was done in the last few milliseconds (somehow need to make sure the clear selection click is not triggered by an actual selection click).
  • Should the context come with a "Provider" implementation that handles the selection state & callback or should we leave that up to each implementation to handle?

Outside the scope of this PR (So ignore)

  • How to make it clear an element is selectable (cursor or other affordance)
  • How to make an element that is both draggable and selectable and differentiate between the two (going to be a bit tricky with Panel but we solved it in the past)
element_selection_context.mp4

@torkelo torkelo requested review from a team as code owners November 26, 2024 10:41
@torkelo torkelo requested review from aocenas, ashharrison90 and Ukochka and removed request for a team November 26, 2024 10:41
@github-actions github-actions bot added this to the 11.4.x milestone Nov 26, 2024
@torkelo torkelo requested review from dprokop, bfmatei and mdvictor and removed request for aocenas and Ukochka November 26, 2024 10:41
@torkelo torkelo changed the title ElementSelection: New element selction context to support selecting elements like panels ElementSelection: New element selection context to support selecting elements like panels Nov 26, 2024
@torkelo torkelo changed the title ElementSelection: New element selection context to support selecting elements like panels ElementSelection: New element selection context to support selecting elements Nov 26, 2024
@torkelo torkelo mentioned this pull request Nov 26, 2024
34 tasks
@torkelo torkelo added the no-changelog Skip including change in changelog/release notes label Nov 26, 2024
Comment on lines 89 to 114
<Stack gap={3} alignItems={'center'}>
<Checkbox
label="Enable element selection"
value={elementSelectionState.enabled}
onChange={() => onToggleElementSelection()}
/>
Selected:
{elementSelectionState.selected.map((item) => (
<span key={item.id}>{item.id}</span>
))}
</Stack>
<ElementSelectionContext.Provider value={elementSelectionState}>
<VizGridLayout>
<PanelChrome title={'Panel title 1'} selectionId="panel-1">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 2'} selectionId="panel-2">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 3'} selectionId="panel-3">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 4'} selectionId="panel-4">
Hello
</PanelChrome>
</VizGridLayout>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding this demo impl, thoughts

  • Remove it
  • Move it to a storybook story for ElementSelectionContext
  • Keep it here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping it here feels weird. I'd move it to storybook or remove it.

Copy link
Contributor

@bfmatei bfmatei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the overall implementation of this. Left two comments in the code but overall I really like it.

A couple of additional comments:

  • let's avoid having the timer for selection clearing. I think we can improve the existing solution in the future if it becomes a problem
  • I find providers pretty awful to work with, especially because they're causing more nesting in JSX which can lead to ugly code. However, one of my comments was about having a hook that consumes the context and encapsulates the entire logic of handling selection. Or we can have both and allow devs to use whatever feels better for them?

}

const isSelected = context.selected.some((item) => item.id === id);
const onSelect = useCallback<React.PointerEventHandler>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is breaking the rules of hooks again.

Copy link
Member Author

@torkelo torkelo Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really, if theres is no context and a context is added there will be an unmount/remount

so an early exit when there is no context is fine

Comment on lines 89 to 114
<Stack gap={3} alignItems={'center'}>
<Checkbox
label="Enable element selection"
value={elementSelectionState.enabled}
onChange={() => onToggleElementSelection()}
/>
Selected:
{elementSelectionState.selected.map((item) => (
<span key={item.id}>{item.id}</span>
))}
</Stack>
<ElementSelectionContext.Provider value={elementSelectionState}>
<VizGridLayout>
<PanelChrome title={'Panel title 1'} selectionId="panel-1">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 2'} selectionId="panel-2">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 3'} selectionId="panel-3">
Hello
</PanelChrome>
<PanelChrome title={'Panel title 4'} selectionId="panel-4">
Hello
</PanelChrome>
</VizGridLayout>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping it here feels weird. I'd move it to storybook or remove it.

@@ -14,26 +26,93 @@ export const TestStuffPage = () => {
};

const notifyApp = useAppNotification();
const [elementSelectionState, setElementSelectionState] = useState<ElementSelectionContextState>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make this a standalone hook that can be reused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bfmatei maybe, not sure what the real dashboard implementation will look like yet. But after we make that one we could we can see how re-usable it is

@torkelo torkelo merged commit 335241d into main Nov 28, 2024
16 of 17 checks passed
@torkelo torkelo deleted the element-selection-context branch November 28, 2024 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/frontend no-changelog Skip including change in changelog/release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants