1- import React , { FC , useCallback , useEffect } from 'react' ;
2-
1+ import React , { FC , memo , useCallback , useEffect , useMemo } from 'react' ;
32import { PermissionTypes } from '@labkey/api' ;
3+ import classNames from 'classnames' ;
44
55import { DataViewInfo } from '../../internal/DataViewInfo' ;
66
7- import { blurActiveElement } from '../../internal/util/utils' ;
8-
97import { DropdownButton , MenuDivider , MenuHeader , MenuItem } from '../../internal/dropdowns' ;
108
119import { useServerContext } from '../../internal/components/base/ServerContext' ;
@@ -16,29 +14,57 @@ import { ChartBuilderMenuItem } from '../../internal/components/chart/ChartBuild
1614import { hasPermissions } from '../../internal/components/base/models/User' ;
1715
1816import { RequiresModelAndActions } from './withQueryModels' ;
17+ import { DisableableMenuItem } from '../../internal/components/samples/DisableableMenuItem' ;
18+
19+ const MAX_CHARTS = 5 ;
20+ const DISABLED_MESSAGE = `Only ${ MAX_CHARTS } charts can be shown at once.` ;
1921
2022interface ChartMenuItemProps {
2123 chart : DataViewInfo ;
22- showChart : ( chart : DataViewInfo ) => void ;
24+ selectChart : ( reportId : string , selected : boolean ) => void ;
25+ selectedReportIds : string [ ] ;
2326}
2427
25- export const ChartMenuItem : FC < ChartMenuItemProps > = ( { chart, showChart } ) => {
26- const onClick = useCallback ( ( ) => showChart ( chart ) , [ showChart , chart ] ) ;
28+ export const ChartMenuItem : FC < ChartMenuItemProps > = ( { chart, selectChart, selectedReportIds } ) => {
29+ const { reportId } = chart ;
30+ const selected = useMemo ( ( ) => selectedReportIds . includes ( reportId ) , [ reportId , selectedReportIds ] ) ;
31+ const onClick = useCallback ( ( ) => selectChart ( reportId , ! selected ) , [ reportId , selectChart , selected ] ) ;
2732 const useSVG = chart . icon ?. indexOf ( '.svg' ) > - 1 ;
33+ const className = classNames ( 'chart-menu-checkbox' , 'fa' , {
34+ 'fa-check-square' : selected ,
35+ 'fa-square-o' : ! selected ,
36+ } ) ;
37+ const disabled = ! selected && selectedReportIds . length >= MAX_CHARTS ;
2838
2939 return (
30- < MenuItem onClick = { onClick } >
31- { useSVG && < img src = { chart . icon } width = { 16 } alt = { chart . icon } /> }
40+ < DisableableMenuItem disabled = { disabled } disabledMessage = { DISABLED_MESSAGE } onClick = { onClick } >
41+ < span className = { className } />
42+ { useSVG && < img alt = { chart . icon } src = { chart . icon } width = { 16 } /> }
3243 { ! useSVG && < i className = { `chart-menu-icon ${ chart . iconCls ?? '' } ` } /> }
3344 < span className = "chart-menu-label" > { chart . name } </ span >
34- </ MenuItem >
45+ </ DisableableMenuItem >
3546 ) ;
3647} ;
48+ ChartMenuItem . displayName = 'ChartMenuItem' ;
49+
50+ interface ChartMenuTitleProps {
51+ isLoading : boolean ;
52+ }
53+ export const ChartMenuTitle : FC < ChartMenuTitleProps > = memo ( ( { isLoading } ) => {
54+ if ( isLoading ) return < span className = "fa fa-spinner fa-pulse" /> ;
55+ return (
56+ < span >
57+ < span className = "fa fa-area-chart" />
58+ < span > Charts</ span >
59+ </ span >
60+ ) ;
61+ } ) ;
62+ ChartMenuTitle . displayName = 'ChartMenuTitle' ;
3763
38- export const ChartMenu : FC < RequiresModelAndActions > = props => {
39- const { model, actions } = props ;
64+ export const ChartMenu : FC < RequiresModelAndActions > = memo ( ( { actions, model } ) => {
4065 const { moduleContext, user } = useServerContext ( ) ;
41- const { charts, chartsError, hasCharts, isLoading, isLoadingCharts, rowsError, queryInfoError } = model ;
66+ const { charts, chartsError, hasCharts, isLoading, isLoadingCharts, rowsError, selectedReportIds, queryInfoError } =
67+ model ;
4268 const viewCharts = charts ?. filter ( chart => chart . viewName === model . schemaQuery . viewName ) ?? [ ] ; // filter chart menu based on selected view
4369 const privateCharts = hasCharts ? viewCharts . filter ( chart => ! chart . shared ) : [ ] ;
4470 const publicCharts = hasCharts ? viewCharts . filter ( chart => chart . shared ) : [ ] ;
@@ -49,55 +75,51 @@ export const ChartMenu: FC<RequiresModelAndActions> = props => {
4975 const hasError = queryInfoError !== undefined || rowsError !== undefined ;
5076 const disabled = isLoading || isLoadingCharts || hasError || ( noCharts && ! showCreateChart ) ;
5177
52- useEffect (
53- ( ) => {
54- actions . loadCharts ( model . id ) ;
55- } ,
56- [
57- /* on mount */
58- ]
59- ) ;
78+ useEffect ( ( ) => {
79+ actions . loadCharts ( model . id ) ;
80+ } , [ ] ) ; // eslint-disable-line react-hooks/exhaustive-deps -- only desired on mount
6081
61- const chartClicked = useCallback (
62- ( chart : DataViewInfo ) : void => {
63- blurActiveElement ( ) ;
64- actions . selectReport ( model . id , chart . reportId ) ;
82+ const selectChart = useCallback (
83+ ( reportId : string , selected : boolean ) : void => {
84+ actions . selectReport ( model . id , reportId , selected ) ;
6585 } ,
6686 [ actions , model ]
6787 ) ;
6888
69- if ( noCharts && ! showCreateChart ) {
70- return null ;
71- }
89+ if ( noCharts && ! showCreateChart ) return null ;
7290
7391 return (
7492 < div className = "chart-menu" >
7593 < DropdownButton
7694 buttonClassName = "chart-menu-button"
7795 disabled = { disabled }
7896 pullRight
79- title = {
80- isLoadingCharts ? (
81- < span className = "fa fa-spinner fa-pulse" />
82- ) : (
83- < span >
84- < span className = "fa fa-area-chart" />
85- < span > Charts</ span >
86- </ span >
87- )
88- }
97+ title = { < ChartMenuTitle isLoading = { isLoadingCharts } /> }
8998 >
9099 { chartsError !== undefined && < MenuItem > { chartsError } </ MenuItem > }
91100
92- { showCreateChart && < ChartBuilderMenuItem actions = { actions } model = { model } /> }
101+ { showCreateChart && (
102+ < ChartBuilderMenuItem
103+ actions = { actions }
104+ disabledMessage = { DISABLED_MESSAGE }
105+ maxCharts = { MAX_CHARTS }
106+ model = { model }
107+ selectedReportIds = { selectedReportIds }
108+ />
109+ ) }
93110
94111 { showCreateChartDivider && < MenuDivider /> }
95112
96113 { privateCharts . length > 0 && < MenuHeader text = "Your Charts" /> }
97114
98115 { privateCharts . length > 0 &&
99116 privateCharts . map ( chart => (
100- < ChartMenuItem key = { chart . reportId } chart = { chart } showChart = { chartClicked } />
117+ < ChartMenuItem
118+ chart = { chart }
119+ key = { chart . reportId }
120+ selectChart = { selectChart }
121+ selectedReportIds = { selectedReportIds }
122+ />
101123 ) ) }
102124
103125 { privateCharts . length > 0 && publicCharts . length > 0 && < MenuDivider /> }
@@ -106,9 +128,15 @@ export const ChartMenu: FC<RequiresModelAndActions> = props => {
106128
107129 { publicCharts . length > 0 &&
108130 publicCharts . map ( chart => (
109- < ChartMenuItem key = { chart . reportId } chart = { chart } showChart = { chartClicked } />
131+ < ChartMenuItem
132+ chart = { chart }
133+ key = { chart . reportId }
134+ selectChart = { selectChart }
135+ selectedReportIds = { selectedReportIds }
136+ />
110137 ) ) }
111138 </ DropdownButton >
112139 </ div >
113140 ) ;
114- } ;
141+ } ) ;
142+ ChartMenu . displayName = 'ChartMenu' ;
0 commit comments