Skip to content

Commit af31d9a

Browse files
committed
fix various display issues around multi-table support
1 parent 20a2ecf commit af31d9a

File tree

7 files changed

+303
-163
lines changed

7 files changed

+303
-163
lines changed

py-src/data_formulator/agents/client_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from litellm import completion
33
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
44

5-
65
class Client(object):
76
"""
87
Returns a LiteLLM client configured for the specified endpoint and model.

src/app/App.tsx

Lines changed: 170 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ import { ModelSelectionButton } from '../views/ModelSelectionDialog';
6363
import { TableCopyDialogV2 } from '../views/TableSelectionView';
6464
import { TableUploadDialog } from '../views/TableSelectionView';
6565
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';
6669

6770
const AppBar = styled(MuiAppBar)(({ theme }) => ({
6871
color: 'black',
@@ -111,46 +114,49 @@ export const ImportStateButton: React.FC<{}> = ({ }) => {
111114
};
112115

113116
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>
134137
);
135138
}
136139

137140
export const ExportStateButton: React.FC<{}> = ({ }) => {
138141
const fullStateJson = useSelector((state: DataFormulatorState) => JSON.stringify(state));
139142

140143
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 />}
152158
>
153-
Export
159+
export session
154160
</Button>
155161
</Tooltip>
156162
}
@@ -163,6 +169,132 @@ export const toolName = "Data Formulator"
163169
export interface AppFCProps {
164170
}
165171

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+
166298
export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
167299

168300
const visViewMode = useSelector((state: DataFormulatorState) => state.visViewMode);
@@ -278,15 +410,6 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
278410
</Box>
279411
)
280412

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-
290413
let appBar = [
291414
<AppBar className="app-bar" position="static" key="app-bar-main">
292415
<Toolbar variant="dense" sx={{ backgroundColor: betaMode ? 'lavender' : '' }}>
@@ -314,97 +437,18 @@ export const AppFC: FC<AppFCProps> = function AppFC(appProps) {
314437
<ModelSelectionButton />
315438
<Divider orientation="vertical" variant="middle" flexItem />
316439
<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 />
349441
</Typography>
350442
<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>
353446
<Divider orientation="vertical" variant="middle" flexItem />
354-
<Button variant="text" onClick={() => { setResetDialogOpen(true) }} endIcon={<PowerSettingsNewIcon />}>
355-
Reset session
356-
</Button>
447+
<ResetDialog />
357448
<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-
</>}
376449
</Box>
377450
</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>
408452
];
409453

410454
let router = createBrowserRouter([

src/app/dfSlice.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,13 @@ export const dataFormulatorSlice = createSlice({
687687
export const dfSelectors = {
688688
getActiveModel: (state: DataFormulatorState) : ModelConfig => {
689689
return state.models.find(m => m.id == state.selectedModelId) || state.models[0];
690+
},
691+
getActiveBaseTableIds: (state: DataFormulatorState) => {
692+
let focusedTableId = state.focusedTableId;
693+
let tables = state.tables;
694+
let focusedTable = tables.find(t => t.id == focusedTableId);
695+
let sourceTables = focusedTable?.derive?.source || [focusedTable?.id];
696+
return sourceTables;
690697
}
691698
}
692699

0 commit comments

Comments
 (0)