From 5cdabed4f867054ff1eade0365a56864c51e328f Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Tue, 5 Mar 2024 11:46:13 -0500 Subject: hover to show alt content --- .../views/nodes/formattedText/FormattedTextBox.tsx | 28 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f2c4c6c8f..0193cdbfc 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -924,6 +924,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI), icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); + if (this.Document._layout_enableAltContentUI) { + const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; + appearanceItems.push({ + description: (this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate:hover' ? 'no hover' : 'hover') + ' to show alt content', + event: () => (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined), + icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', + }); + // if (usepath === undefined || usepath === 'alternate') usepath = 'alternate: hover' + // if (usepath === 'alternate:hover') usepath = undefined; + } + !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && appearanceItems.push({ @@ -1939,7 +1950,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this.layoutDoc._layout_enableAltContentUI) { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; + //: usePath === 'alternate' ? 'alternate:hover' : undefined; + //usePath === undefined ? 'alternate' : + //undefined; + //: usePath === 'alternate' ? 'alternate:hover' : undefined; } }; @computed get overlayAlternateIcon() { @@ -1947,18 +1962,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent + flip + {/*
toggle (%/) between - primary, - + primary + and - alternate, + alternate and show alternate on hover - + */}
}>
Date: Tue, 5 Mar 2024 17:23:33 -0500 Subject: difference between flashcard and comparison node --- src/client/documents/Documents.ts | 12 ++++++- src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/nodes/ComparisonBox.tsx | 38 ++++++++++++---------- src/client/views/nodes/DocumentView.tsx | 3 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 2 -- 5 files changed, 37 insertions(+), 21 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d2f5fe4a..f334e958a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -265,6 +265,7 @@ export class DocumentOptions { _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false); _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button'); + _layout_isFlashcard?: BOOLt = new BoolInfo('whether comparison node should be displayed as a flashcard'); _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption @@ -728,7 +729,16 @@ export namespace Docs { { data: '', layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' }, + options: { + _layout_isFlashcard: false, + backgroundColor: 'gray', + dropAction: 'move', + waitForDoubleClickToClick: 'always', + layout_reflowHorizontal: true, + layout_reflowVertical: true, + layout_nativeDimEditable: true, + systemIcon: 'BsLayoutSplit', + }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 714e33d25..95e7841bf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -241,7 +241,8 @@ export class CurrentUserUtils { creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, - {key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, + {key: "Flashcard", creator: Docs.Create.ComparisonDocument, opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, + //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 116dc48a6..bdf92038f 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -206,24 +206,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; - return ( -
- {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)} -
- {displayBox(`${this.fieldKey}_1`, 0, 0)} -
+ if (this.Document._layout_isFlashcard) { + return
{displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
; + } else { + return ( +
+ {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)} +
+ {displayBox(`${this.fieldKey}_1`, 0, 0)} +
-
(this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, - }} - onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ - > -
+
(this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, + }} + onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ + > +
+
-
- ); + ); + } } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ed5d31664..7e86125e5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -569,6 +569,9 @@ export class DocumentViewInternal extends DocComponent LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); } appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); + + // if(this.layoutDoc.) + appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0193cdbfc..61d6094b9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -931,8 +931,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined), icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); - // if (usepath === undefined || usepath === 'alternate') usepath = 'alternate: hover' - // if (usepath === 'alternate:hover') usepath = undefined; } !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); -- cgit v1.2.3-70-g09d2 From 32a742020243ffadfdae2cbc0a8b2853b0f06fc5 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Sun, 10 Mar 2024 14:33:08 -0400 Subject: flashcard --- src/client/views/nodes/ComparisonBox.tsx | 12 +++++++++++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ---- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index fa0ab61bc..86a9bcf63 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -37,6 +37,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() get clipWidthKey() { return '_' + this._props.fieldKey + '_clipWidth'; } + + @computed get clipHeight() { + return NumCast(this.layoutDoc[this.clipHeightKey], 200); + } + get clipHeightKey() { + return '_' + this._props.fieldKey + '_clipHeight'; + } + componentDidMount() { this._props.setContentViewBox?.(this); } @@ -80,6 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() this._animating = 'all 200ms'; // on click, animate slider movement to the targetWidth this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth(); + setTimeout( action(() => (this._animating = '')), 200 @@ -201,6 +210,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); + return targetDoc ? ( <> () moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={returnZero} - NativeHeight={returnZero} + NativeHeight={() => this.clipHeight} isContentActive={emptyFunction} isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 61d6094b9..4250b018e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1949,10 +1949,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Wed, 8 May 2024 23:16:54 -0400 Subject: fixed text selection on web pages. cleaned up gptSummarize to work on any text. --- src/ClientUtils.ts | 2 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 9 +++++++++ src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 ++- src/client/views/pdf/AnchorMenu.tsx | 15 ++++----------- 5 files changed, 17 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index dbbba896f..d03ae1486 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -518,7 +518,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe export function getWordAtPoint(elem: any, x: number, y: number): string | undefined { if (elem.tagName === 'INPUT') return 'input'; if (elem.tagName === 'TEXTAREA') return 'textarea'; - if (elem.nodeType === elem.TEXT_NODE) { + if (elem.nodeType === elem.TEXT_NODE || elem.textContent) { const range = elem.ownerDocument.createRange(); range.selectNodeContents(elem); let currentPos = 0; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index a917a7d57..c18ac6738 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -258,6 +258,7 @@ export class MarqueeAnnotator extends ObservableReactComponent { + AnchorMenu.Instance.setSelectedText(''); const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox'); const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style; if (!this.isEmpty && marqueeStyle) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8df28a770..ada3de355 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -981,6 +981,9 @@ export class DocumentViewInternal extends DocComponent() { public static ROOT_DIV = 'documentView-effectsWrapper'; + /** + * Opens a new Tab for the doc in the specified location (or in the lightbox) + */ public static addSplit: (Doc: Doc, where: OpenWhereMod) => void; // Lightbox public static _lightboxDoc: () => Doc | undefined; @@ -1012,7 +1015,13 @@ export class DocumentView extends DocComponent() { public static DeselectAll: (except?: Doc) => void | undefined; public static DeselectView: (dv: DocumentView | undefined) => void | undefined; public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined; + /** + * returns a list of all currently selected DocumentViews + */ public static Selected: () => DocumentView[]; + /** + * returns a list of all currently selected Docs + */ public static SelectedDocs: () => Doc[]; public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void; public static SelectedSchemaDoc: () => Opt; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e354aedb7..624721d7c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -240,7 +240,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { AnchorMenu.Instance.Status = 'marquee'; - AnchorMenu.Instance.OnClick = () => { !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created @@ -292,6 +291,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.getAnchor(true), targetCreator), e.pageX, e.pageY); }); + + AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? ''); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); let ele: Opt; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index a837969aa..495ea59f0 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -36,10 +36,10 @@ export class AnchorMenu extends AntimodeMenu { @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions - @observable private selectedText: string = ''; + @observable private _selectedText: string = ''; @action public setSelectedText = (txt: string) => { - this.selectedText = txt; + this._selectedText = txt.trim(); }; public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search @@ -76,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu { * @param e pointer down event */ gptSummarize = async () => { - GPTPopup.Instance?.setSelectedText(this.selectedText); + GPTPopup.Instance?.setSelectedText(this._selectedText); GPTPopup.Instance.generateSummary(); }; @@ -140,13 +140,6 @@ export class AnchorMenu extends AntimodeMenu { this.highlightColor = ClientUtils.colorString(col); }; - /** - * Returns whether the selected text can be summarized. The goal is to have - * all selected text available to summarize but its only supported for pdf and web ATM. - * @returns Whether the GPT icon for summarization should appear - */ - canSummarize = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any)); - render() { const buttons = this.Status === 'marquee' ? ( @@ -161,7 +154,7 @@ export class AnchorMenu extends AntimodeMenu { />
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */} - {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( + {this._selectedText && ( Date: Fri, 10 May 2024 16:34:37 -0400 Subject: fixed exception when text templates have no inherited data, and when text views are modified after the editor has been destroyed. random code cleanup --- src/client/views/StyleProvider.tsx | 6 +- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 64 ++++++++++------------ .../views/nodes/formattedText/FormattedTextBox.tsx | 7 ++- 4 files changed, 36 insertions(+), 43 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 0b8201903..3842f930a 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -80,8 +80,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt { @computed get renderBullet() { TraceMobx(); - const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question'; + const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) || 'question'; const color = SettingsManager.userColor; const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined; return ( diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 14feca0b5..804d014a1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -123,31 +123,23 @@ export class DocumentViewInternal extends DocComponent this._animateScaleTime ?? 100; style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop); - @computed get showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt; } // prettier-ignore - @computed get opacity() { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore - @computed get boxShadow() { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore - @computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore - @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore - @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore - @computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore - @computed get showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0; } // prettier-ignore - @computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0; } // prettier-ignore - @computed get docContents() { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore - @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore - @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore - - @computed get onClickHandler() { - return this._props.onClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick)); - } - @computed get onDoubleClickHandler() { - return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick)); - } - @computed get onPointerDownHandler() { - return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown)); - } - @computed get onPointerUpHandler() { - return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp)); - } + @computed get opacity() { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore + @computed get boxShadow() { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore + @computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore + @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore + @computed get backgroundBoxColor(){ return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':docView'); } // prettier-ignore + @computed get showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt; } // prettier-ignore + @computed get showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0; } // prettier-ignore + @computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore + @computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0; } // prettier-ignore + @computed get docContents() { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore + @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore + @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore + + @computed get onClickHdlr() { return this._props.onClickScript?.() ?? ScriptCast(this.layoutDoc.onClick ?? this.Document.onClick); } // prettier-ignore + @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore + @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore + @computed get onPointerUpHdlr() { return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp ?? this.Document.onPointerUp); } // prettier-ignore @computed get disableClickScriptFunc() { const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; @@ -163,7 +155,7 @@ export class DocumentViewInternal extends DocComponent this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); + if (this.onDoubleClickHdlr?.script) { + UndoManager.RunInBatch(() => this.onDoubleClickHdlr.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { UndoManager.RunInBatch(() => this._props.addDocTab(this.Document, OpenWhere.lightboxAlways), 'double tap'); DocumentView.DeselectAll(); @@ -329,9 +321,9 @@ export class DocumentViewInternal extends DocComponent any); - if (!this.disableClickScriptFunc && this.onClickHandler?.script) { + if (!this.disableClickScriptFunc && this.onClickHdlr?.script) { clickFunc = undoable(() => { - this.onClickHandler?.script.run(scriptProps, console.log).result?.select && this._props.select(false); + this.onClickHdlr?.script.run(scriptProps, console.log).result?.select && this._props.select(false); }, 'click ' + this.Document.title); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -409,10 +401,10 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' }); + onClicks.push({ description: this.onClickHdlr ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (Doc.Links(this.Document).length) { @@ -688,7 +680,7 @@ export class DocumentViewInternal extends DocComponent this._rootSelected; panelHeight = () => this._props.PanelHeight() - this.headerMargin; screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); - onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; + onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr; setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore isContentActive = (): boolean | undefined => this._isContentActive; @@ -793,7 +785,7 @@ export class DocumentViewInternal extends DocComponent {!dropdownWidth ? null : (
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 624721d7c..e62a639c8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -320,7 +320,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView) { + if (this._editorView && !this._editorView.isDestroyed) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this.tryUpdateDoc(false); @@ -353,6 +353,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Tue, 14 May 2024 23:16:04 -0400 Subject: post merge fixes for sarah-ai-visualizatoin --- src/client/views/DocComponent.tsx | 2 +- src/client/views/nodes/DataVizBox/utils/D3Utils.ts | 5 ++++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++++-- src/client/views/nodes/generativeFill/GenerativeFill.tsx | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 94e84e647..e5752dcd2 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -233,7 +233,7 @@ export function ViewBoxAnnotatableComponent

() { const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them, so we copy them below (should really be some kind of inheritance since the template contents could change) if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); - else targetDataDoc[annotationKey ?? this.annotationKey] = new List([...added, ...(annoDocs === undefined ? DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]) : [])]); + else targetDataDoc[annotationKey || this.annotationKey] = new List([...added, ...(annoDocs === undefined ? DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]) : [])]); targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(); } } diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index be05c3529..ffc859c92 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -1,6 +1,9 @@ import * as d3 from 'd3'; -import { DataPoint } from '../components/LineChart'; +export interface DataPoint { + x: number; + y: number; +} // TODO: nda - implement function that can handle range for strings export const minMaxRange = (dataPts: DataPoint[][]) => { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e62a639c8..93cb67491 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -955,7 +955,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); - optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); + optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' }); this._props.renderDepth && optionItems.push({ description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', @@ -989,6 +989,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { try { + GPTPopup.Instance.setSidebarId(this.SidebarKey); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); if (!res) { this.animateRes(0, 'Something went wrong.'); @@ -1573,7 +1575,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent document.removeEventListener('pointerup', this.onSelectEnd); + onSelectEnd = () => { + GPTPopup.Instance.setSidebarId(this.SidebarKey); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; + document.removeEventListener('pointerup', this.onSelectEnd); + }; onPointerUp = (e: React.PointerEvent): void => { const state = this.EditorView?.state; if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) { diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 25289967e..6d8ba9222 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -535,14 +535,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD key={i} alt="image edits" width={75} - src={edit[0] as string} + src={edit.url} style={{ cursor: 'pointer' }} onClick={async () => { const img = new Image(); - img.src = edit[0] as string; + img.src = edit.url; ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); currImg.current = img; - parentDoc.current = edit[1] as Doc; + parentDoc.current = edit.saveRes ?? null; }} /> ))} -- cgit v1.2.3-70-g09d2 From 41e47a7b7789e8c192fb31188cf549178ec71a22 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 May 2024 11:28:10 -0400 Subject: moved isHovering to DocumentView as instance variable instead from FormattedText as a Document field. cleaned up cluster style provider. fixed freeformView to not trigger as many updates when a doc is dropped, and fixed layoutEngines to not return a background or color unless actually set by the engine. --- src/client/views/StyleProvider.tsx | 2 +- .../CollectionFreeFormClusters.ts | 16 ++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 71 +++++++++++----------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 13 ++-- src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/FieldView.tsx | 1 + .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +-- 7 files changed, 53 insertions(+), 60 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3842f930a..557f7db37 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -218,7 +218,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey+alternate], StrCast(doc?.['backgroundColor' +alternate], isCaption ? 'rgba(0,0,0,0.4)' : '')); if (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor) // prettier-ignore diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts index 26a52cd2a..6415d5225 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts @@ -2,7 +2,7 @@ import { action, observable } from 'mobx'; import { CollectionFreeFormView } from '.'; import { intersectRect } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; -import { NumCast, StrCast } from '../../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; @@ -179,7 +179,6 @@ export class CollectionFreeFormClusters { }; styleProvider = (doc: Opt, props: Opt, property: string) => { - let styleProp = this.viewStyleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (doc && this.childDocs?.includes(doc)) switch (property.split(':')[0]) { case StyleProp.BackgroundColor: @@ -189,14 +188,9 @@ export class CollectionFreeFormClusters { if (this._clusterSets.length <= cluster) { setTimeout(() => doc && this.addDocument(doc)); } else { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.forEach(s => { - styleProp = StrCast(s.backgroundColor); - }); + const palette = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + // override palette cluster color with an explicitly set cluster doc color + return this._clusterSets[cluster]?.reduce((b, s) => StrCast(s.backgroundColor, b), palette[cluster % palette.length]); } } } @@ -208,7 +202,7 @@ export class CollectionFreeFormClusters { break; default: } - return styleProp; + return this.viewStyleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 }; tryToSelect = (addToSel: boolean) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 53493a968..cc195385b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -404,38 +404,40 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout) - .slice() - .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - zsorted.forEach((doc, index) => { - doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1; - }); - const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); - const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; - - docDragData.droppedDocuments.forEach((d, i) => { - const layoutDoc = Doc.Layout(d); - const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate); - if (this.Document._currentFrame !== undefined) { - CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); - const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position - const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else - vals.x = NumCast(pvals.x) + delta.x; - vals.y = NumCast(pvals.y) + delta.y; - CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals); - } else { - d.x = NumCast(d.x) + delta.x; - d.y = NumCast(d.y) + delta.y; - } - d._layout_modificationDate = new DateField(); - const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; - layoutDoc._width = NumCast(layoutDoc._width, 300); - layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300); - !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront + runInAction(() => { + // needs to be in action to avoid having each edit trigger a freeform layout engine recompute - this triggers just one for each document at the end + const zsorted = this.childLayoutPairs + .map(pair => pair.layout) // + .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + zsorted.forEach((doc, index) => { + doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1; + }); + const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); + const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + + docDragData.droppedDocuments.forEach((d, i) => { + const layoutDoc = Doc.Layout(d); + const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate); + if (this.Document._currentFrame !== undefined) { + CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); + const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position + const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else + vals.x = NumCast(pvals.x) + delta.x; + vals.y = NumCast(pvals.y) + delta.y; + CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals); + } else { + d.x = NumCast(d.x) + delta.x; + d.y = NumCast(d.y) + delta.y; + } + d._layout_modificationDate = new DateField(); + const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; + layoutDoc._width = NumCast(layoutDoc._width, 300); + layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300); + !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront + }); + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments); }); - (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments); return true; } @@ -1128,16 +1130,15 @@ export class CollectionFreeFormView extends CollectionSubView) { + this._clusters.initLayout(); this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair))); return [] as ViewDefResult[]; } @@ -1237,7 +1239,6 @@ export class CollectionFreeFormView extends CollectionSubView this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { - if (doc === this.layoutDoc) { + const overrideProp = () => { switch (property.split(':')[0]) { - case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children + case StyleProp.Opacity: return this.Opacity; case StyleProp.BackgroundColor: return this.BackgroundColor; case StyleProp.Color: return this.Color; - default: - } // prettier-ignore - } - return this._props.styleProvider?.(doc, props, property); + default: return undefined; + }}; // prettier-ignore + + // only override values for this specific document, not any children + return (doc === this.layoutDoc && overrideProp()) ?? this._props.styleProvider?.(doc, props, property); }; public static getValues(doc: Doc, time: number, fillIn: boolean = true) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f56c8288e..66540678d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1383,6 +1383,7 @@ export class DocumentView extends DocComponent() { PanelHeight = () => this.panelHeight; NativeDimScaling = () => this.nativeScaling; hideLinkCount = () => !!this.hideLinkButton; + isHovering = () => this._isHovering; selfView = () => this; /** * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) @@ -1449,6 +1450,7 @@ export class DocumentView extends DocComponent() { DocumentView[]; setHeight?: (height: number) => void; NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal + isHovering?: () => boolean; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 93cb67491..de9ba87d3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1990,9 +1990,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (e.clientX > this.ProseRef!.getBoundingClientRect().right) { return; @@ -2038,11 +2037,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._isHovering = true; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true); - })} - onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore ref={r => { this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; -- cgit v1.2.3-70-g09d2 From 5ff0bef5d3c4825aa7210a26c98aae3b24f4a835 Mon Sep 17 00:00:00 2001 From: alyssaf16 Date: Fri, 17 May 2024 13:18:40 -0400 Subject: chatcards, quizcards, and ai flashcards --- src/client/apis/gpt/GPT.ts | 36 ++++-- src/client/util/CurrentUserUtils.ts | 2 - .../views/collections/CollectionCarouselView.tsx | 79 ++++++++++-- src/client/views/collections/CollectionView.tsx | 15 ++- src/client/views/nodes/ComparisonBox.scss | 143 +++++++++++++++++++++ src/client/views/nodes/ComparisonBox.tsx | 109 +++++++++++----- src/client/views/nodes/DocumentView.tsx | 24 +++- .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- src/client/views/pdf/AnchorMenu.tsx | 27 ++-- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 45 ------- src/client/views/pdf/PDFViewer.tsx | 4 +- 11 files changed, 367 insertions(+), 122 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 2757fc830..6600ddab2 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,10 +1,13 @@ import { ClientOptions, OpenAI } from 'openai'; +import { ChatCompletionMessageParam } from 'openai/resources'; enum GPTCallType { SUMMARY = 'summary', COMPLETION = 'completion', EDIT = 'edit', + CHATCARD = 'chatcard', FLASHCARD = 'flashcard', + QUIZ = 'quiz', } type GPTCallOpts = { @@ -15,10 +18,17 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with questions and answers: ' }, - completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' }, + completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: '' }, + chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' }, + quiz: { + model: 'gpt-4-turbo', + maxTokens: 1024, + temp: 0, + prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct', + }, }; /** @@ -28,7 +38,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { * @returns AI Output */ const gptAPICall = async (inputText: string, callType: GPTCallType) => { - if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD) inputText += '.'; + if (!inputText) return 'Please provide a response.'; + if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD || GPTCallType.QUIZ) inputText += '.'; const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration: ClientOptions = { @@ -36,13 +47,20 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { dangerouslyAllowBrowser: true, }; const openai = new OpenAI(configuration); - const response = await openai.completions.create({ + + let messages: ChatCompletionMessageParam[] = [ + { role: 'system', content: opts.prompt }, + { role: 'user', content: inputText }, + ]; + + const response = await openai.chat.completions.create({ model: opts.model, - max_tokens: opts.maxTokens, + messages: messages, temperature: opts.temp, - prompt: `${opts.prompt}${inputText}`, + max_tokens: opts.maxTokens, }); - return response.choices[0].text; + + return response.choices[0].message.content; } catch (err) { console.log(err); return 'Error connecting with API.'; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ab0315ba1..b6ba3f187 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -342,8 +342,6 @@ pie title Minerals in my tap water }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}}, - // {key: "Flashcard", creator: Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }}, - //{key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts), opts: { _width: 300, _height: 300, }}, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 7f5176123..d45b0822b 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -33,29 +33,53 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + /** + * Goes to the next flashcard in the stack and filters + * based on the the currently selected option. + */ advance = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { - // go to a new index that is starred, skip the ones that aren't + if (this.layoutDoc[`filterOp`] == 'star') { + // go to a flashcard that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd + 1) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + + // if the user has gone through all of the cards and gotten them all correct, show all cards and exit practice mode + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + // set all the cards to missed + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + + /** + * Goes to the previous flashcard in the stack and filters + * based on the the currently selected option. + */ goback = (e: React.MouseEvent) => { e.stopPropagation(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; @@ -63,38 +87,58 @@ export class CollectionCarouselView extends CollectionSubView() { var startInd = this.layoutDoc._carousel_index; // if the star filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') { + if (this.layoutDoc[`filterOp`] == 'star') { // go to a new index that is starred, skip the ones that aren't while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } this.layoutDoc._carousel_index = startInd; + // if there aren't any starred, show all cards + if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) { + this.layoutDoc[`filterOp`] = 'all'; + } } // if the practice filter is selected - if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') { + if (this.layoutDoc[`filterOp`] == 'practice') { // go to a new index that is missed, skip the ones that are correct while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) { startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; } + this.layoutDoc._carousel_index = startInd; + + // See all flashcards when finish going through practice mode and set all of the flashcards back to + if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') { + this.layoutDoc[`filterOp`] = 'all'; + + for (var i = 0; i < this.childLayoutPairs.length; i++) { + const curDoc = this.childLayoutPairs?.[NumCast(i)]; + curDoc.layout[`${this.fieldKey}_missed`] = undefined; + } + } } }; + /* + * Stars the document when the star button is pressed. + */ star = (e: React.MouseEvent) => { e.stopPropagation(); - // stars the document when the button is pressed const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; + if (!curDoc) return; if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true; else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`]; }; + /* + * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode. + */ missed = (e: React.MouseEvent, val: string) => { e.stopPropagation(); const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]; curDoc.layout[`${this.fieldKey}_missed`] = val; - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; - this.advance; + this.advance(e); }; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string): any => { @@ -154,6 +198,7 @@ export class CollectionCarouselView extends CollectionSubView() { ); } @computed get buttons() { + if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return; return ( <>

@@ -165,10 +210,10 @@ export class CollectionCarouselView extends CollectionSubView() {
-
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> +
this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
-
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}> +
this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
@@ -185,6 +230,18 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), }}> {this.content} + {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} +

+ Add flashcards! +

+ {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */}

Recently missed!

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 168176edf..2f0f2a773 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -80,6 +80,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' }); - revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' }); - revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' }); + revealItems.push({ description: 'All', event: () => (this.layoutDoc[`filterOp`] = 'all'), icon: 'layer-group' }); + revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`filterOp`] = 'star'), icon: 'star' }); + revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`filterOp`] = 'practice'), icon: 'check' }); + revealItems.push({ description: 'Quiz Cards', event: () => (this.layoutDoc[`filterOp`] = 'quiz'), icon: 'pencil' }); - //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); - !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + // only show the filter options if it is a collection of type Carousel view + if (this.Document?._type_collection === CollectionViewType.Carousel) { + !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); + } const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 39c864b2b..093b9c004 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -1,4 +1,5 @@ .comparisonBox-interactive, +.quiz-card, .comparisonBox { border-radius: inherit; width: 100%; @@ -7,6 +8,40 @@ z-index: 0; pointer-events: none; display: flex; + p { + color: rgb(0, 0, 0); + -webkit-text-stroke-color: black; + -webkit-text-stroke-width: 0.2px; + } + + .input-box { + position: relative; + padding: 10px; + width: 100%; + height: 100%; + display: flex; + } + + .submit-button { + position: relative; + padding-bottom: 10px; + padding-left: 5px; + padding-right: 5px; + width: 100%; + height: 15%; + display: flex; + + button { + flex: 1; + position: relative; + } + } + textarea { + flex: 1; + padding: 10px; + position: relative; + resize: none; + } .clip-div { position: absolute; @@ -95,4 +130,112 @@ display: flex; } } + // .input-box { + // position: relative; + // padding: 10px; + // } + // input[type='text'] { + // flex: 1; + // position: relative; + // margin-right: 10px; + // width: 100px; + // } +} + +// .quiz-card { +// position: relative; + +// input[type='text'] { +// flex: 1; +// position: relative; +// margin-right: 10px; +// width: 100px; +// } +// } +.QuizCard { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .QuizCard-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .QuizCardBox { + /* existing code */ + + .DIYNodeBox-iframe { + height: 100%; + width: 100%; + border: none; + } + } + + .search-bar { + display: flex; + justify-content: left; + align-items: left; + width: 100%; + padding: 10px; + + input[type='text'] { + flex: 1; + margin-right: 10px; + } + + button { + padding: 5px 10px; + } + } + + .content { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + .diagramBox { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + svg { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + } + } + + .loading-circle { + position: relative; + width: 50px; + height: 50px; + border-radius: 50%; + border: 3px solid #ccc; + border-top-color: #333; + animation: spin 1s infinite linear; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + } } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 19fccce8a..9fd4d696a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; +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'; @@ -34,6 +34,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() makeObservable(this); } + @observable inputValue = ''; + @observable outputValue = ''; + @observable loading = false; + @observable errorMessage = ''; + @observable outputMessage = ''; + + @action handleInputChange = (e: React.ChangeEvent) => { + this.inputValue = e.target.value; + console.log(this.inputValue); + }; + @observable _animating = ''; @computed get clipWidth() { @@ -160,7 +171,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() e => { 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); }; de.canEmbed = true; @@ -181,15 +191,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); _closeRef = React.createRef(); + /** + * Flips a flashcard to the alternate side for the user to view. + */ flipFlashcard = () => { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined; }; + + /** + * Changes the view option to hover for a flashcard. + */ hoverFlip = (side: string | undefined) => { if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; }; + /** + * Creates the button used to flip the flashcards. + */ @computed get overlayAlternateIcon() { const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; return ( @@ -203,21 +222,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() this.flipFlashcard(); console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); - - //const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text; - // DocCast(this.dataDoc[this.fieldKey + '_1'])[DocData].text = 'hello'; - // const mes = gptAPICall(queryText, GPTCallType.COMPLETION).trim(); - // const res = await gptAPICall(queryText, GPTCallType.COMPLETION) - // console.log(res); - //.then(value => (DocCast(this.dataDoc[this.fieldKey + '_1']).text = value.trim())); - if (usepath !== 'alternate') { - this.askGPT(); - } } }) } style={{ - //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none', background: usepath === 'alternate' ? 'white' : 'black', color: usepath === 'alternate' ? 'black' : 'white', }}> @@ -227,15 +235,34 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); } + @action handleRenderGPTClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; + this.outputValue = ''; + if (this.inputValue) this.askGPT(); + }; + + @action handleRenderClick = () => { + // Call the GPT model and get the output + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined; + }; + + /** + * Calls the GPT model to create QuizCards. Evaluates how similar the user's response is to the alternate + * side of the flashcard. + */ askGPT = async (): Promise => { - const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text; + const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text); + const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText; + try { - let res = await gptAPICall(StrCast(queryText), GPTCallType.COMPLETION); + let res = await gptAPICall(queryText, GPTCallType.QUIZ); if (!res) { console.error('GPT call failed'); return; } - DocCast(this.dataDoc[this.fieldKey + '_0'])[DocData].text = res; + this.outputValue = res; console.log(res); } catch (err) { console.error('GPT call failed'); @@ -292,8 +319,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() ); }; const displayBox = (which: string, index: number, cover: number) => { - // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100; - // else this.layoutDoc.height = 300; return (
this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} @@ -326,36 +351,54 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() if (this.Document._layout_isFlashcard) { const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0; - // add text box when first created + // add text box to each side when comparison box is first created if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[1]); + // if there is text from the pdf ai cards, put the question on the front side. newDoc[DocData].text = dataSplit[1]; this.addDoc(newDoc, this.fieldKey + '_0'); } if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) { const dataSplit = StrCast(this.dataDoc.data).split('Answer'); const newDoc = Docs.Create.TextDocument(dataSplit[0]); - newDoc[DocData].text = 'placeholder...'; + // if there is text from the pdf ai cards, put the answer on the alternate side. + newDoc[DocData].text = dataSplit[0]; this.addDoc(newDoc, this.fieldKey + '_1'); } - if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') { - { - return ( -
- {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)} - {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)} + // render the QuizCards + if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') { + return ( +
+

{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}

+ {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */} +
+ { +