diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/CellRenderers.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/CellRenderers.tsx index 0335397c5d7d..d705a1df0541 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/CellRenderers.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/CellRenderers.tsx @@ -60,7 +60,7 @@ export const CellViewingRenderer: React.FC< serverValue, }) => { const [isHovered, setIsHovered] = useState(false); - const {setEditedRows, setAddedRows} = useDatasetEditContext(); + const {setEditedRows, setAddedRows, setFieldEdited} = useDatasetEditContext(); const isWeaveUrl = isRefPrefixedString(value); const isEditable = @@ -77,6 +77,7 @@ export const CellViewingRenderer: React.FC< event.stopPropagation(); const existingRow = api.getRow(id); const updatedRow = {...existingRow}; + set(updatedRow, field, serverValue); api.updateRows([{id, ...updatedRow}]); api.setEditCellValue({id, field, value: serverValue}); @@ -85,6 +86,9 @@ export const CellViewingRenderer: React.FC< newMap.set(existingRow.___weave?.index, updatedRow); return newMap; }); + if (existingRow.___weave?.index !== undefined) { + setFieldEdited(existingRow.___weave.index, field, false); + } }; const getBackgroundColor = () => { @@ -107,16 +111,22 @@ export const CellViewingRenderer: React.FC< const existingRow = api.getRow(id); const updatedRow = {...existingRow, [field]: !value}; api.updateRows([{id, ...updatedRow}]); + const rowToUpdate = {...updatedRow}; + if (existingRow.___weave?.isNew) { setAddedRows(prev => { const newMap = new Map(prev); - newMap.set(existingRow.___weave?.id, updatedRow); + newMap.set(existingRow.___weave?.id, rowToUpdate); return newMap; }); } else { + if (!rowToUpdate.___weave.editedFields) { + rowToUpdate.___weave.editedFields = new Set(); + } + rowToUpdate.___weave.editedFields.add(field); setEditedRows(prev => { const newMap = new Map(prev); - newMap.set(existingRow.___weave?.index, updatedRow); + newMap.set(existingRow.___weave?.index, rowToUpdate); return newMap; }); } @@ -311,9 +321,10 @@ const NumberEditor: React.FC<{ api: any; id: string | number; field: string; -}> = ({value, onClose, api, id, field}) => { + serverValue?: any; +}> = ({value, onClose, api, id, field, serverValue}) => { const [inputValue, setInputValue] = useState(value.toString()); - const {setEditedRows, setAddedRows} = useDatasetEditContext(); + const {setEditedRows, setAddedRows, setFieldEdited} = useDatasetEditContext(); const handleValueUpdate = (newValue: string) => { setInputValue(newValue); @@ -327,6 +338,8 @@ const NumberEditor: React.FC<{ if (inputValue !== '') { const numValue = Number(inputValue); const existingRow = api.getRow(id); + const isValueChanged = numValue !== serverValue; + if (existingRow.___weave?.isNew) { setAddedRows((prev: Map) => { const newMap = new Map(prev); @@ -343,6 +356,9 @@ const NumberEditor: React.FC<{ newMap.set(existingRow.___weave?.index, updatedRow); return newMap; }); + if (isValueChanged && existingRow.___weave?.index !== undefined) { + setFieldEdited(existingRow.___weave.index, field, isValueChanged); + } } } onClose(); @@ -524,13 +540,13 @@ const StringEditor: React.FC<{ export const CellEditingRenderer: React.FC< CellEditingRendererProps -> = params => { +> = props => { const {setEditedRows, setAddedRows} = useDatasetEditContext(); - const {id, value, field, api, serverValue, preserveFieldOrder} = params; + const {id, value, field, api, serverValue, preserveFieldOrder} = props; // Convert edit params to render params const renderParams: GridRenderCellParams = { - ...params, + ...props, value, }; @@ -557,6 +573,7 @@ export const CellEditingRenderer: React.FC< api={api} id={id} field={field} + serverValue={serverValue} /> ); } @@ -569,16 +586,38 @@ export const CellEditingRenderer: React.FC< onClose={() => { const existingRow = api.getRow(id); const updatedRow = updateRow(existingRow, value); + + const isValueChanged = value !== serverValue; + const rowToUpdate = {...updatedRow}; + if (existingRow.___weave?.isNew) { setAddedRows(prev => { const newMap = new Map(prev); - newMap.set(existingRow.___weave?.id, updatedRow); + newMap.set(existingRow.___weave?.id, rowToUpdate); return newMap; }); } else { + if (!rowToUpdate.___weave.editedFields) { + rowToUpdate.___weave.editedFields = new Set(); + } + + if (isValueChanged) { + rowToUpdate.___weave.editedFields.add(field); + } else { + rowToUpdate.___weave.editedFields.delete(field); + } + setEditedRows(prev => { const newMap = new Map(prev); - newMap.set(existingRow.___weave?.index, updatedRow); + + // If we don't have any edited fields and it's not a new row, + // don't add it to the editedRows map + if (rowToUpdate.___weave.editedFields.size === 0) { + newMap.delete(existingRow.___weave?.index); + } else { + newMap.set(existingRow.___weave?.index, rowToUpdate); + } + return newMap; }); } diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetEditorContext.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetEditorContext.tsx index 85ba87eda7d5..5e944b5c943f 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetEditorContext.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/DatasetEditorContext.tsx @@ -1,8 +1,5 @@ -import isEqual from 'lodash/isEqual'; import React, {createContext, useCallback, useContext, useState} from 'react'; -import {flattenObjectPreservingWeaveTypes} from '../flattenObject'; - export interface DatasetRow { [key: string]: any; ___weave: { @@ -10,6 +7,7 @@ export interface DatasetRow { index?: number; isNew?: boolean; serverValue?: any; + editedFields?: Set; // Set of field paths that have been edited }; } @@ -17,8 +15,14 @@ interface DatasetEditContextType { /** Map of complete edited rows, keyed by row absolute index */ editedRows: Map; setEditedRows: React.Dispatch>>; - /** Get edited fields for a row */ - getEditedFields: (rowIndex: number) => {[fieldName: string]: unknown}; + /** Check if a specific field in a row has been edited */ + isFieldEdited: (rowIndex: number, fieldName: string) => boolean; + /** Mark a field as edited or not edited within the row object */ + setFieldEdited: ( + rowIndex: number, + fieldName: string, + isEdited: boolean + ) => void; /** Array of row indices that have been marked for deletion */ deletedRows: number[]; setDeletedRows: React.Dispatch>; @@ -66,46 +70,49 @@ export const DatasetEditProvider: React.FC = ({ initialAddedRows || new Map() ); - const getEditedFields = useCallback( - (rowIndex: number) => { + const isFieldEdited = useCallback( + (rowIndex: number, fieldName: string): boolean => { const editedRow = editedRows.get(rowIndex); - const originalRow = editedRow?.___weave?.serverValue ?? editedRow; if (!editedRow) { - return {}; + return false; } - const flattenedOriginalRow = - flattenObjectPreservingWeaveTypes(originalRow); - const flattenedEditedRow = flattenObjectPreservingWeaveTypes(editedRow); - return Object.fromEntries( - Object.entries(flattenedEditedRow).filter( - ([key, value]) => - !key.startsWith('___weave') && - !isEqual(value, flattenedOriginalRow[key]) - ) - ); + + return editedRow.___weave?.editedFields?.has(fieldName) ?? false; }, [editedRows] ); - // Cleanup effect to remove rows that no longer have any edits - // from the editedRows map. - React.useEffect(() => { - const rowsToRemove: number[] = []; - editedRows.forEach((editedRow, rowIndex) => { - const fields = getEditedFields(rowIndex); - if (Object.keys(fields).length === 0) { - rowsToRemove.push(rowIndex); - } - }); - - if (rowsToRemove.length > 0) { + const setFieldEdited = useCallback( + (rowIndex: number, fieldName: string, isEdited: boolean) => { setEditedRows(prev => { const newMap = new Map(prev); - rowsToRemove.forEach(index => newMap.delete(index)); + const row = newMap.get(rowIndex); + + if (!row) { + return newMap; + } + + if (!row.___weave.editedFields) { + row.___weave.editedFields = new Set(); + } + + if (isEdited) { + row.___weave.editedFields.add(fieldName); + } else { + row.___weave.editedFields.delete(fieldName); + } + + if (row.___weave.editedFields.size === 0 && !row.___weave.isNew) { + newMap.delete(rowIndex); + } else { + newMap.set(rowIndex, row); + } + return newMap; }); - } - }, [editedRows, getEditedFields]); + }, + [setEditedRows] + ); const reset = useCallback(() => { setEditedRows(new Map()); @@ -172,7 +179,8 @@ export const DatasetEditProvider: React.FC = ({ value={{ editedRows, setEditedRows, - getEditedFields, + isFieldEdited, + setFieldEdited, deletedRows, setDeletedRows, addedRows, diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/EditableDatasetView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/EditableDatasetView.tsx index 09e4667e6ee1..d2384be83908 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/EditableDatasetView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/datasets/EditableDatasetView.tsx @@ -91,11 +91,11 @@ export const EditableDatasetView: React.FC = ({ const { editedRows, - getEditedFields, deletedRows, setDeletedRows, setAddedRows, addedRows, + isFieldEdited, } = useDatasetEditContext(); const [paginationModel, setPaginationModel] = useState({ @@ -428,14 +428,14 @@ export const EditableDatasetView: React.FC = ({ } const rowIndex = params.row.___weave?.index; - const editedFields = - rowIndex != null && !params.row.___weave?.isNew - ? getEditedFields(rowIndex) - : {}; return ( = ({ return [...baseColumns, ...fieldColumns]; }, [ combinedRows, - getEditedFields, deleteRow, restoreRow, deletedRows, @@ -477,6 +476,7 @@ export const EditableDatasetView: React.FC = ({ columnWidths, preserveFieldOrder, hideRemoveForAddedRows, + isFieldEdited, ]); const handleColumnWidthChange = useCallback((params: any) => {