aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ComparisonBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/ComparisonBox.tsx')
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx163
1 files changed, 105 insertions, 58 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 9fd4d696a..84d14d4ef 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,30 +1,31 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
-import { Doc, Opt, DocListCast } from '../../../fields/Doc';
-import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
-import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager, dropActionType } from '../../util/DragManager';
-import { undoBatch } from '../../util/UndoManager';
-import { SnappingManager } from '../../util/SnappingManager';
-import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
-import { StyleProp } from '../StyleProvider';
-import { Tooltip } from '@mui/material';
-import { CSSTransition } from 'react-transition-group';
+import { returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { RichTextField } from '../../../fields/RichTextField';
+import { DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
+import { DocUtils } from '../../documents/DocUtils';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
+import { DragManager } from '../../util/DragManager';
+import { dropActionType } from '../../util/DropActionTypes';
+import { undoable } from '../../util/UndoManager';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { PinDocView, PinProps } from '../PinFuncs';
+import { StyleProp } from '../StyleProp';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
-import { PinProps, PresBox } from './trails';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import { RichTextField } from '../../../fields/RichTextField';
-import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
-import { DocData } from '../../../fields/DocSymbols';
-import { KeyValueBox } from './KeyValueBox';
@observer
-export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
@@ -71,17 +72,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
};
- @undoBatch
- private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
+ private internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
if (dropEvent.complete.docDragData) {
- const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
- const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.Document, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey));
- Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc);
+ const { droppedDocuments } = dropEvent.complete.docDragData;
+ const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey));
+ Doc.SetContainer(droppedDocuments.lastElement(), this.dataDoc);
!added && e.preventDefault();
e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
return added;
}
- };
+ return undefined;
+ }, 'internal drop');
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
if (e.button !== 2) {
@@ -90,11 +91,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
e,
this.onPointerMove,
emptyFunction,
- action((e, doubleTap) => {
+ action((moveEv, 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,
@@ -107,7 +108,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
setTimeout(
- action(() => (this._animating = '')),
+ action(() => {
+ this._animating = '';
+ }),
200
);
})
@@ -133,18 +136,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
});
if (anchor) {
if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
- /* addAsAnnotation &&*/ this.addDocument(anchor);
- PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
+ /* addAsAnnotation && */ this.addDocument(anchor);
+ PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), clippable: true } }, this.Document);
return anchor;
}
return this.Document;
};
- @undoBatch
- clearDoc = (fieldKey: string) => {
+ clearDoc = undoable((fieldKey: string) => {
delete this.dataDoc[fieldKey];
this.dataDoc[fieldKey] = 'empty';
- };
+ }, 'clear doc');
// clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey];
moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc);
@@ -162,33 +164,63 @@ 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 => {
+ moveEv => {
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 => {
return addDocument(doc);
};
de.canEmbed = true;
- DragManager.StartDocumentDrag([this._closeRef.current!], de, e.clientX, e.clientY);
+ DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY);
return true;
},
emptyFunction,
- e => this.clearDoc(which)
+ () => this.clearDoc(which)
);
};
docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
return this._props.styleProvider?.(doc, props, property);
};
- moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
- 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);
+ moveDoc1 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true);
+ moveDoc2 = (docs: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => toList(docs).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true);
+ remDoc1 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
+ remDoc2 = (docs: Doc | Doc[]) => toList(docs).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 slotData = Doc.Get(this.dataDoc, whichSlot, true);
+ const slotHasText = slotData instanceof RichTextField || typeof slotData === '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)) {
+ const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ...
+ if (queryText?.match(/\(\(.*\)\)/)) {
+ Doc.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>();
/**
@@ -283,22 +315,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</Tooltip>
);
};
- const displayDoc = (which: string) => {
- const whichDoc = DocCast(this.dataDoc[which]);
+ const displayDoc = (whichSlot: string) => {
+ const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
// if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot
- const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+ const layoutString = !targetDoc && whichSlot.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
- return targetDoc || layoutTemplateString ? (
+ return targetDoc || layoutString ? (
<>
<DocumentView
+ // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
+ ignoreUsePath={layoutString ? true : undefined}
renderDepth={this.props.renderDepth + 1}
- LayoutTemplateString={layoutTemplateString}
- Document={layoutTemplateString ? this.Document : targetDoc}
+ LayoutTemplateString={layoutString}
+ Document={layoutString ? 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={() => NumCast(this.layoutDoc.width, 200)}
NativeHeight={(): number => {
return NumCast(this.layoutDoc.height, 200);
@@ -307,10 +341,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider}
- hideLinkButton={true}
+ hideLinkButton
pointerEvents={this._isAnyChildContentActive ? undefined : returnNone}
/>
- {layoutTemplateString ? null : clearButton(which)}
+ {layoutString ? null : clearButton(whichSlot)}
</> // placeholder image if doc is missing
) : (
<div className="placeholder">
@@ -318,13 +352,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
);
};
- const displayBox = (which: string, index: number, cover: number) => {
- return (
- <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
- {displayDoc(which)}
- </div>
- );
- };
+ const displayBox = (which: string, index: number, cover: number) => (
+ <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
+ {displayDoc(which)}
+ </div>
+ );
const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => {
return (
@@ -434,3 +466,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
}
}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.COMPARISON, {
+ data: '',
+ layout: { view: ComparisonBox, dataField: 'data' },
+ options: {
+ acl: '',
+ backgroundColor: 'gray',
+ dropAction: dropActionType.move,
+ waitForDoubleClickToClick: 'always',
+ _layout_reflowHorizontal: true,
+ _layout_reflowVertical: true,
+ _layout_nativeDimEditable: true,
+ systemIcon: 'BsLayoutSplit',
+ },
+});