1- import { useEffect , useMemo , useState } from 'react' ;
2- import * as R from 'ramda' ;
31import { graphql , useFragment } from 'react-relay' ;
4- import ReactGridLayout , { useContainerWidth } from 'react-grid-layout' ;
5- import { v4 as uuid } from 'uuid' ;
6- import { Stack , Box } from '@mui/material' ;
7- import { useTheme } from '@mui/styles' ;
8- import DashboardTimeFilters from './DashboardTimeFilters' ;
2+ import Stack from '@mui/material/Stack' ;
3+ import DashboardTimeFilters from '../../../../components/dashboard/DashboardTimeFilters' ;
94import WorkspaceHeader from '../workspaceHeader/WorkspaceHeader' ;
10- import { commitMutation , handleError } from '../../../../relay/environment' ;
5+ import { commitMutation , handleError , fetchQuery } from '../../../../relay/environment' ;
116import { workspaceMutationFieldPatch } from '../WorkspaceEditionOverview' ;
127import useGranted , { EXPLORE_EXUPDATE } from '../../../../utils/hooks/useGranted' ;
13- import WorkspaceWidgetPopover from './WorkspaceWidgetPopover' ;
14- import { fromB64 , toB64 } from '../../../../utils/String' ;
15- import { deserializeDashboardManifestForFrontend , serializeDashboardManifestForBackend } from '../../../../utils/filters/filtersUtils' ;
168import useApiMutation from '../../../../utils/hooks/useApiMutation' ;
17- import DashboardViz from './DashboardViz' ;
9+ import DashboardContent from '../../../../components/dashboard/DashboardContent' ;
10+ import useDashboard from '../../../../components/dashboard/useDashboard' ;
11+
12+ const dashboardExportWidgetQuery = graphql `
13+ query DashboardWidgetExportQuery($id: String!, $widgetId: ID!) {
14+ workspace(id: $id) {
15+ toWidgetExport(widgetId: $widgetId)
16+ }
17+ }
18+ ` ;
1819
1920const dashboardLayoutMutation = graphql `
2021 mutation DashboardLayoutMutation($id: ID!, $input: [EditInput!]!) {
@@ -54,142 +55,40 @@ const dashboardFragment = graphql`
5455 }
5556` ;
5657
57- const DashboardComponent = ( { data, noToolbar = false } ) => {
58- const theme = useTheme ( ) ;
59- const [ commitWidgetImportMutation ] = useApiMutation ( dashboardImportWidgetMutation ) ;
58+ const onExportWidget = async ( id , widget ) => {
59+ const data = await fetchQuery ( dashboardExportWidgetQuery , { id, widgetId : widget . id } )
60+ . toPromise ( ) ;
61+ return data . workspace ?. toWidgetExport ;
62+ } ;
6063
64+ const DashboardComponent = ( { data, noToolbar = false } ) => {
6165 const workspace = useFragment ( dashboardFragment , data ) ;
62- const { width, containerRef } = useContainerWidth ( ) ;
63-
64- const [ deleting , setDeleting ] = useState ( false ) ;
65- const [ idToResize , setIdToResize ] = useState ( ) ;
66- const handleResize = ( updatedWidget ) => setIdToResize ( updatedWidget ) ;
66+ const [ commitWidgetImportMutation ] = useApiMutation ( dashboardImportWidgetMutation ) ;
6767
6868 const userHasEditAccess = workspace . currentUserAccessRight === 'admin'
6969 || workspace . currentUserAccessRight === 'edit' ;
7070 const userHasUpdateCapa = useGranted ( [ EXPLORE_EXUPDATE ] ) ;
7171 const userCanEdit = userHasEditAccess && userHasUpdateCapa ;
7272
73- useEffect ( ( ) => {
74- const timeout = setTimeout ( ( ) => {
75- window . dispatchEvent ( new Event ( 'resize' ) ) ;
76- } , 1200 ) ;
77- return ( ) => {
78- clearTimeout ( timeout ) ;
79- } ;
80- } , [ ] ) ;
81-
82- // Map of widget layouts, refreshed when workspace is updated (thanks to useMemo below).
83- // We use a local map of layouts to avoid a lot of computation when only changing position
84- // or dimension of widgets.
85- const [ widgetsLayouts , setWidgetsLayouts ] = useState ( { } ) ;
86-
87- // Deserialized manifest, refreshed when workspace is updated.
88- const manifest = useMemo ( ( ) => {
89- return workspace . manifest && workspace . manifest . length > 0
90- ? deserializeDashboardManifestForFrontend ( fromB64 ( workspace . manifest ) )
91- : { widgets : { } , config : { } } ;
92- } , [ workspace . manifest ] ) ;
93-
94- // Array of all widgets, refreshed when workspace is updated.
95- const widgetsArray = useMemo ( ( ) => {
96- return Object . values ( manifest . widgets ) ;
97- } , [ manifest ] ) ;
98-
99- useEffect ( ( ) => {
100- setWidgetsLayouts (
101- widgetsArray . reduce ( ( res , widget ) => {
102- res [ widget . id ] = widget . layout ;
103- return res ;
104- } , { } ) ,
105- ) ;
106- } , [ widgetsArray ] ) ;
107-
108- /**
109- * Merge a manifest with some layouts and transform it in base64.
110- *
111- * @param newManifest Manifest to merge with local changes and stringify.
112- * @param layouts Local layout changes.
113- * @returns {string } Manifest in B64.
114- */
115- const prepareManifest = ( newManifest , layouts ) => {
116- // Need to sync manifest with local layouts before sending for update.
117- // A desync occurs when resizing or moving a widget because in those cases
118- // we skip a complete reload to avoid performance issue.
119- const syncWidgets = Object . values ( newManifest . widgets ) . reduce ( ( res , widget ) => {
120- const localLayout = layouts [ widget . id ] ;
121- res [ widget . id ] = {
122- ...widget ,
123- layout : localLayout || widget . layout ,
124- } ;
125- return res ;
126- } , { } ) ;
127- const manifestToSave = {
128- ...newManifest ,
129- widgets : syncWidgets ,
130- } ;
131-
132- const strManifest = serializeDashboardManifestForBackend ( manifestToSave ) ;
133- return toB64 ( strManifest ) ;
134- } ;
135-
136- const saveManifest = ( newManifest , opts = { layouts : widgetsLayouts , noRefresh : false } ) => {
137- const { layouts, noRefresh } = opts ;
138- const newManifestEncoded = prepareManifest ( newManifest , layouts ) ;
139- // Sometimes (in case of layout adjustment) we do not want to re-fetch
140- // all the manifest because widgets data is still the same, and it's costly
141- // in performance.
73+ const onSave = ( id , newManifestEncoded , noRefresh , onCompleted ) => {
14274 const mutation = noRefresh ? dashboardLayoutMutation : workspaceMutationFieldPatch ;
143- if ( workspace . manifest !== newManifestEncoded ) {
144- commitMutation ( {
145- mutation,
146- variables : {
147- id : workspace . id ,
148- input : {
149- key : 'manifest' ,
150- value : newManifestEncoded ,
151- } ,
152- } ,
153- onCompleted : ( ) => {
154- setDeleting ( false ) ;
75+ commitMutation ( {
76+ mutation,
77+ variables : {
78+ id,
79+ input : {
80+ key : 'manifest' ,
81+ value : newManifestEncoded ,
15582 } ,
156- } ) ;
157- }
158- } ;
159-
160- const handleDateChange = ( type , value ) => {
161- let newManifest = R . assoc (
162- 'config' ,
163- R . assoc ( type , value === 'none' ? null : value , manifest . config ) ,
164- manifest ,
165- ) ;
166- if ( type === 'relativeDate' && value !== 'none' ) {
167- newManifest = R . assoc (
168- 'config' ,
169- R . assoc ( 'startDate' , null , newManifest . config ) ,
170- newManifest ,
171- ) ;
172- newManifest = R . assoc (
173- 'config' ,
174- R . assoc ( 'endDate' , null , newManifest . config ) ,
175- newManifest ,
176- ) ;
177- }
178- saveManifest ( newManifest ) ;
179- } ;
180-
181- const getNextRow = ( ) => {
182- return widgetsArray . reduce ( ( max , { layout } ) => {
183- const widgetEndRow = layout . y + layout . h ;
184- return widgetEndRow > max ? widgetEndRow : max ;
185- } , 0 ) ;
83+ } ,
84+ onCompleted,
85+ } ) ;
18686 } ;
18787
188- const importWidget = ( widgetConfig ) => {
189- const manifestEncoded = prepareManifest ( manifest , widgetsLayouts ) ;
88+ const onImportWidget = ( id , widgetConfig , manifestEncoded ) => {
19089 commitWidgetImportMutation ( {
19190 variables : {
192- id : workspace . id ,
91+ id,
19392 input : {
19493 importType : 'widget' ,
19594 file : widgetConfig ,
@@ -202,134 +101,32 @@ const DashboardComponent = ({ data, noToolbar = false }) => {
202101 } ) ;
203102 } ;
204103
205- const handleAddWidget = ( widgetConfig ) => {
206- saveManifest ( {
207- ...manifest ,
208- widgets : {
209- ...manifest . widgets ,
210- [ widgetConfig . id ] : {
211- ...widgetConfig ,
212- layout : {
213- i : widgetConfig . id ,
214- x : 0 ,
215- y : getNextRow ( ) ,
216- w : 4 ,
217- h : 2 ,
218- } ,
219- } ,
220- } ,
221- } ) ;
222- } ;
223-
224- const handleUpdateWidget = ( widgetManifest ) => {
225- const newManifest = {
226- ...manifest ,
227- widgets : { ...manifest . widgets , [ widgetManifest . id ] : widgetManifest } ,
228- } ;
229- saveManifest ( newManifest ) ;
230- } ;
231-
232- const handleDeleteWidget = ( widgetId ) => {
233- setDeleting ( true ) ;
234- const newWidgets = { ...manifest . widgets } ;
235- delete newWidgets [ widgetId ] ;
236- saveManifest ( {
237- ...manifest ,
238- widgets : newWidgets ,
239- } ) ;
240- } ;
241-
242- const handleDuplicateWidget = ( widgetToDuplicate ) => {
243- handleAddWidget ( {
244- ...widgetToDuplicate ,
245- id : uuid ( ) ,
246- } ) ;
247- } ;
248-
249- const onLayoutChange = ( layouts ) => {
250- if ( deleting ) return ;
251-
252- const newLayouts = layouts . reduce ( ( res , layout ) => {
253- res [ layout . i ] = layout ;
254- return res ;
255- } , { } ) ;
256-
257- if ( R . equals ( newLayouts , widgetsLayouts ) ) return ; // ⛔ prevent loop
258-
259- setWidgetsLayouts ( newLayouts ) ;
260- saveManifest ( manifest , { layouts : newLayouts , noRefresh : true } ) ;
261- } ;
262-
104+ const helpers = useDashboard ( { entity : workspace , onSave, onImportWidget, onExportWidget } ) ;
105+ const { handleAddWidget, handleImportWidget, handleDateChange, config } = helpers ;
263106 return (
264- < Box
265- id = "container"
266- ref = { containerRef }
267- sx = { {
268- '& .react-grid-item.react-grid-placeholder' : {
269- border : `2px solid ${ theme . palette . primary . main } ` ,
270- borderRadius : 1 ,
271- } ,
272- } }
273- >
274- < Stack gap = { 2 } >
275- { ! noToolbar && (
276- < Stack gap = { 1 } >
277- < WorkspaceHeader
278- handleAddWidget = { handleAddWidget }
279- handleImportWidget = { importWidget }
280- data = { workspace }
281- variant = "dashboard"
282- />
283- < DashboardTimeFilters
284- currentUserAccessRight = { workspace . currentUserAccessRight }
285- config = { manifest . config }
286- handleDateChange = { handleDateChange }
287- />
288- </ Stack >
289- ) }
290- < ReactGridLayout
291- className = "layout"
292- width = { width }
293- layout = { Object . values ( widgetsLayouts ) }
294- gridConfig = { { margin : [ 20 , 20 ] , rowHeight : 50 , cols : 12 , containerPadding : [ 0 , 0 ] } }
295- dragConfig = { { enabled : userCanEdit ? ! noToolbar : false , cancel : '.noDrag' } }
296- resizeConfig = { { enabled : userCanEdit ? ! noToolbar : false } }
297- onLayoutChange = { userCanEdit && ! noToolbar ? onLayoutChange : ( ) => true }
298- onResizeStart = { userCanEdit ? ( _ , { i } ) => handleResize ( i ) : undefined }
299- onResizeStop = { userCanEdit ? handleResize : undefined }
300- >
301- { widgetsArray . map ( ( widget ) => {
302- if ( ! widgetsLayouts [ widget . id ] ) return null ;
303- const popover = userCanEdit && ! noToolbar && (
304- < WorkspaceWidgetPopover
305- widget = { widget }
306- workspace = { workspace }
307- onUpdate = { handleUpdateWidget }
308- onDuplicate = { handleDuplicateWidget }
309- onDelete = { ( ) => handleDeleteWidget ( widget . id ) }
310- />
311- ) ;
312-
313- return (
314- < div
315- key = { widget . id }
316- style = { {
317- display : 'relative' ,
318- } }
319- >
320- { widget . id === idToResize ? < div /> : (
321- < DashboardViz
322- widget = { widget }
323- config = { manifest . config }
324- popover = { popover }
325- />
326- ) }
327- </ div >
328- ) ;
329- } ) }
330- </ ReactGridLayout >
331- </ Stack >
332- </ Box >
107+ < Stack gap = { 2 } >
108+ { ! noToolbar && (
109+ < Stack gap = { 1 } >
110+ < WorkspaceHeader
111+ handleAddWidget = { handleAddWidget }
112+ handleImportWidget = { handleImportWidget }
113+ data = { workspace }
114+ variant = "dashboard"
115+ />
116+ < DashboardTimeFilters
117+ currentUserAccessRight = { workspace . currentUserAccessRight }
118+ config = { config }
119+ handleDateChange = { handleDateChange }
120+ />
121+ </ Stack >
122+ )
123+ }
124+ < DashboardContent
125+ helpers = { helpers }
126+ isEditable = { userCanEdit }
127+ dashboardEntity = { workspace }
128+ />
129+ </ Stack >
333130 ) ;
334131} ;
335132
0 commit comments