From 5a8f75b9a374f44a6d0866ccb0b902fd6570ec80 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 28 Oct 2024 09:55:19 +0100 Subject: [PATCH 1/3] persist json value in document --- .../src/components/json-editor.tsx | 14 +++++- .../virtualized-document-json-view.spec.tsx | 44 +++++++++++++++++++ packages/hadron-document/src/document.ts | 8 ++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/compass-crud/src/components/json-editor.tsx b/packages/compass-crud/src/components/json-editor.tsx index 767c54160d8..3674fe65d38 100644 --- a/packages/compass-crud/src/components/json-editor.tsx +++ b/packages/compass-crud/src/components/json-editor.tsx @@ -76,10 +76,22 @@ const JSONEditor: React.FunctionComponent = ({ const [expanded, setExpanded] = useState(doc.expanded); const [editing, setEditing] = useState(doc.editing); const [deleting, setDeleting] = useState(doc.markedForDeletion); - const [value, setValue] = useState(() => doc.toEJSON()); + const [value, setValue] = useState( + () => doc.modifiedEJSONString ?? doc.toEJSON() + ); const [initialValue] = useState(() => doc.toEJSON()); const [containsErrors, setContainsErrors] = useState(false); + useEffect(() => { + return () => { + // When this component is used in virtualized list, the editor is + // unmounted on scroll and if the user is editing the document, the + // editor value is lost. This is a way to keep track of the editor + // value when the it's unmounted and is restored on next mount. + doc.setModifiedEJSONString(editing ? value : null); + }; + }, [value, editing, doc]); + const handleCopy = useCallback(() => { copyToClipboard?.(doc); }, [copyToClipboard, doc]); diff --git a/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx b/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx index 0c8060d267a..37672979171 100644 --- a/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx +++ b/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx @@ -12,6 +12,10 @@ import { import { type VirtualListRef } from '@mongodb-js/compass-components'; import VirtualizedDocumentJsonView from './virtualized-document-json-view'; +import { + getCodemirrorEditorValue, + setCodemirrorEditorValue, +} from '@mongodb-js/compass-editor'; const createBigDocument = (variable: number) => new HadronDocument({ @@ -168,4 +172,44 @@ describe('VirtualizedDocumentJsonView', function () { expect(() => within(documentElement).getByText('Cancel')).to.throw; expect(() => within(documentElement).getByText('Replace')).to.throw; }); + + it('preserves the edit state of document when a document goes out of visible viewport when scrolling', async function () { + const bigDocuments = Array.from({ length: 20 }, (_, idx) => + createBigDocument(idx) + ); + const listRef: VirtualListRef = React.createRef(); + render( + + ); + + let [firstDocumentElement] = screen.getAllByTestId('editable-json'); + // Trigger the edit state (we only set the edited value if the document is being edited) + userEvent.click(within(firstDocumentElement).getByLabelText('Edit')); + + let cmEditor = firstDocumentElement.querySelector( + '[data-codemirror="true"]' + ); + await setCodemirrorEditorValue(cmEditor, '{value: "edited"}'); + + // Scroll down and then scroll back up + act(() => { + listRef.current?.scrollToItem(15); + }); + act(() => { + listRef.current?.scrollToItem(0); + }); + + [firstDocumentElement] = screen.getAllByTestId('editable-json'); + cmEditor = firstDocumentElement.querySelector('[data-codemirror="true"]'); + + const value = getCodemirrorEditorValue(cmEditor); + expect(value).to.equal('{value: "edited"}'); + }); }); diff --git a/packages/hadron-document/src/document.ts b/packages/hadron-document/src/document.ts index 35c51c3995d..1670ca3ccbd 100644 --- a/packages/hadron-document/src/document.ts +++ b/packages/hadron-document/src/document.ts @@ -50,6 +50,9 @@ export class Document extends EventEmitter { maxVisibleElementsCount = DEFAULT_VISIBLE_ELEMENTS; editing = false; markedForDeletion = false; + // This is used to store the changed EJSON string when the document is modified + // via the JSONEditor. + modifiedEJSONString: string | null = null; /** * Send cancel event. @@ -456,6 +459,7 @@ export class Document extends EventEmitter { finishEditing() { if (this.editing) { this.editing = false; + this.setModifiedEJSONString(null); this.emit(DocumentEvents.EditingFinished); } } @@ -503,6 +507,10 @@ export class Document extends EventEmitter { onRemoveError(error: Error) { this.emit('remove-error', error.message); } + + setModifiedEJSONString(ejson: string | null) { + this.modifiedEJSONString = ejson; + } } export default Document; From c73a4381a76da91084dbbbb768eab414c8d99f85 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 28 Oct 2024 14:55:53 +0100 Subject: [PATCH 2/3] use ref and remove useEffect dep --- packages/compass-crud/src/components/json-editor.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/compass-crud/src/components/json-editor.tsx b/packages/compass-crud/src/components/json-editor.tsx index 3674fe65d38..399628ff92e 100644 --- a/packages/compass-crud/src/components/json-editor.tsx +++ b/packages/compass-crud/src/components/json-editor.tsx @@ -81,16 +81,20 @@ const JSONEditor: React.FunctionComponent = ({ ); const [initialValue] = useState(() => doc.toEJSON()); const [containsErrors, setContainsErrors] = useState(false); + const setModifiedEJSONStringRef = useRef<(value: string | null) => void>( + doc.setModifiedEJSONString.bind(doc) + ); useEffect(() => { + const setModifiedEJSONString = setModifiedEJSONStringRef.current; return () => { // When this component is used in virtualized list, the editor is // unmounted on scroll and if the user is editing the document, the // editor value is lost. This is a way to keep track of the editor // value when the it's unmounted and is restored on next mount. - doc.setModifiedEJSONString(editing ? value : null); + setModifiedEJSONString(editing ? value : null); }; - }, [value, editing, doc]); + }, [value, editing]); const handleCopy = useCallback(() => { copyToClipboard?.(doc); From 02dee89c04e68f8584ff5c1109d285482fd4f44e Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Tue, 29 Oct 2024 10:33:18 +0100 Subject: [PATCH 3/3] ref current assignment --- packages/compass-crud/src/components/json-editor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compass-crud/src/components/json-editor.tsx b/packages/compass-crud/src/components/json-editor.tsx index 399628ff92e..2dda284dba4 100644 --- a/packages/compass-crud/src/components/json-editor.tsx +++ b/packages/compass-crud/src/components/json-editor.tsx @@ -84,6 +84,7 @@ const JSONEditor: React.FunctionComponent = ({ const setModifiedEJSONStringRef = useRef<(value: string | null) => void>( doc.setModifiedEJSONString.bind(doc) ); + setModifiedEJSONStringRef.current = doc.setModifiedEJSONString.bind(doc); useEffect(() => { const setModifiedEJSONString = setModifiedEJSONStringRef.current;