diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/DashFieldView.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/DashFieldView.tsx | 163 |
1 files changed, 35 insertions, 128 deletions
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index bf6fa2ec6..6c61f6709 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -2,18 +2,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; -import { NodeSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; -import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { DataSym, Doc, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { ComputedField } from '../../../../fields/ScriptField'; import { Cast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; @@ -70,10 +69,10 @@ export class DashFieldView { } catch {} }); } - deselectNode() { + @action deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } - selectNode() { + @action selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } } @@ -98,6 +97,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna _fieldKey: string; _fieldStringRef = React.createRef<HTMLSpanElement>(); @observable _dashDoc: Doc | undefined; + @observable _expanded = false; constructor(props: IDashFieldViewInternal) { super(props); @@ -114,139 +114,46 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna this._reactionDisposer?.(); } - public static multiValueDelimeter = ';'; - public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) { - const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? ''; - const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(':=') || dashVal === '' ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal; - return { boolVal: Cast(fval, 'boolean', null), strVal: Field.toString(fval as Field) || '' }; - } - // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { - if (this._dashDoc) { - const { boolVal, strVal } = DashFieldViewInternal.fieldContent(this._textBoxDoc, this._dashDoc, this._fieldKey); - // field value is a boolean, so use a checkbox or similar widget to display it - if (boolVal === true || boolVal === false) { - return ( - <input - className="dashFieldView-fieldCheck" - type="checkbox" - checked={boolVal} - onChange={e => { - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); - }} - /> - ); - } // field value is a string, so display it as an editable span - else { - // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't - // use React events. Essentially, React events occur after native events have been processed, so corresponding React events - // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return ( - <span - className="dashFieldView-fieldSpan" - contentEditable={!this.props.unclickable()} - style={{ display: strVal.length < 2 ? 'inline-block' : undefined }} - suppressContentEditableWarning={true} - defaultValue={strVal} - ref={r => { - r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false)); - r?.addEventListener( - 'pointerdown', - action(e => { - // let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> - // while (target && !target.dataset?.targethrefs) target = target.parentElement; - this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new NodeSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos())))); - // FormattedTextBoxComment.update(this.props.tbox, this.props.tbox.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); - // e.stopPropagation(); - }) - ); - }}> - {strVal} - </span> - ); - } - } + return !this._dashDoc ? null : ( + <div onClick={action(e => (this._expanded = !this.props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this.props.hideKey ? this.props.tbox.props.PanelWidth() - 20 : undefined }}> + <SchemaTableCell + Document={this._dashDoc} + col={0} + deselectCell={emptyFunction} + selectCell={emptyFunction} + maxWidth={this.props.hideKey ? undefined : () => 100} + columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero} + selectedCell={() => [this._dashDoc!, 0]} + fieldKey={this._fieldKey} + rowHeight={returnZero} + isRowActive={() => this._expanded && this.props.editable} + padding={0} + getFinfo={emptyFunction} + setColumnValues={returnFalse} + allowCRs={true} + oneLine={!this._expanded} + finishEdit={action(() => (this._expanded = false))} + /> + </div> + ); } - // we need to handle all key events on the input span or else they will propagate to prosemirror. - @action - fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => { - if (e.key === 'c' && (e.ctrlKey || e.metaKey)) { - navigator.clipboard.writeText(window.getSelection()?.toString() || ''); - return; - } - if (e.key === 'Enter') { - // handle the enter key by "submitting" the current text to Dash's database. - this.updateText(span.textContent!, true); - e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view - } - if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { - // handle ctrl-A to select all the text within the span - if (window.getSelection) { - const range = document.createRange(); - range.selectNodeContents(span); - window.getSelection()!.removeAllRanges(); - window.getSelection()!.addRange(range); - } - e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected - } - if (!this.props.editable) { - e.preventDefault(); - } - e.stopPropagation(); // we need to handle all events or else they will propagate to prosemirror. - }; - - @action - updateText = (nodeText: string, forceMatch: boolean) => { - if (nodeText) { - const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText; - // look for a document whose id === the fieldKey being displayed. If there's a match, then that document - // holds the different enumerated values for the field in the titles of its collected documents. - // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. - DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ''; - options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); - if (modText) { - // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); - } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(':=')) { - this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith('=:=')) { - Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); - } else { - if (Number(newText).toString() === newText) { - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); - Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true); - } else { - const splits = newText.split(DashFieldViewInternal.multiValueDelimeter); - if (!this._textBoxDoc[this._fieldKey]) { - const strVal = splits.length > 1 ? new List<string>(splits) : newText; - if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); - } - } - } - }); - } - }; - createPivotForField = (e: React.MouseEvent) => { let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement(); if (container) { - const alias = Doc.MakeAlias(container.props.Document); - alias._viewType = CollectionViewType.Time; - let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField)); + const embedding = Doc.MakeEmbedding(container.rootDoc); + embedding._viewType = CollectionViewType.Time; + const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders'; + let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField)); if (!list) { - alias._columnHeaders = list = new List<SchemaHeaderField>(); + embedding[colHdrKey] = list = new List<SchemaHeaderField>(); } list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); - alias._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; - this.props.tbox.props.addDocTab(alias, OpenWhere.addRight); + embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey; + this.props.tbox.props.addDocTab(embedding, OpenWhere.addRight); } }; |