@@ -63,6 +63,9 @@ import { ModelSelectionButton } from '../views/ModelSelectionDialog';
63
63
import { TableCopyDialogV2 } from '../views/TableSelectionView' ;
64
64
import { TableUploadDialog } from '../views/TableSelectionView' ;
65
65
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' ;
66
+ import ContentPasteIcon from '@mui/icons-material/ContentPaste' ;
67
+ import UploadFileIcon from '@mui/icons-material/UploadFile' ;
68
+ import DownloadIcon from '@mui/icons-material/Download' ;
66
69
67
70
const AppBar = styled ( MuiAppBar ) ( ( { theme } ) => ( {
68
71
color : 'black' ,
@@ -111,46 +114,49 @@ export const ImportStateButton: React.FC<{}> = ({ }) => {
111
114
} ;
112
115
113
116
return (
114
- < Tooltip title = "load a saved session" >
115
- < Button
116
- variant = "text"
117
- color = "primary"
118
- onClick = { ( ) => inputRef . current ?. click ( ) }
119
- >
120
- < Input
121
- inputProps = { {
122
- accept : '.dfstate' ,
123
- multiple : false
124
- } }
125
- id = "upload-data-file"
126
- type = " file"
127
- sx = { { display : 'none' } }
128
- inputRef = { inputRef }
129
- onChange = { handleFileUpload }
130
- />
131
- Import
132
- </ Button >
133
- </ Tooltip >
117
+ < Button
118
+ variant = "text"
119
+ color = "primary"
120
+ sx = { { textTransform : 'none' } }
121
+ onClick = { ( ) => inputRef . current ?. click ( ) }
122
+ startIcon = { < UploadFileIcon /> }
123
+ >
124
+ < Input
125
+ inputProps = { {
126
+ accept : '.dfstate' ,
127
+ multiple : false
128
+ } }
129
+ id = "upload-data- file"
130
+ type = "file"
131
+ sx = { { display : 'none' } }
132
+ inputRef = { inputRef }
133
+ onChange = { handleFileUpload }
134
+ />
135
+ import a saved session
136
+ </ Button >
134
137
) ;
135
138
}
136
139
137
140
export const ExportStateButton : React . FC < { } > = ( { } ) => {
138
141
const fullStateJson = useSelector ( ( state : DataFormulatorState ) => JSON . stringify ( state ) ) ;
139
142
140
143
return < Tooltip title = "save session locally" >
141
- < Button variant = "text" onClick = { ( ) => {
142
- function download ( content : string , fileName : string , contentType : string ) {
143
- let a = document . createElement ( "a" ) ;
144
- let file = new Blob ( [ content ] , { type : contentType } ) ;
145
- a . href = URL . createObjectURL ( file ) ;
146
- a . download = fileName ;
147
- a . click ( ) ;
148
- }
149
- download ( fullStateJson , `data-formulator.${ new Date ( ) . toISOString ( ) } .dfstate` , 'text/plain' ) ;
150
- } }
151
- //endIcon={<OutputIcon />}
144
+ < Button
145
+ variant = "text"
146
+ sx = { { textTransform : 'none' } }
147
+ onClick = { ( ) => {
148
+ function download ( content : string , fileName : string , contentType : string ) {
149
+ let a = document . createElement ( "a" ) ;
150
+ let file = new Blob ( [ content ] , { type : contentType } ) ;
151
+ a . href = URL . createObjectURL ( file ) ;
152
+ a . download = fileName ;
153
+ a . click ( ) ;
154
+ }
155
+ download ( fullStateJson , `data-formulator.${ new Date ( ) . toISOString ( ) } .dfstate` , 'text/plain' ) ;
156
+ } }
157
+ startIcon = { < DownloadIcon /> }
152
158
>
153
- Export
159
+ export session
154
160
</ Button >
155
161
</ Tooltip >
156
162
}
@@ -163,6 +169,132 @@ export const toolName = "Data Formulator"
163
169
export interface AppFCProps {
164
170
}
165
171
172
+ // Extract menu components into separate components to prevent full app re-renders
173
+ const TableMenu : React . FC = ( ) => {
174
+ const [ anchorEl , setAnchorEl ] = useState < null | HTMLElement > ( null ) ;
175
+ const open = Boolean ( anchorEl ) ;
176
+
177
+ return (
178
+ < >
179
+ < Button
180
+ variant = "text"
181
+ onClick = { ( e ) => setAnchorEl ( e . currentTarget ) }
182
+ endIcon = { < KeyboardArrowDownIcon /> }
183
+ aria-controls = { open ? 'add-table-menu' : undefined }
184
+ aria-haspopup = "true"
185
+ aria-expanded = { open ? 'true' : undefined }
186
+ sx = { { textTransform : 'none' } }
187
+ >
188
+ Add Table
189
+ </ Button >
190
+ < Menu
191
+ id = "add-table-menu"
192
+ anchorEl = { anchorEl }
193
+ open = { open }
194
+ onClose = { ( ) => setAnchorEl ( null ) }
195
+ MenuListProps = { {
196
+ 'aria-labelledby' : 'add-table-button' ,
197
+ sx : { py : '4px' , px : '8px' }
198
+ } }
199
+ sx = { { '& .MuiMenuItem-root' : { padding : 0 , margin : 0 } } }
200
+ >
201
+ < MenuItem onClick = { ( e ) => {
202
+ e . preventDefault ( ) ;
203
+ e . stopPropagation ( ) ;
204
+ } } >
205
+ < TableCopyDialogV2 buttonElement = {
206
+ < Typography sx = { { fontSize : 14 , textTransform : 'none' , display : 'flex' , alignItems : 'center' , gap : 1 } } >
207
+ < ContentPasteIcon fontSize = "small" />
208
+ from clipboard
209
+ </ Typography >
210
+ } disabled = { false } />
211
+ </ MenuItem >
212
+ < MenuItem onClick = { ( e ) => { } } >
213
+ < TableUploadDialog buttonElement = {
214
+ < Typography sx = { { fontSize : 14 , textTransform : 'none' , display : 'flex' , alignItems : 'center' , gap : 1 } } >
215
+ < UploadFileIcon fontSize = "small" />
216
+ from file
217
+ </ Typography >
218
+ } disabled = { false } />
219
+ </ MenuItem >
220
+ </ Menu >
221
+ </ >
222
+ ) ;
223
+ } ;
224
+
225
+ const SessionMenu : React . FC = ( ) => {
226
+ const [ anchorEl , setAnchorEl ] = useState < null | HTMLElement > ( null ) ;
227
+ const open = Boolean ( anchorEl ) ;
228
+
229
+ return (
230
+ < >
231
+ < Button
232
+ variant = "text"
233
+ onClick = { ( e ) => setAnchorEl ( e . currentTarget ) }
234
+ endIcon = { < KeyboardArrowDownIcon /> }
235
+ sx = { { textTransform : 'none' } }
236
+ >
237
+ Session
238
+ </ Button >
239
+ < Menu
240
+ id = "session-menu"
241
+ anchorEl = { anchorEl }
242
+ open = { open }
243
+ onClose = { ( ) => setAnchorEl ( null ) }
244
+ MenuListProps = { {
245
+ 'aria-labelledby' : 'session-menu-button' ,
246
+ sx : { py : '4px' , px : '8px' }
247
+ } }
248
+ sx = { { '& .MuiMenuItem-root' : { padding : 0 , margin : 0 } } }
249
+ >
250
+ < MenuItem onClick = { ( ) => { } } >
251
+ < ExportStateButton />
252
+ </ MenuItem >
253
+ < MenuItem onClick = { ( e ) => { } } >
254
+ < ImportStateButton />
255
+ </ MenuItem >
256
+ </ Menu >
257
+ </ >
258
+ ) ;
259
+ } ;
260
+
261
+ const ResetDialog : React . FC = ( ) => {
262
+ const [ open , setOpen ] = useState ( false ) ;
263
+ const dispatch = useDispatch ( ) ;
264
+
265
+ return (
266
+ < >
267
+ < Button
268
+ variant = "text"
269
+ onClick = { ( ) => setOpen ( true ) }
270
+ endIcon = { < PowerSettingsNewIcon /> }
271
+ >
272
+ Reset session
273
+ </ Button >
274
+ < Dialog onClose = { ( ) => setOpen ( false ) } open = { open } >
275
+ < DialogTitle sx = { { display : "flex" , alignItems : "center" } } > Reset Session?</ DialogTitle >
276
+ < DialogContent >
277
+ < DialogContentText >
278
+ < Typography > All unexported content (charts, derived data, concepts) will be lost upon reset.</ Typography >
279
+ </ DialogContentText >
280
+ </ DialogContent >
281
+ < DialogActions >
282
+ < Button
283
+ onClick = { ( ) => {
284
+ dispatch ( dfActions . resetState ( ) ) ;
285
+ setOpen ( false ) ;
286
+ } }
287
+ endIcon = { < PowerSettingsNewIcon /> }
288
+ >
289
+ reset session
290
+ </ Button >
291
+ < Button onClick = { ( ) => setOpen ( false ) } > cancel</ Button >
292
+ </ DialogActions >
293
+ </ Dialog >
294
+ </ >
295
+ ) ;
296
+ } ;
297
+
166
298
export const AppFC : FC < AppFCProps > = function AppFC ( appProps ) {
167
299
168
300
const visViewMode = useSelector ( ( state : DataFormulatorState ) => state . visViewMode ) ;
@@ -278,15 +410,6 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
278
410
</ Box >
279
411
)
280
412
281
- const [ anchorEl , setAnchorEl ] = useState < null | HTMLElement > ( null ) ;
282
- const open = Boolean ( anchorEl ) ;
283
- const handleClick = ( event : React . MouseEvent < HTMLButtonElement > ) => {
284
- setAnchorEl ( event . currentTarget ) ;
285
- } ;
286
- const handleClose = ( ) => {
287
- setAnchorEl ( null ) ;
288
- } ;
289
-
290
413
let appBar = [
291
414
< AppBar className = "app-bar" position = "static" key = "app-bar-main" >
292
415
< Toolbar variant = "dense" sx = { { backgroundColor : betaMode ? 'lavender' : '' } } >
@@ -314,97 +437,18 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
314
437
< ModelSelectionButton />
315
438
< Divider orientation = "vertical" variant = "middle" flexItem />
316
439
< Typography sx = { { display : 'flex' , fontSize : 14 , alignItems : 'center' , gap : 1 } } >
317
- < Button
318
- variant = "text"
319
- onClick = { handleClick }
320
- endIcon = { < KeyboardArrowDownIcon /> }
321
- aria-controls = { open ? 'add-table-menu' : undefined }
322
- aria-haspopup = "true"
323
- aria-expanded = { open ? 'true' : undefined }
324
- >
325
- Add A Table
326
- </ Button >
327
- < Menu
328
- id = "add-table-menu"
329
- anchorEl = { anchorEl }
330
- open = { open }
331
- onClose = { handleClose }
332
- MenuListProps = { {
333
- 'aria-labelledby' : 'add-table-button' ,
334
- sx : { py : '2px' }
335
- } }
336
- sx = { { '& .MuiMenuItem-root' : { padding : 0 , margin : 0 } } }
337
- >
338
- < MenuItem onClick = { ( e ) => {
339
- e . preventDefault ( ) ;
340
- e . stopPropagation ( ) ;
341
- } } sx = { { fontSize : 14 , '& .MuiButton-root' : { textTransform : 'none' } } } >
342
- < TableCopyDialogV2 buttonElement = { "from clipboard" } disabled = { false } />
343
- </ MenuItem >
344
- < MenuItem onClick = { ( e ) => {
345
- } } sx = { { fontSize : 14 , '& .MuiButton-root' : { textTransform : 'none' } } } >
346
- < TableUploadDialog buttonElement = { "from file" } disabled = { false } />
347
- </ MenuItem >
348
- </ Menu >
440
+ < TableMenu />
349
441
</ Typography >
350
442
< Divider orientation = "vertical" variant = "middle" flexItem />
351
- < ExportStateButton />
352
- < ImportStateButton />
443
+ < Typography sx = { { display : 'flex' , fontSize : 14 , alignItems : 'center' , gap : 1 } } >
444
+ < SessionMenu />
445
+ </ Typography >
353
446
< Divider orientation = "vertical" variant = "middle" flexItem />
354
- < Button variant = "text" onClick = { ( ) => { setResetDialogOpen ( true ) } } endIcon = { < PowerSettingsNewIcon /> } >
355
- Reset session
356
- </ Button >
447
+ < ResetDialog />
357
448
< Popup popupConfig = { popupConfig } appConfig = { appConfig } table = { tables [ 0 ] } />
358
- < Dialog onClose = { ( ) => { setResetDialogOpen ( false ) } } open = { resetDialogOpen } >
359
- < DialogTitle sx = { { display : "flex" , alignItems : "center" } } > Reset Session?</ DialogTitle >
360
- < DialogContent >
361
- < DialogContentText >
362
- < Typography > All unexported content (charts, derived data, concepts) will be lost upon reset.</ Typography >
363
- </ DialogContentText >
364
- </ DialogContent >
365
- < DialogActions >
366
- < Button onClick = { ( ) => { dispatch ( dfActions . resetState ( ) ) ; setResetDialogOpen ( false ) ; } } endIcon = { < PowerSettingsNewIcon /> } > reset session </ Button >
367
- < Button onClick = { ( ) => { setResetDialogOpen ( false ) ; } } > cancel</ Button >
368
- </ DialogActions >
369
- </ Dialog >
370
- { userInfo && < >
371
- < Divider orientation = "vertical" variant = "middle" flexItem />
372
- < Divider orientation = "vertical" variant = "middle" flexItem sx = { { marginRight : "6px" } } />
373
- < Avatar key = "user-avatar" { ...stringAvatar ( userInfo ?. name || 'U' ) } />
374
- < Button variant = "text" className = "ml-auto" href = "/.auth/logout" > Sign out</ Button >
375
- </ > }
376
449
</ Box >
377
450
</ Toolbar >
378
- </ AppBar > ,
379
- // <Dialog key="table-selection-dialog" onClose={()=>{setTableDialogOpen(false)}} open={tableDialogOpen}
380
- // sx={{ '& .MuiDialog-paper': { maxWidth: '80%', maxHeight: 800, minWidth: 800 } }}
381
- // >
382
- // <DialogTitle sx={{display: "flex"}}>Recently used tables
383
- // <IconButton
384
- // sx={{marginLeft: "auto"}}
385
- // edge="start"
386
- // size="small"
387
- // color="inherit"
388
- // onClick={()=>{ setTableDialogOpen(false) }}
389
- // aria-label="close"
390
- // >
391
- // <CloseIcon fontSize="inherit"/>
392
- // </IconButton>
393
- // </DialogTitle>
394
- // <DialogContent sx={{overflowX: "hidden", padding: 0}} dividers>
395
- // {/* <TableSelectionView tables={tables}
396
- // handleDeleteTable={(index) => {
397
- // // dispatch(dfActions.removeFromRecentTables(index));
398
- // // if (recentTables.length <= 1) {
399
- // // setTableDialogOpen(false);
400
- // // }
401
- // }}
402
- // handleSelectTable={(table) => {
403
- // // dispatch(dfActions.setTable(table));
404
- // // setTableDialogOpen(false);
405
- // }}/> */ }
406
- // </ DialogContent>
407
- // </Dialog>
451
+ </ AppBar >
408
452
] ;
409
453
410
454
let router = createBrowserRouter ( [
0 commit comments