Skip to content

Commit

Permalink
feat(weave): dataset editing UI
Browse files Browse the repository at this point in the history
  • Loading branch information
bcsherma committed Jan 10, 2025
1 parent 6841aa7 commit 536be2e
Show file tree
Hide file tree
Showing 6 changed files with 1,076 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {Box, Popover} from '@mui/material';
import {
GridEditInputCell,
GridRenderCellParams,
GridRenderEditCellParams,
} from '@mui/x-data-grid-pro';
import {Icon} from '@wandb/weave/components/Icon';
import React, {useState} from 'react';
import styled from 'styled-components';

const commonCellStyles = {
height: '100%',
width: '100%',
fontFamily: '"Source Sans Pro", sans-serif',
fontSize: '14px',
lineHeight: '1.5',
padding: '8px 12px',
display: 'flex',
alignItems: 'center',
transition: 'background-color 0.2s ease',
};

interface CellViewingRendererProps extends GridRenderCellParams {
isEdited?: boolean;
isDeleted?: boolean;
isNew?: boolean;
}

const StyledEditCell = styled(GridEditInputCell)`
textarea {
height: 100% !important;
padding: 8px 12px;
font-family: 'Source Sans Pro', sans-serif;
font-size: 14px;
line-height: 1.5;
}
.MuiInputBase-root {
height: 100%;
padding: 0;
}
.MuiInputBase-input {
height: 100% !important;
}
`;

export const CellViewingRenderer: React.FC<CellViewingRendererProps> = ({
value,
isEdited = false,
isDeleted = false,
isNew = false,
api,
id,
field,
}) => {
const [isHovered, setIsHovered] = useState(false);

const handleEditClick = (event: React.MouseEvent) => {
event.stopPropagation();
api.startCellEditMode({id, field});
};

const getBackgroundColor = () => {
if (isDeleted) {
return 'rgba(255, 0, 0, 0.1)';
}
if (isEdited) {
return 'rgba(0, 128, 128, 0.1)';
}
if (isNew) {
return 'rgba(0, 255, 0, 0.1)';
}
return 'transparent';
};

return (
<Box
onClick={handleEditClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
sx={{
...commonCellStyles,
position: 'relative',
cursor: 'pointer',
backgroundColor: getBackgroundColor(),
opacity: isDeleted ? 0.5 : 1,
textDecoration: isDeleted ? 'line-through' : 'none',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)',
},
}}>
<span style={{flex: 1}}>{value}</span>
{isHovered && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
opacity: 0,
transition: 'opacity 0.2s ease',
cursor: 'pointer',
animation: 'fadeIn 0.2s ease forwards',
'@keyframes fadeIn': {
from: {opacity: 0},
to: {opacity: 0.5},
},
'&:hover': {
opacity: 0.8,
},
}}>
<Icon name="pencil-edit" height={14} width={14} />
</Box>
)}
</Box>
);
};

export const CellEditingRenderer: React.FC<
GridRenderEditCellParams
> = params => {
return (
<>
<CellViewingRenderer {...params} />
<Popover
open={true}
anchorEl={params.api.getCellElement(params.id, params.field)}
onClose={() =>
params.api.stopCellEditMode({id: params.id, field: params.field})
}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
sx={{
'& .MuiPopover-paper': {
minWidth: '200px',
minHeight: '100px',
padding: '8px',
},
}}>
<StyledEditCell {...params} multiline={true} />
</Popover>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, {createContext, useCallback, useContext, useState} from 'react';

interface DatasetRow {
// id: string | number;
[key: string]: any;
___weave: {
id: string;
// The index must be set for rows of an existing dataset.
index?: number;
isNew?: boolean;
};
}

interface EditedCell {
[fieldName: string]: unknown;
}

interface DatasetEditContextType {
/** Map of edited cells, keyed by row absolute index */
editedCellsMap: Map<number, EditedCell>;
setEditedCellsMap: React.Dispatch<
React.SetStateAction<Map<number, EditedCell>>
>;
/** Map of complete edited rows, keyed by row absolute index */
editedRows: Map<number, DatasetRow>;
setEditedRows: React.Dispatch<React.SetStateAction<Map<number, DatasetRow>>>;
/** Callback to process row updates from the data grid */
processRowUpdate: (newRow: DatasetRow, oldRow: DatasetRow) => DatasetRow;
/** Array of row indices that have been marked for deletion */
deletedRows: number[];
setDeletedRows: React.Dispatch<React.SetStateAction<number[]>>;
/** Map of newly added rows, keyed by temporary row ID */
addedRows: Map<string, DatasetRow>;
setAddedRows: React.Dispatch<React.SetStateAction<Map<string, DatasetRow>>>;
/** Reset the context to its initial state */
reset: () => void;
}

export const DatasetEditContext = createContext<
DatasetEditContextType | undefined
>(undefined);

export const useDatasetEditContext = () => {
const context = useContext(DatasetEditContext);
if (!context) {
throw new Error(
'useDatasetEditContext must be used within a DatasetEditProvider'
);
}
return context;
};

interface DatasetEditProviderProps {
children: React.ReactNode;
}

export const DatasetEditProvider: React.FC<DatasetEditProviderProps> = ({
children,
}) => {
const [editedCellsMap, setEditedCellsMap] = useState<Map<number, EditedCell>>(
new Map()
);
const [editedRows, setEditedRows] = useState<Map<number, DatasetRow>>(
new Map()
);
const [deletedRows, setDeletedRows] = useState<number[]>([]);
const [addedRows, setAddedRows] = useState<Map<string, DatasetRow>>(
new Map()
);

const processRowUpdate = useCallback(
(newRow: DatasetRow, oldRow: DatasetRow): DatasetRow => {
const changedField = Object.keys(newRow).find(
key => newRow[key] !== oldRow[key] && key !== 'id'
);

if (changedField) {
const rowKey = String(oldRow.___weave.id);
const rowIndex = oldRow.___weave.index;
if (oldRow.___weave.isNew) {
setAddedRows(prev => {
const updatedMap = new Map(prev);
updatedMap.set(rowKey, newRow);
return updatedMap;
});
} else {
setEditedCellsMap(prev => {
const existingEdits = prev.get(rowIndex!) || {};
const updatedMap = new Map(prev);
updatedMap.set(rowIndex!, {
...existingEdits,
[changedField]: newRow[changedField],
});
return updatedMap;
});
setEditedRows(prev => {
const updatedMap = new Map(prev);
updatedMap.set(rowIndex!, newRow);
return updatedMap;
});
}
}
return newRow;
},
[]
);

const reset = useCallback(() => {
setEditedCellsMap(new Map());
setEditedRows(new Map());
setDeletedRows([]);
setAddedRows(new Map());
}, []);

return (
<DatasetEditContext.Provider
value={{
editedCellsMap,
setEditedCellsMap,
editedRows,
setEditedRows,
processRowUpdate,
deletedRows,
setDeletedRows,
addedRows,
setAddedRows,
reset,
}}>
{children}
</DatasetEditContext.Provider>
);
};
Loading

0 comments on commit 536be2e

Please sign in to comment.