diff options
| author | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2024-10-01 04:01:07 -0400 |
|---|---|---|
| committer | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2024-10-01 04:01:07 -0400 |
| commit | 96883cb177d44ed9e06e800de9b35bda36e6fd1c (patch) | |
| tree | b9c13734c07e25dbeb99c0fbebee0e3b8be09c00 /src/client/views/collections/collectionSchema | |
| parent | 115c243e57dd490dfcf09930913a941ca7ecfabc (diff) | |
| parent | 63e8e1fd38835d193930bc6103a12dc4cf4d8934 (diff) | |
Merge branch 'nathan-starter' of https://github.com/brown-dash/Dash-Web into nathan-starter
Diffstat (limited to 'src/client/views/collections/collectionSchema')
| -rw-r--r-- | src/client/views/collections/collectionSchema/SchemaCellField.tsx | 190 |
1 files changed, 97 insertions, 93 deletions
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<SchemaCellFieldProps> { - private _disposers: { [name: string]: IReactionDisposer } = {}; private _inputref: HTMLDivElement | null = null; private _unrenderedContent: string = ''; @@ -48,7 +47,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro constructor(props: SchemaCellFieldProps) { super(props); makeObservable(this); - setTimeout(() => { + 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<SchemaCellFieldPro get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore - get selfRefPattern() {return `d${this.docIndex}.${this._props.fieldKey}`}; + get selfRefPattern() { + return `d${this.docIndex}.${this._props.fieldKey}`; + } - @computed get lastCharBeforeCursor(){ + @computed get lastCharBeforeCursor() { const pos = this.cursorPosition; const content = this._unrenderedContent; const text = this._unrenderedContent.substring(0, pos ?? content.length); @@ -90,7 +91,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro this._props.highlightCells?.(this._unrenderedContent); this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); - } + } }); } else { this._overlayDisposer?.(); @@ -104,10 +105,11 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro this._disposers.fieldUpdate = reaction( () => 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<SchemaCellFieldProps>) { @@ -120,7 +122,10 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro }); } + _unmounted = false; componentWillUnmount(): void { + this._unmounted = true; + console.log('Unmount: ' + this._props.Document.title, this._props.fieldKey); this._overlayDisposer?.(); Object.values(this._disposers).forEach(disposer => disposer?.()); this.finalizeEdit(false, true, false); @@ -129,7 +134,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro generateSpan = (text: string, cell: HTMLDivElement | undefined) => { const selfRef = text === this.selfRefPattern; return `<span style="text-decoration: ${selfRef ? 'underline' : 'none'}; text-decoration-color: red; color: ${selfRef ? 'gray' : cell?.style.borderTop.replace('2px solid', '')}">${text}</span>`; - } + }; makeSpans = (content: string) => { let chunkedText = content; @@ -144,28 +149,28 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro const cell = this._props.getCells(match[0]); if (cell.length) { matches.push(match[0]); - cells.set(match[0], cell[0]) + cells.set(match[0], cell[0]); } } matches.forEach((match: string) => { 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<SchemaCellFieldPro const newText = atPos ? content.slice(0, robustPos) + text + content.slice(cursorPos ?? content.length) : this._unrenderedContent.concat(text); this.onChange(undefined, newText); setTimeout(() => this.setCursorPosition(robustPos + text.length)); - } + }; @action setIsFocused = (value: boolean) => { @@ -195,32 +200,31 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro */ get cursorPosition() { const selection = window.getSelection(); - if (!selection || selection.rangeCount === 0 || !this._inputref) return null; - + if (!selection || selection.rangeCount === 0 || !this._inputref) return null; + const range = selection.getRangeAt(0); const adjRange = range.cloneRange(); - + adjRange.selectNodeContents(this._inputref); adjRange.setEnd(range.startContainer, range.startOffset); - return adjRange.toString().length; + return adjRange.toString().length; } - setCursorPosition = (position: number | null) => { 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<SchemaCellFieldPro return true; } currentPos = nextPos; - - } else if ((node.nodeType === Node.ELEMENT_NODE) && (setRange(node.childNodes))) return true; + } else if (node.nodeType === Node.ELEMENT_NODE && setRange(node.childNodes)) return true; } return false; - } + }; setRange(this._inputref.childNodes); }; @@ -265,7 +268,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro const properties = this._props.refSelectModeInfo; properties.enabled = enabled; properties.currEditing = enabled ? this : undefined; - } + }; @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { @@ -289,9 +292,12 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro e.stopPropagation(); this._editing = false; break; - case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': // prettier-ignore + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': // prettier-ignore e.stopPropagation(); - setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0) + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); break; case ' ': e.stopPropagation(); @@ -300,13 +306,16 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setTimeout(() => { 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<SchemaCellFieldPro }; @action - finalizeEdit(shiftDown: boolean, lostFocus: boolean, enterKey: boolean) { + finalizeEdit = (shiftDown: boolean, lostFocus: boolean, enterKey: boolean) => { + 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 <span className='editableView-static'> - { - // eslint-disable-next-line react/jsx-props-no-spreading - this._props.fieldContents ? <FieldView {...this._props.fieldContents}/> : '' - } - </span> - } + return <span className="editableView-static">{this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : ''}</span>; + }; renderEditor = () => { return ( - <div - contentEditable - className='schemaField-editing' - ref={r => { 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 }} - > - </div> + <div + contentEditable + className="schemaField-editing" + ref={r => { + 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 }}></div> ); - } + }; render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); - if ((this._editing && gval !== undefined)) { + if (this._editing && gval !== undefined) { return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>; - } else return ( - this._props.contents instanceof ObjectField ? null : ( + } else + return this._props.contents instanceof ObjectField ? null : ( <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} style={{ @@ -393,8 +399,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro onClick={this.onClick}> {this.staticDisplay()} </div> - ) - ); + ); } - -}
\ No newline at end of file +} |
