diff options
Diffstat (limited to 'src')
3 files changed, 206 insertions, 6 deletions
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index f94f4be86..f5271f749 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -58,6 +58,7 @@ export interface EditableProps { wrap?: string; // nowrap, pre-wrap, etc schemaFieldType?: SchemaFieldType; + prohibitedText?: Array<string>; onClick?: () => void; updateAlt?: (newAlt: string) => void; updateSearch?: (value: string) => void; @@ -203,8 +204,13 @@ export class EditableView extends ObservableReactComponent<EditableProps> { } }; + // checkForInvalidText = (text: string) => { + // const regX = new RegExp(new Array<string>(...this._props.prohibitedText), 'g') + // } + @action finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) { + //if (invalid) raise error if (this._props.SetValue(value, shiftDown, enterKey)) { this._editing = false; this._props.isEditingCallback?.(false); @@ -252,7 +258,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { onChange: this._props.autosuggestProps.onChange, }} /> - ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? ( + ) : ( this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? ( <input className="editableView-input" ref={r => { this._inputref = r; }} // prettier-ignore @@ -284,7 +290,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { onClick={this.stopPropagation} onPointerUp={this.stopPropagation} /> - ); + )); } staticDisplay = () => { @@ -319,8 +325,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { if ((this._editing && gval !== undefined)) { return this._props.sizeToContent ? ( <div style={{ display: 'grid', minWidth: 100 }}> - <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div> - {this.renderEditor()} + <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{this.renderEditor()}</div> </div> ) : ( <div > diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx new file mode 100644 index 000000000..5f758683d --- /dev/null +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -0,0 +1,195 @@ +import { IReactionDisposer, action, 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"; + +export interface SchemaCellFieldProps { + contents: any; + fieldContents?: FieldViewProps; + editing?: boolean; + oneLine?: boolean; + highlightCells?: (text: string) => void; + GetValue(): string | undefined; + SetValue(value: string, shiftDown?: boolean, enterKey?: boolean): boolean; +} + +@observer +export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldProps> { + + private _disposers: { [name: string]: IReactionDisposer } = {}; + private _inputref: HTMLDivElement | null = null; + _overlayDisposer?: () => void; + @observable _editing: boolean = false; + + constructor(props: SchemaCellFieldProps) { + super(props); + makeObservable(this); + } + + componentDidMount(): void { + this._disposers.editing = reaction( + () => this._editing, + editing => { + if (editing) { + setTimeout(() => { + if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) { + this._overlayDisposer?.(); + this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); + this._props.highlightCells?.(this._props.GetValue() ?? ''); + } + }); + } else { + this._overlayDisposer?.(); + this._overlayDisposer = undefined; + this._props.highlightCells?.(''); + } + }, + { fireImmediately: true } + ); + } + + componentDidUpdate(prevProps: Readonly<SchemaCellFieldProps>) { + super.componentDidUpdate(prevProps); + if (this._editing && this._props.editing === false) { + this._inputref?.innerText && this.finalizeEdit(this._inputref.innerText, false, true, false); + } else + runInAction(() => { + if (this._props.editing !== undefined) this._editing = this._props.editing; + }); + } + + componentWillUnmount(): void { + this._overlayDisposer?.(); + this._disposers.editing?.(); + this._inputref?.innerText && this.finalizeEdit(this._inputref.innerText, false, true, false); + } + + @action + setIsFocused = (value: boolean) => { + const wasFocused = this._editing; + this._editing = value; + return wasFocused !== this._editing; + }; + + onChange = (e: FormEvent<HTMLInputElement>) => { + const targVal = e.currentTarget.innerText; + if (!(targVal.startsWith(':=') || targVal.startsWith('='))) { + this._overlayDisposer?.(); + this._overlayDisposer = undefined; + } else if (!this._overlayDisposer) { + this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); + } + this._props.highlightCells?.(targVal); + }; + + @action + onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here + switch (e.key) { + case 'Tab': + e.stopPropagation(); + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false); + break; + case 'Backspace': + e.stopPropagation(); + break; + case 'Enter': + e.stopPropagation(); + if (!e.ctrlKey) { + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true); + } + break; + case 'Escape': + e.stopPropagation(); + this._editing = false; + break; + case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': //prettier-ignore + e.stopPropagation(); + break; + case 'Shift': case 'Alt': case 'Meta': case 'Control': case ':': //prettier-ignore + break; + // eslint-disable-next-line no-fallthrough + default: + break; + } + }; + + + @action + onClick = (e?: React.MouseEvent) => { + if (this._props.editing !== false) { + e?.nativeEvent.stopPropagation(); + this._editing = true; + } + }; + + @action + finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) { + //if (invalid) raise error + if (this._props.SetValue(value, shiftDown, enterKey)) { + this._editing = false; + } else { + this._editing = false; + !lostFocus && + setTimeout( + action(() => { + this._editing = true; + }), + 0 + ); + } + } + + staticDisplay = () => { + return <span className='editableView-static'> + { + // eslint-disable-next-line react/jsx-props-no-spreading + this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : '' + } + </span> + } + + renderEditor = () => { + return ( + <div + contentEditable + className="editableView-input" + ref={r => { this._inputref = r; }} + style={{ overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, }} + onBlur={e => this.finalizeEdit(this._inputref ? this._inputref.innerText : '', false, true, false)} + autoFocus + onInput={this.onChange} + onKeyDown={this.onKeyDown} + onPointerDown={e => e.stopPropagation} + onClick={e => e.stopPropagation} + onPointerUp={e => e.stopPropagation} + > + </div> + ); + } + + render() { + const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); + if ((this._editing && gval !== undefined)) { + return <div>{this.renderEditor()}</div>; + } else return ( + this._props.contents instanceof ObjectField ? null : ( + <div + className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} + style={{ + minHeight: '10px', + whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line', + width: '100%', + }} + onClick={this.onClick}> + {this.staticDisplay()} + </div> + ) + ); + } + +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 69880b280..79f9067e2 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -34,6 +34,7 @@ import { CollectionSchemaView, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { ContextMenu } from '../../ContextMenu'; +import { SchemaCellField } from './SchemaCellField'; export interface SchemaTableCellProps { Document: Doc; @@ -176,11 +177,10 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro width: '100%', pointerEvents: this.lockedInteraction ? 'none' : pointerEvents, }}> - <EditableView + <SchemaCellField highlightCells={this.adjustedHighlight} ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} - allowCRs={this._props.allowCRs} contents={undefined} fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} |