diff options
Diffstat (limited to 'src/client/views/nodes/ComparisonBox.tsx')
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 72 |
1 files changed, 57 insertions, 15 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 116dc48a6..9ffdc350d 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,15 +4,18 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { RichTextField } from '../../../fields/RichTextField'; +import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; -import { DragManager } from '../../util/DragManager'; +import { DragManager, dropActionType } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; +import { KeyValueBox } from './KeyValueBox'; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PinProps, PresBox } from './trails'; @observer @@ -66,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() action((e, doubleTap) => { if (doubleTap) { this._isAnyChildContentActive = true; - if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); - if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); } }), false, @@ -128,14 +131,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return false; }; - whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive)); - closeDown = (e: React.PointerEvent, which: string) => { setupMoveUpEvents( this, e, e => { - const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], 'move'); + const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move); de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { this.clearDoc(which); return addDocument(doc); @@ -156,6 +157,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); + + /** + * Tests for whether a comparison box slot (ie, before or after) has renderable text content + * @param whichSlot field key for start or end slot + * @returns a JSX layout string if a text field is found, othwerise undefined + */ + testForTextFields = (whichSlot: string) => { + const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string'; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); + const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim(); + const layoutTemplateString = + slotHasText ? FormattedTextBox.LayoutString(whichSlot): + whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) : + altText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore + + // A bit hacky to try out the concept of using GPT to fill in flashcards + // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string) + // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.<field>). + // eg., this.text_alternate is + // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))" + // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field + // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) + if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { + var queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + if (queryText && queryText.match(/\(\(.*\)\)/)) { + KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt + } + } + return layoutTemplateString; + }; + _closeRef = React.createRef<HTMLDivElement>(); render() { const clearButton = (which: string) => { @@ -169,18 +201,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() </div> ); }; - const displayDoc = (which: string) => { - const whichDoc = DocCast(this.dataDoc[which]); + + /** + * Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case + * where if there are no Docs in the slots, but the main fieldKey contains text, then + * @param whichSlot + * @returns + */ + const displayDoc = (whichSlot: string) => { + const whichDoc = DocCast(this.dataDoc[whichSlot]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); - return targetDoc ? ( + const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot); + return targetDoc || layoutTemplateString ? ( <> <DocumentView {...this._props} - Document={targetDoc} - TemplateDataDocument={undefined} + ignoreUsePath={layoutTemplateString ? true : undefined} + renderDepth={this.props.renderDepth + 1} + LayoutTemplateString={layoutTemplateString} + Document={layoutTemplateString ? this.Document : targetDoc} containerViewPath={this.DocumentView?.().docViewPath} - moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} - removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} + moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2} + removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={returnZero} NativeHeight={returnZero} isContentActive={emptyFunction} @@ -190,7 +232,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() hideLinkButton={true} pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> - {clearButton(which)} + {layoutTemplateString ? null : clearButton(whichSlot)} </> // placeholder image if doc is missing ) : ( <div className="placeholder"> |