Skip to content

Commit f43fe7d

Browse files
committed
feat(weave): dataset editing UI
1 parent 6841aa7 commit f43fe7d

File tree

6 files changed

+1055
-58
lines changed

6 files changed

+1055
-58
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {Box} from '@mui/material';
2+
import {
3+
GridEditInputCell,
4+
GridRenderCellParams,
5+
GridRenderEditCellParams,
6+
} from '@mui/x-data-grid-pro';
7+
import {Icon} from '@wandb/weave/components/Icon';
8+
import React, {useState} from 'react';
9+
import styled from 'styled-components';
10+
11+
const commonCellStyles = {
12+
height: '100%',
13+
width: '100%',
14+
fontFamily: '"Source Sans Pro", sans-serif',
15+
fontSize: '14px',
16+
lineHeight: '1.5',
17+
padding: '12px',
18+
borderRadius: '2px',
19+
transition: 'background-color 0.5s ease',
20+
};
21+
22+
interface CellViewingRendererProps extends GridRenderCellParams {
23+
isEdited?: boolean;
24+
isDeleted?: boolean;
25+
isNew?: boolean;
26+
}
27+
28+
const StyledEditCell = styled(GridEditInputCell)`
29+
textarea {
30+
height: 100% !important;
31+
padding: 12px;
32+
padding-top: 26px;
33+
font-family: 'Source Sans Pro', sans-serif;
34+
vertical-align: top;
35+
}
36+
.MuiInputBase-root {
37+
height: 100%;
38+
align-items: flex-start;
39+
}
40+
.MuiInputBase-input {
41+
height: 100% !important;
42+
}
43+
`;
44+
45+
export const CellViewingRenderer: React.FC<CellViewingRendererProps> = ({
46+
value,
47+
isEdited = false,
48+
isDeleted = false,
49+
isNew = false,
50+
api,
51+
id,
52+
field,
53+
}) => {
54+
const [isHovered, setIsHovered] = useState(false);
55+
56+
const handleEditClick = (event: React.MouseEvent) => {
57+
event.stopPropagation();
58+
api.startCellEditMode({id, field});
59+
};
60+
61+
const getBackgroundColor = () => {
62+
if (isDeleted) {
63+
return 'rgba(255, 0, 0, 0.1)';
64+
}
65+
if (isEdited) {
66+
return 'rgba(0, 128, 128, 0.1)';
67+
}
68+
if (isNew) {
69+
return 'rgba(0, 255, 0, 0.1)';
70+
}
71+
return 'transparent';
72+
};
73+
74+
return (
75+
<Box
76+
onClick={handleEditClick}
77+
onMouseEnter={() => setIsHovered(true)}
78+
onMouseLeave={() => setIsHovered(false)}
79+
sx={{
80+
...commonCellStyles,
81+
position: 'relative',
82+
cursor: 'pointer',
83+
backgroundColor: getBackgroundColor(),
84+
opacity: isDeleted ? 0.5 : 1,
85+
textDecoration: isDeleted ? 'line-through' : 'none',
86+
'&:hover': {
87+
backgroundColor: 'rgba(0, 0, 0, 0.1)',
88+
},
89+
}}>
90+
{isHovered && (
91+
<Box
92+
sx={{
93+
position: 'absolute',
94+
top: 12,
95+
right: 12,
96+
opacity: 0,
97+
transition: 'opacity 0.2s ease',
98+
cursor: 'pointer',
99+
animation: 'fadeIn 0.2s ease forwards',
100+
'@keyframes fadeIn': {
101+
from: {opacity: 0},
102+
to: {opacity: 0.5},
103+
},
104+
'&:hover': {
105+
opacity: 0.8,
106+
},
107+
}}>
108+
<Icon name="pencil-edit" height={14} width={14} />
109+
</Box>
110+
)}
111+
{value}
112+
</Box>
113+
);
114+
};
115+
116+
export const CellEditingRenderer: React.FC<
117+
GridRenderEditCellParams
118+
> = params => {
119+
return <StyledEditCell {...params} multiline={true} />;
120+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import React, {createContext, useCallback, useContext, useState} from 'react';
2+
3+
interface DatasetRow {
4+
id: string | number;
5+
[key: string]: any;
6+
}
7+
8+
interface EditedCell {
9+
[fieldName: string]: unknown;
10+
}
11+
12+
interface DatasetEditContextType {
13+
/** Map of edited cells, keyed by row absolute index */
14+
editedCellsMap: Map<number, EditedCell>;
15+
setEditedCellsMap: React.Dispatch<
16+
React.SetStateAction<Map<number, EditedCell>>
17+
>;
18+
/** Map of complete edited rows, keyed by row absolute index */
19+
editedRows: Map<number, DatasetRow>;
20+
setEditedRows: React.Dispatch<React.SetStateAction<Map<number, DatasetRow>>>;
21+
/** Callback to process row updates from the data grid */
22+
processRowUpdate: (newRow: DatasetRow, oldRow: DatasetRow) => DatasetRow;
23+
/** Array of row indices that have been marked for deletion */
24+
deletedRows: number[];
25+
setDeletedRows: React.Dispatch<React.SetStateAction<number[]>>;
26+
/** Map of newly added rows, keyed by temporary row ID */
27+
addedRows: Map<string, DatasetRow>;
28+
setAddedRows: React.Dispatch<React.SetStateAction<Map<string, DatasetRow>>>;
29+
/** Reset the context to its initial state */
30+
reset: () => void;
31+
}
32+
33+
export const DatasetEditContext = createContext<
34+
DatasetEditContextType | undefined
35+
>(undefined);
36+
37+
export const useDatasetEditContext = () => {
38+
const context = useContext(DatasetEditContext);
39+
if (!context) {
40+
throw new Error(
41+
'useDatasetEditContext must be used within a DatasetEditProvider'
42+
);
43+
}
44+
return context;
45+
};
46+
47+
interface DatasetEditProviderProps {
48+
children: React.ReactNode;
49+
}
50+
51+
export const DatasetEditProvider: React.FC<DatasetEditProviderProps> = ({
52+
children,
53+
}) => {
54+
const [editedCellsMap, setEditedCellsMap] = useState<Map<number, EditedCell>>(
55+
new Map()
56+
);
57+
const [editedRows, setEditedRows] = useState<Map<number, DatasetRow>>(
58+
new Map()
59+
);
60+
const [deletedRows, setDeletedRows] = useState<number[]>([]);
61+
const [addedRows, setAddedRows] = useState<Map<string, DatasetRow>>(
62+
new Map()
63+
);
64+
65+
const processRowUpdate = useCallback(
66+
(newRow: DatasetRow, oldRow: DatasetRow): DatasetRow => {
67+
const changedField = Object.keys(newRow).find(
68+
key => newRow[key] !== oldRow[key] && key !== 'id'
69+
);
70+
71+
if (changedField) {
72+
const rowKey = String(newRow.id);
73+
const rowIndex = newRow._index;
74+
if (rowKey.startsWith('new-')) {
75+
setAddedRows(prev => {
76+
const updatedMap = new Map(prev);
77+
updatedMap.set(rowKey, newRow);
78+
return updatedMap;
79+
});
80+
} else {
81+
setEditedCellsMap(prev => {
82+
const existingEdits = prev.get(rowIndex) || {};
83+
const updatedMap = new Map(prev);
84+
updatedMap.set(rowIndex, {
85+
...existingEdits,
86+
[changedField]: newRow[changedField],
87+
});
88+
return updatedMap;
89+
});
90+
setEditedRows(prev => {
91+
const updatedMap = new Map(prev);
92+
updatedMap.set(rowIndex, newRow);
93+
return updatedMap;
94+
});
95+
}
96+
}
97+
return newRow;
98+
},
99+
[]
100+
);
101+
102+
const reset = useCallback(() => {
103+
setEditedCellsMap(new Map());
104+
setEditedRows(new Map());
105+
setDeletedRows([]);
106+
setAddedRows(new Map());
107+
}, []);
108+
109+
return (
110+
<DatasetEditContext.Provider
111+
value={{
112+
editedCellsMap,
113+
setEditedCellsMap,
114+
editedRows,
115+
setEditedRows,
116+
processRowUpdate,
117+
deletedRows,
118+
setDeletedRows,
119+
addedRows,
120+
setAddedRows,
121+
reset,
122+
}}>
123+
{children}
124+
</DatasetEditContext.Provider>
125+
);
126+
};

0 commit comments

Comments
 (0)