diff options
author | bobzel <zzzman@gmail.com> | 2024-03-24 19:04:42 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-03-24 19:04:42 -0400 |
commit | 2e0cb3e0a470994eecbb7f6b2ec87296baf517b9 (patch) | |
tree | d08a06b933fffad344e3cb40d48779e1a9a30fe1 /src | |
parent | b949608ff69fb66c30bbed439b1c37f8fffd2333 (diff) |
fixed linkdocpreviews to sequence through multiple links. fixed text boxes to update text when dashfieldView text changes (but the fieldview doesn't), fixed dashFieldViews to be editable cleanly, and to allow sub-dashFieldViews to be editbale. allowed toggle on/off of dashFieldView fieldKey. got rid of sidebars in scemaCells. fied editing dashFieldViews in captions and as childrend of dashFieldViews by passing rootSelected
Diffstat (limited to 'src')
6 files changed, 80 insertions, 34 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 711ef507c..ce73ff8a4 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -51,6 +51,8 @@ export interface SchemaTableCellProps { options?: string[]; menuTarget: HTMLDivElement | null; transform: () => Transform; + autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click + rootSelected?: () => boolean; } @observer @@ -89,6 +91,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro select: emptyFunction, dragAction: dropActionType.move, renderDepth: 1, + noSidebar: true, isContentActive: returnFalse, whenChildContentsActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, @@ -99,6 +102,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro fieldKey: fieldKey, PanelWidth: columnWidth, PanelHeight: props.rowHeight, + rootSelected: props.rootSelected, }; const readOnly = getFinfo(fieldKey)?.readOnly ?? false; const cursor = !readOnly ? 'text' : 'default'; @@ -124,6 +128,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro pointerEvents, }}> <EditableView + ref={r => this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} @@ -314,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps const selected: [Doc, number] | undefined = this._props.selectedCell(); return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } + + // if the text box blurs and none of its contents are focused(), then the edit finishes selectedFunc = () => this.selected; render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return ( <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e9ce98583..1044d6609 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -892,6 +892,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document fieldKey={this.layout_showCaption} styleProvider={this.captionStyleProvider} dontRegisterView={true} + rootSelected={this.rootSelected} noSidebar={true} dontScale={true} renderDepth={this._props.renderDepth} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index ae25ff179..4b6ee7d72 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -172,6 +172,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps if (nextHrefInd !== this._hrefInd) { this._linkDoc = undefined; this._hrefInd = nextHrefInd; + this.updateHref(); } }), true diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 7a0ff8776..74eeb014c 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -1,20 +1,11 @@ @import '../../global/globalCssVariables.module.scss'; +.dashFieldView-active, .dashFieldView { position: relative; display: inline-flex; align-items: center; - select { - display: none; - } - - &:hover { - select { - display: unset; - } - } - .dashFieldView-enumerables { width: 10px; height: 10px; @@ -50,6 +41,27 @@ } } } + +.dashFieldView, +.dashFieldView-active { + .dashFieldView-select { + height: 10p; + font-size: 12px; + background: transparent; + opacity: 0; + width: 5px; + } +} + +.dashFieldView { + &:hover { + .dashFieldView-select { + opacity: unset; + width: 15px !important; + } + } +} + .ProseMirror-selectedNode { outline: solid 1px $light-blue !important; } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 6b66d829c..5b03e2236 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -133,20 +133,29 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi this._reactionDisposer?.(); } isRowActive = () => this._expanded && this._props.editable; - finishEdit = action(() => (this._expanded = false)); + + finishEdit = action(() => { + if (this._expanded) { + this._expanded = false; + // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus + // the timeout allows switching focus from one dashFieldView to another in the same text box + setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.()); + } + }); selectedCell = (): [Doc, number] => [this._dashDoc!, 0]; + columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { 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 }}> + <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}> <SchemaTableCell Document={this._dashDoc} col={0} deselectCell={emptyFunction} selectCell={emptyFunction} maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth} - columnWidth={returnZero} + columnWidth={this._expanded ? this.columnWidth : returnZero} selectedCell={this.selectedCell} fieldKey={this._fieldKey} rowHeight={returnZero} @@ -159,6 +168,8 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi finishEdit={this.finishEdit} transform={Transform.Identity} menuTarget={null} + autoFocus={true} + rootSelected={this._props.tbox._props.rootSelected} /> </div> ); @@ -187,12 +198,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi ); @computed get _hideKey() { - return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey']; + return this._dashDoc?.[this._fieldKey + '_hideKey'] && !this._expanded; } // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label - onPointerDownLabelSpan = (e: any) => { + onPointerDownLabelSpan = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; @@ -215,7 +226,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi render() { return ( <div - className="dashFieldView" + className={`dashFieldView${this.isRowActive() ? '-active' : ''}`} ref={this._fieldRef} style={{ width: this._props.width, @@ -229,7 +240,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi )} {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} {!this.values.length ? null : ( - <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}> + <select className="dashFieldView-select" onChange={this.selectVal}> {this.values.map(val => ( <option value={val.value}>{val.label}</option> ))} @@ -272,14 +283,18 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { }; render() { return this.getElement( - <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}> - <button className="antimodeMenu-button" onPointerDown={this.showFields}> - <FontAwesomeIcon icon="eye" size="lg" /> - </button> - <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}> - <FontAwesomeIcon icon="bullseye" size="lg" /> - </button> - </Tooltip> + <> + <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}> + <button className="antimodeMenu-button" onPointerDown={this.showFields}> + <FontAwesomeIcon icon="eye" size="lg" /> + </button> + </Tooltip> + <Tooltip key="trash" title={<div className="dash-tooltip">Toggle view of field key</div>}> + <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}> + <FontAwesomeIcon icon="bullseye" size="lg" /> + </button> + </Tooltip> + </> ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2b48494f2..793595694 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -21,7 +21,7 @@ import { List } from '../../../../fields/List'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { ComputedField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; @@ -68,8 +68,13 @@ import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; // import * as applyDevTools from 'prosemirror-dev-tools'; + +interface FormattedTextBoxProps extends FieldViewProps { + onBlur?: () => void; // callback when text loses focus + autoFocus?: boolean; // whether text should get input focus when created +} @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface { +export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextBoxProps>() implements ViewBoxInterface { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -200,7 +205,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId } - constructor(props: FieldViewProps) { + constructor(props: FormattedTextBoxProps) { super(props); makeObservable(this); FormattedTextBox.Instance = this; @@ -322,7 +327,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps leafText = (node: Node) => { if (node.type === this._editorView?.state.schema.nodes.dashField) { const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); - return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field); + const fieldKey = StrCast(node.attrs.fieldKey); + return fieldKey + ':' + Field.toJavascriptString(refDoc[fieldKey] as Field); } return ''; }; @@ -359,15 +365,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } let unchanged = true; - if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) { + const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes + if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) { this._applyingChange = this.fieldKey; - const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); if ((!prevData && !protoData) || newText || (!newText && !protoData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) { + if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) { const numstring = NumCast(dataDoc[this.fieldKey], null); - dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined; + dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText || DocCast(dataDoc.proto)?.[this.fieldKey] === undefined ? new RichTextField(newJson, newText) : undefined; + textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText }); this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited @@ -1483,6 +1490,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps FormattedTextBox.PasteOnLoad = undefined; pdfAnchorId && this.addPdfReference(pdfAnchorId); } + if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. @@ -1724,6 +1732,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.Document.title).length + 2))).deleteSelection()); }, 'titler'); } + // if the text box blurs and none of its contents are focused(), then pass the blur along + setTimeout(() => !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.()); }; onKeyDown = (e: React.KeyboardEvent) => { |