-
Notifications
You must be signed in to change notification settings - Fork 10
Chart: custom context menu #4005
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
base: develop
Are you sure you want to change the base?
Conversation
Still in progress. |
Now ready for review, with point and rightClick event passed to actionFn |
# Conflicts: # CHANGELOG.md
Thanks for this @cnrudd - I will review this very shortly and reach out with any questions |
# Conflicts: # CHANGELOG.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was pre-existing code, but since we're looking at the context menu here, in Chart.ts
render function we have
return !XH.isMobileApp ? useContextMenu(coreContents, impl.contextMenu) : coreContents;
Seems like we should check impl.contextMenu
instead and return coreContents
directly if null. That way the "not supported on mobile" fact is expressed once, and we also aren't wrapping the component in the case where the user has disabled the context menu.
Thanks for making this change - I think we have all the parts we need, just wondering in my review comments here about the "packaging" - specifically the roles of ChartModel
vs. ChartLocalModel
and the need for the new ChartContextMenu
class vs a functional implementation. Have a look and LMK when you think when you have a chance.
contextMenuClickEvt: e, | ||
point | ||
}).items; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be more clear to have this work of determining the point and creating the new object done in the main ChartModel
, with the render function getting it directly off of the main model vs this impl
?
What's ChartLocalModel
doing for us here?
@amcclain @lbwexler This is ready for a 2nd look. I've implemented these changes:
|
|
||
/** | ||
* If a String, value can be '-' for a separator, or a token supported by HighCharts | ||
* for its native menu items, or a Hoist specific token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it also be any arbitrary string to insert a header? If not, think we should spec '-'
vs string
here
actionFn: () => chartModel.highchart.downloadXLS() | ||
}; | ||
default: | ||
return token; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here - wondering if this can only be -
or...
return { | ||
text: 'View in full screen', | ||
icon: Icon.expand(), | ||
hidden: !chartModel, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice that all of these have this hidden: !chartModel
- are we ever rendering this menu without a chartModel
? Should there be some early out before we reach this point?
| 'downloadPDF' | ||
| 'copyToClipboard'; | ||
|
||
export interface ChartMenuItem extends Omit<MenuItem, 'actionFn' | 'items'> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth making the naming consistent - we could drop Context
from the other exported members here (there's no other kind of menu), or make this ChartContextMenuItem
* If a String, value can be '-' for a separator, or a token supported by HighCharts | ||
* for its native menu items, or a Hoist specific token. | ||
*/ | ||
export type ChartContextMenuItemLike = ChartMenuItem | ChartContextMenuToken | string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I'm still a little confused by ChartContextMenuItemLike
and ChartMenuItemLike
below - just having a hard time following all the variations in this file, not sure if it's the naming, or if we have some extra layer or something..... Left wondering if any way to streamline a bit. Maybe if we clarify the role of the '-'
string it would help.
} | ||
|
||
function isMenuItem(item: ChartMenuItemLike): item is MenuItem { | ||
return !isString(item) && !isValidElement(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is called in just one place here, and we've already checked if the value is a string and won't reach here if so - could inline the validElement check and drop this function which I think just adds a bit more noise to the file at the moment
@@ -30,7 +30,8 @@ export function installZoomoutGesture(Highcharts) { | |||
|
|||
Highcharts.addEvent(this, 'selection', e => { | |||
if (pixelDiff < 0) { | |||
this.zoom(); // call w/o arguments resets zoom | |||
// `undefined`'s preserve defaults, last arg `false` disables animation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this change doing? Doesn't seem related to the others, or is it?
@@ -284,10 +284,10 @@ export interface MenuItem { | |||
className?: string; | |||
|
|||
/** Executed when the user clicks the menu item. */ | |||
actionFn?: (e: MouseEvent | PointerEvent) => void; | |||
actionFn?: (e: MouseEvent | PointerEvent, contextMenuEvent?: MouseEvent | PointerEvent) => void; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the first event e
is the click on a specific item within the menu, and the new second event contextMenuEvent
is the right-click that caused the menu to appear in the first place?
@@ -20,6 +20,12 @@ export type ContextMenuSpec = MenuItemLike[] | ((e: MouseEvent) => MenuItemLike[ | |||
|
|||
export interface ContextMenuProps extends HoistProps { | |||
menuItems: MenuItemLike[]; | |||
/** | |||
* Optional event because this component is sometimes used as the content in popovers, to | |||
* create menu buttons. In those cases, it is not expected that the click event contains any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because this component is sometimes used as the content in popovers, to create menu buttons
Is this not always how it's used? What's an example of the "contextual information" here, or when would you expect to find something in the first event e
? Just as a developer I'm left a little uncertain as to what I would do with one event or the other.
| ChartContextMenuItemLike[] | ||
| ((chartModel: ChartModel) => ChartContextMenuItemLike[]); | ||
|
||
export function getChartContextMenuItems( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we mark as @internal
?
if (isNil(spec) || spec === true) return ChartModel.defaultContextMenu; | ||
|
||
return spec; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great / very clear
cmp/chart/ChartModel.ts
Outdated
/** True to showContextMenu. Defaults to true. Desktop only. */ | ||
showContextMenu?: boolean; | ||
/** | ||
* True to show default ContextMenu. Defaults to true. Desktop only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* True to show default ContextMenu. Defaults to true. Desktop only. | |
* True (default) to show default ContextMenu. Supported on desktop only. |
cmp/chart/Chart.ts
Outdated
@@ -106,7 +108,7 @@ class ChartLocalModel extends HoistModel { | |||
model: ChartModel; | |||
|
|||
chartRef = createObservableRef<HTMLElement>(); | |||
contextMenu: any; | |||
contextMenu: (e) => MenuItemLike[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry we can leave this as is, but still feels like there's some way to streamline a bit more.
Currently we have
ChartLocalModel.contextMenu
- function created/captured as soon as linkedChartLocalModel.getContextMenu()
- function to create the above functionChartModel.contextMenu
- partially processed/parsed spec, might be a function that this local model calls, passing back reference toChartModel
itselfgetChartContextMenuItems()
exported util
Still wondering what we're getting from having the context menu stuff in this local model - I am probably missing some subtlety as to why we need this layer involved, but whatever that subtlety is, if we could make more clear might help whenever we revisit this code in the future.
Or maybe we could drop contextMenu from this local model altogether and have the primary ChartModel
process the specs/tokens it's given "all the way" into the function that ChartLocalModel.getContextMenu()
currently produces.
So we'd have context menu stuff in two places - the main ChartModel
and the helper util - and ChartModel
in its ctor would process the specs it's passed all the way through to the form that the rendering within Chart.ts
consumes and passes on to the useContextMenu
hook.
Don't mean to be a pain with all of this, but am having a hard time keeping track of the current flow and if it could be simpler, great - if not, no worries :)
Tight up ChartContextMenuItemLike spec.
This addresses the following tickets:
#2389
#3813
#3388
#2753
Yes, you get 4 for 1 by merging this PR. :)
Corresponding Toolbox PR with new custom context menu on Line Chart: xh/toolbox#765
Hoist P/R Checklist
Pull request authors: Review and check off the below. Items that do not apply can also be
checked off to indicate they have been considered. If unclear if a step is relevant, please leave
unchecked and note in comments.
develop
branch as of last change.If your change is still a WIP, please use the "Create draft pull request" option in the split
button below to indicate it is not ready yet for a final review.