-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
380 additions
and
13 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
weave-js/src/components/PagePanelComponents/Home/Browse3/grid/pagination.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
302 changes: 302 additions & 0 deletions
302
.../src/components/PagePanelComponents/Home/Browse3/pages/CallPage/EditableDataTableView.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
import {Box, Button as MuiButton, Modal, Typography} from '@mui/material'; | ||
import {GridPaginationModel, GridRenderCellParams} from '@mui/x-data-grid-pro'; | ||
import React, {FC, useCallback, useMemo, useState} from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
import {isWeaveObjectRef, parseRefMaybe} from '../../../../../../react'; | ||
import {Tooltip} from '../../../../../Tooltip'; | ||
import {flattenObjectPreservingWeaveTypes} from '../../../Browse2/browse2Util'; | ||
import {DEFAULT_PAGE_SIZE} from '../../grid/pagination'; | ||
import {StyledDataGrid} from '../../StyledDataGrid'; | ||
import {A} from '../common/Links'; | ||
import {useWFHooks} from '../wfReactInterface/context'; | ||
import {SortBy} from '../wfReactInterface/traceServerClientTypes'; | ||
import {RowId} from './DataTableView'; // Import shared components | ||
|
||
export type TableUpdateSpec = TableAppendSpec | TablePopSpec | TableInsertSpec; | ||
|
||
type TableAppendSpec = { | ||
append: { | ||
row: Record<string, any>; | ||
}; | ||
}; | ||
|
||
type TablePopSpec = { | ||
pop: { | ||
index: number; | ||
}; | ||
}; | ||
|
||
type TableInsertSpec = { | ||
insert: { | ||
index: number; | ||
row: Record<string, any>; | ||
}; | ||
}; | ||
|
||
interface EditableDataTableViewProps { | ||
datasetObjectId: string; | ||
datasetVersionIndex: number; | ||
tableRefUri: string; | ||
fullHeight?: boolean; | ||
} | ||
|
||
// Add styled component for edited cells | ||
const StyledBox = styled(Box)` | ||
.edited-cell { | ||
background-color: rgba(25, 118, 210, 0.1); | ||
border-radius: 4px; | ||
transition: background-color 0.3s ease; | ||
} | ||
`; | ||
|
||
export const EditableDataTableView: FC<EditableDataTableViewProps> = props => { | ||
const {useTableRowsQuery, useTableQueryStats, useObjCreate} = useWFHooks(); | ||
const [sortBy] = useState<SortBy[]>([]); | ||
const [editedCellsMap, setEditedCellsMap] = useState<Map<string, any>>( | ||
new Map() | ||
); | ||
|
||
const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({ | ||
page: 0, | ||
pageSize: DEFAULT_PAGE_SIZE, | ||
}); | ||
|
||
const [isPublishModalOpen, setIsPublishModalOpen] = useState(false); | ||
|
||
// Parse table ref | ||
const parsedRef = useMemo( | ||
() => parseRefMaybe(props.tableRefUri), | ||
[props.tableRefUri] | ||
); | ||
const lookupKey = useMemo(() => { | ||
if ( | ||
parsedRef == null || | ||
!isWeaveObjectRef(parsedRef) || | ||
parsedRef.weaveKind !== 'table' | ||
) { | ||
return null; | ||
} | ||
return { | ||
entity: parsedRef.entityName, | ||
project: parsedRef.projectName, | ||
digest: parsedRef.artifactVersion, | ||
}; | ||
}, [parsedRef]); | ||
|
||
// Fetch row count | ||
const numRowsQuery = useTableQueryStats( | ||
lookupKey?.entity ?? '', | ||
lookupKey?.project ?? '', | ||
lookupKey?.digest ?? '', | ||
{skip: lookupKey == null} | ||
); | ||
|
||
// Fetch rows | ||
const fetchQuery = useTableRowsQuery( | ||
lookupKey?.entity ?? '', | ||
lookupKey?.project ?? '', | ||
lookupKey?.digest ?? '', | ||
undefined, | ||
paginationModel.pageSize, | ||
paginationModel.page * paginationModel.pageSize, | ||
sortBy, | ||
{skip: lookupKey == null} | ||
); | ||
|
||
// Convert data to list of dictionaries and flatten nested objects | ||
const dataAsListOfDict = useMemo(() => { | ||
return (fetchQuery.result?.rows ?? []).map(row => { | ||
let val = row; | ||
if (val == null) { | ||
return {}; | ||
} else if (typeof val === 'object' && !Array.isArray(val)) { | ||
if ('val' in val) { | ||
val = val.val; // Extract val field | ||
} | ||
return flattenObjectPreservingWeaveTypes(val); | ||
} | ||
return {'': val}; | ||
}); | ||
}, [fetchQuery.result?.rows]); | ||
|
||
// Reapply edits when rows are fetched | ||
const rows = useMemo(() => { | ||
if (!fetchQuery.loading && fetchQuery.result?.rows) { | ||
return dataAsListOfDict.map((row, i) => { | ||
const digest = fetchQuery.result!.rows[i].digest; | ||
const rowKey = `${digest}`; | ||
const editedRow = editedCellsMap.get(rowKey); | ||
const baseRow = editedRow ? {...row, ...editedRow} : row; | ||
return { | ||
id: digest, | ||
...baseRow, | ||
}; | ||
}); | ||
} | ||
return []; | ||
}, [fetchQuery.loading, fetchQuery.result, dataAsListOfDict, editedCellsMap]); | ||
|
||
// Generate columns with cell class names for edited cells | ||
const columns = useMemo(() => { | ||
const firstRow = rows[0] ?? {}; | ||
return Object.keys(firstRow).map(field => { | ||
if (field === 'id') { | ||
return { | ||
field, | ||
headerName: 'id', | ||
width: 50, | ||
editable: false, | ||
filterable: false, | ||
sortable: false, | ||
renderCell: (params: GridRenderCellParams) => { | ||
const id = params.value; | ||
const rowLabel = id ? id.slice(-4) : params.id; | ||
const rowSpan = ( | ||
<Tooltip trigger={<RowId>{rowLabel}</RowId>} content={id} /> | ||
); | ||
return <A onClick={() => {}}>{rowSpan}</A>; | ||
}, | ||
}; | ||
} | ||
return { | ||
field, | ||
headerName: field, | ||
flex: 1, | ||
editable: true, | ||
sortable: false, | ||
filterable: false, | ||
cellClassName: (params: any) => { | ||
const rowKey = `${params.row.id}`; | ||
const editedRow = editedCellsMap.get(rowKey); | ||
return editedRow && editedRow[field] !== undefined | ||
? 'edited-cell' | ||
: ''; | ||
}, | ||
}; | ||
}); | ||
}, [rows, editedCellsMap]); | ||
|
||
// Function to convert edited cells to TableUpdateSpec | ||
const convertEditsToTableUpdateSpec = useCallback(() => { | ||
const updates: TableUpdateSpec[] = []; | ||
editedCellsMap.forEach((editedRow, rowKey) => { | ||
const rowIndex = rows.findIndex(row => row.id === rowKey); | ||
if (rowIndex !== -1) { | ||
const insertSpec: TableInsertSpec = { | ||
insert: { | ||
index: rowIndex, | ||
row: editedRow, | ||
}, | ||
}; | ||
updates.push(insertSpec); | ||
} | ||
}); | ||
return updates; | ||
}, [editedCellsMap, rows]); | ||
|
||
const objCreate = useObjCreate(); | ||
const entity = lookupKey?.entity ?? ''; | ||
const project = lookupKey?.project ?? ''; | ||
const projectId = `${entity}/${project}`; | ||
const handlePublish = useCallback(async () => { | ||
setIsPublishModalOpen(false); | ||
const tableUpdateSpecs = convertEditsToTableUpdateSpec(); | ||
const resp = objCreate(projectId, 'updateSpec', tableUpdateSpecs); | ||
console.log('resp', resp); | ||
// Here you can handle the update specs, e.g., send them to a server | ||
}, [convertEditsToTableUpdateSpec, projectId, objCreate]); | ||
|
||
// Handle cell edits | ||
const processRowUpdate = useCallback((newRow: any, oldRow: any) => { | ||
const changedField = Object.keys(newRow).find( | ||
key => newRow[key] !== oldRow[key] && key !== 'id' | ||
); | ||
|
||
if (changedField) { | ||
const rowKey = `${newRow.id}`; | ||
setEditedCellsMap(prev => { | ||
const updatedMap = new Map(prev); | ||
const existingEdits = updatedMap.get(rowKey) || {}; | ||
updatedMap.set(rowKey, { | ||
...existingEdits, | ||
[changedField]: newRow[changedField], | ||
}); | ||
return updatedMap; | ||
}); | ||
} | ||
return newRow; | ||
}, []); | ||
|
||
// Handle pagination model change | ||
const handlePaginationModelChange = useCallback( | ||
(newModel: GridPaginationModel) => { | ||
setPaginationModel(newModel); | ||
}, | ||
[] | ||
); | ||
|
||
return ( | ||
<StyledBox | ||
sx={{ | ||
height: props.fullHeight ? '50%' : 400, | ||
width: '100%', | ||
}}> | ||
<StyledDataGrid | ||
rows={rows} | ||
columns={columns} | ||
editMode="cell" | ||
pagination={true} | ||
paginationMode="server" | ||
paginationModel={paginationModel} | ||
onPaginationModelChange={handlePaginationModelChange} | ||
rowCount={numRowsQuery.result?.count ?? 0} | ||
pageSizeOptions={[5, 10, 20, 50, 100]} | ||
disableMultipleColumnsSorting | ||
loading={fetchQuery.loading} | ||
disableRowSelectionOnClick | ||
sx={{border: 'none'}} | ||
processRowUpdate={processRowUpdate} | ||
/> | ||
<MuiButton | ||
variant="contained" | ||
onClick={() => setIsPublishModalOpen(true)}> | ||
Publish | ||
</MuiButton> | ||
<Modal | ||
open={isPublishModalOpen} | ||
onClose={() => setIsPublishModalOpen(false)} | ||
aria-labelledby="publish-modal-title" | ||
aria-describedby="publish-modal-description"> | ||
<Box | ||
sx={{ | ||
position: 'absolute', | ||
top: '50%', | ||
left: '50%', | ||
transform: 'translate(-50%, -50%)', | ||
width: 400, | ||
bgcolor: 'background.paper', | ||
boxShadow: 24, | ||
p: 4, | ||
}}> | ||
<Typography id="publish-modal-title" variant="h6" component="h2"> | ||
Confirm Publish | ||
</Typography> | ||
<Typography id="publish-modal-description" sx={{mt: 2}}> | ||
Are you sure you want to publish the changes? | ||
</Typography> | ||
<Box sx={{mt: 2, display: 'flex', justifyContent: 'flex-end'}}> | ||
<MuiButton | ||
onClick={() => setIsPublishModalOpen(false)} | ||
sx={{mr: 1}}> | ||
Cancel | ||
</MuiButton> | ||
<MuiButton variant="contained" onClick={handlePublish}> | ||
Publish | ||
</MuiButton> | ||
</Box> | ||
</Box> | ||
</Modal> | ||
</StyledBox> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.