aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-09-30 18:26:59 -0400
committerbobzel <zzzman@gmail.com>2024-09-30 18:26:59 -0400
commit431a03690ed131e4bb925cec465d91adfb0d9421 (patch)
treebaeb2fc8d7518646f988406b6a3d67a677f42865 /src/client/views/collections
parentcf45abf8ada938caddb226c825166d4acdee3086 (diff)
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)
Diffstat (limited to 'src/client/views/collections')
-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
+}