From 431a03690ed131e4bb925cec465d91adfb0d9421 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 30 Sep 2024 18:26:59 -0400 Subject: fixed so that you can't set the author field from the schema view. fixed schema view to not trigger cell updates after cells have been unmounted (e.g, so that dragging a tab over a schema vew doesn't crash) --- .../collectionSchema/SchemaCellField.tsx | 190 +++++++++++---------- 1 file changed, 97 insertions(+), 93 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx index 065544ac9..e26dd9646 100644 --- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -1,22 +1,22 @@ -import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from "mobx"; -import { ObservableReactComponent } from "../../ObservableReactComponent"; -import { observer } from "mobx-react"; -import { OverlayView } from "../../OverlayView"; -import { DocumentIconContainer } from "../../nodes/DocumentIcon"; -import React, { FormEvent } from "react"; -import { FieldView, FieldViewProps } from "../../nodes/FieldView"; -import { ObjectField } from "../../../../fields/ObjectField"; -import { Doc } from "../../../../fields/Doc"; -import { DocumentView } from "../../nodes/DocumentView"; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { observer } from 'mobx-react'; +import { OverlayView } from '../../OverlayView'; +import { DocumentIconContainer } from '../../nodes/DocumentIcon'; +import React, { FormEvent } from 'react'; +import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { ObjectField } from '../../../../fields/ObjectField'; +import { Doc } from '../../../../fields/Doc'; +import { DocumentView } from '../../nodes/DocumentView'; /** * The SchemaCellField renders text in schema cells while the user is editing, and updates the * contents of the field based on user input. It handles some cell-side logic for equations, such * as how equations are broken up within the text. - * - * The current implementation parses innerHTML to create spans based on the text in the cell. + * + * The current implementation parses innerHTML to create spans based on the text in the cell. * A more robust/safer approach would directly add elements in the react structure, but this - * has been challenging to implement. + * has been challenging to implement. */ export interface SchemaCellFieldProps { @@ -26,7 +26,7 @@ export interface SchemaCellFieldProps { oneLine?: boolean; Document: Doc; fieldKey: string; - refSelectModeInfo: {enabled: boolean, currEditing: SchemaCellField | undefined}; + refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined }; highlightCells?: (text: string) => void; GetValue(): string | undefined; SetValue(value: string, shiftDown?: boolean, enterKey?: boolean): boolean; @@ -35,7 +35,6 @@ export interface SchemaCellFieldProps { @observer export class SchemaCellField extends ObservableReactComponent { - private _disposers: { [name: string]: IReactionDisposer } = {}; private _inputref: HTMLDivElement | null = null; private _unrenderedContent: string = ''; @@ -48,7 +47,7 @@ export class SchemaCellField extends ObservableReactComponent { + setTimeout(() => { this._unrenderedContent = this._props.GetValue() ?? ''; this.setContent(this._unrenderedContent); }); //must be moved to end of batch or else other docs aren't loaded, so render as d-1 in function @@ -56,9 +55,11 @@ export class SchemaCellField extends ObservableReactComponent this.setCursorPosition(this._unrenderedContent.length)); - } + } }); } else { this._overlayDisposer?.(); @@ -104,10 +105,11 @@ export class SchemaCellField extends ObservableReactComponent this._props.GetValue(), fieldVal => { + console.log('Update: ' + this._props.Document.title, this._props.fieldKey, fieldVal); this._unrenderedContent = fieldVal ?? ''; this.finalizeEdit(false, false, false); } - ) + ); } componentDidUpdate(prevProps: Readonly) { @@ -120,7 +122,10 @@ export class SchemaCellField extends ObservableReactComponent disposer?.()); this.finalizeEdit(false, true, false); @@ -129,7 +134,7 @@ export class SchemaCellField extends ObservableReactComponent { const selfRef = text === this.selfRefPattern; return `${text}`; - } + }; makeSpans = (content: string) => { let chunkedText = content; @@ -144,28 +149,28 @@ export class SchemaCellField extends ObservableReactComponent { chunkedText = chunkedText.replace(match, this.generateSpan(match, cells.get(match))); - }) + }); return chunkedText; - } + }; /** - * Sets the rendered content of the cell to save user inputs. + * Sets the rendered content of the cell to save user inputs. * @param content the content to set * @param restoreCursorPos whether the cursor should be set back to where it was rather than the 0th index; should usually be true */ - @action + @action setContent = (content: string, restoreCursorPos?: boolean) => { const pos = this.cursorPosition; this._displayedContent = this.makeSpans(content); restoreCursorPos && setTimeout(() => this.setCursorPosition(pos)); - } + }; //Called from schemaview when a cell is selected to add a reference to the equation /** @@ -181,7 +186,7 @@ export class SchemaCellField extends ObservableReactComponent this.setCursorPosition(robustPos + text.length)); - } + }; @action setIsFocused = (value: boolean) => { @@ -195,32 +200,31 @@ export class SchemaCellField extends ObservableReactComponent { const selection = window.getSelection(); - if (!selection || position === null || !this._inputref) return; - + if (!selection || position === null || !this._inputref) return; + const range = document.createRange(); range.setStart(this._inputref, 0); range.collapse(true); - + let currentPos = 0; const setRange = (nodes: NodeList) => { for (let i = 0; i < nodes.length; ++i) { const node = nodes[i]; - if (node.nodeType === Node.TEXT_NODE) { + if (node.nodeType === Node.TEXT_NODE) { if (!node.textContent) return; const nextPos = currentPos + node.textContent.length; if (position <= nextPos) { @@ -231,11 +235,10 @@ export class SchemaCellField extends ObservableReactComponent) => { @@ -289,9 +292,12 @@ export class SchemaCellField extends ObservableReactComponent this.setupRefSelect(this.refSelectConditionMet), 0) + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); break; case ' ': e.stopPropagation(); @@ -300,13 +306,16 @@ export class SchemaCellField extends ObservableReactComponent { this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(cursorPos)); - } - , 0); + }, 0); break; case 'u': // for some reason 'u' otherwise exits the editor e.stopPropagation(); break; - case 'Shift': case 'Alt': case 'Meta': case 'Control': case ':': // prettier-ignore + case 'Shift': + case 'Alt': + case 'Meta': + case 'Control': + case ':': // prettier-ignore break; // eslint-disable-next-line no-fallthrough default: @@ -323,66 +332,63 @@ export class SchemaCellField extends ObservableReactComponent { + if (this._unmounted) { + return; + } if (this._unrenderedContent.replace(this.selfRefPattern, '') !== this._unrenderedContent) { - this._dependencyMessageShown ? this._dependencyMessageShown = false : - alert(`Circular dependency detected. Please update the field at ${this.selfRefPattern}.`) + if (this._dependencyMessageShown) { + this._dependencyMessageShown = false; + } else alert(`Circular dependency detected. Please update the field at ${this.selfRefPattern}.`); this._dependencyMessageShown = true; return; } this.setContent(this._unrenderedContent); - - if (this._props.SetValue(this._unrenderedContent, shiftDown, enterKey)) { - this._editing = false; - } else { - this._editing = false; - !lostFocus && - setTimeout( - action(() => { - this._editing = true; - }), - 0 - ); + + if (!this._props.SetValue(this._unrenderedContent, shiftDown, enterKey) && !lostFocus) { + setTimeout(action(() => (this._editing = true))); } - } + this._editing = false; + }; staticDisplay = () => { - return - { - // eslint-disable-next-line react/jsx-props-no-spreading - this._props.fieldContents ? : '' - } - - } + return {this._props.fieldContents ? : ''}; + }; renderEditor = () => { return ( -
{ this._inputref = r; }} - style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, }} - onBlur={() => {this._props.refSelectModeInfo.enabled ? setTimeout(() => {this.setIsFocused(true)}, 1000) : this.finalizeEdit(false, true, false)}} - autoFocus - onInput={this.onChange} - onKeyDown={this.onKeyDown} - onPointerDown={e => {e.stopPropagation(); setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0)}} //timeout callback ensures that refSelectMode is properly set - onClick={e => e.stopPropagation} - onPointerUp={e => e.stopPropagation} - onPointerMove={e => {e.stopPropagation(); e.preventDefault()}} - dangerouslySetInnerHTML={{ __html: this._displayedContent }} - > -
+
{ + this._inputref = r; + }} + style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} + onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))} + autoFocus + onInput={this.onChange} + onKeyDown={this.onKeyDown} + onPointerDown={e => { + e.stopPropagation(); + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); + }} //timeout callback ensures that refSelectMode is properly set + onClick={e => e.stopPropagation} + onPointerUp={e => e.stopPropagation} + onPointerMove={e => { + e.stopPropagation(); + e.preventDefault(); + }} + dangerouslySetInnerHTML={{ __html: this._displayedContent }}>
); - } + }; render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); - if ((this._editing && gval !== undefined)) { + if (this._editing && gval !== undefined) { return
{this.renderEditor()}
; - } else return ( - this._props.contents instanceof ObjectField ? null : ( + } else + return this._props.contents instanceof ObjectField ? null : (
{this.staticDisplay()}
- ) - ); + ); } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2