aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionSchema
diff options
context:
space:
mode:
authorNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-10-01 04:01:07 -0400
committerNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-10-01 04:01:07 -0400
commit96883cb177d44ed9e06e800de9b35bda36e6fd1c (patch)
treeb9c13734c07e25dbeb99c0fbebee0e3b8be09c00 /src/client/views/collections/collectionSchema
parent115c243e57dd490dfcf09930913a941ca7ecfabc (diff)
parent63e8e1fd38835d193930bc6103a12dc4cf4d8934 (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.tsx190
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
+}