aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/ComparisonBox.scss143
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx281
-rw-r--r--src/client/views/nodes/DocumentView.tsx33
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx38
4 files changed, 441 insertions, 54 deletions
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 e1d16549c..84d14d4ef 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,18 +1,21 @@
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 { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+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 { undoBatch } from '../../util/UndoManager';
+import { undoable } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
@@ -32,6 +35,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
makeObservable(this);
}
+ @observable inputValue = '';
+ @observable outputValue = '';
+ @observable loading = false;
+ @observable errorMessage = '';
+ @observable outputMessage = '';
+
+ @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this.inputValue = e.target.value;
+ console.log(this.inputValue);
+ };
+
@observable _animating = '';
@computed get clipWidth() {
@@ -40,6 +54,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
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);
}
@@ -50,8 +72,7 @@ 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 { droppedDocuments } = dropEvent.complete.docDragData;
const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey));
@@ -61,7 +82,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return added;
}
return undefined;
- };
+ }, 'internal drop');
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
if (e.button !== 2) {
@@ -83,7 +104,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (this._isAnyChildContentActive) return;
this._animating = 'all 200ms';
// on click, animate slider movement to the targetWidth
- this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+ // this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+ this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
+
setTimeout(
action(() => {
this._animating = '';
@@ -120,17 +143,21 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return this.Document;
};
- @undoBatch
- clearDoc = (fieldKey: string) => delete this.dataDoc[fieldKey];
+ 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);
addDoc = (doc: Doc, which: string) => {
- if (this.dataDoc[which]) return false;
+ if (this.dataDoc[which] && this.dataDoc[which] !== 'empty') return false;
this.dataDoc[which] = doc;
return true;
};
remDoc = (doc: Doc, which: string) => {
if (this.dataDoc[which] === doc) {
+ // this.dataDoc[which] = 'empty';
this.dataDoc[which] = undefined;
return true;
}
@@ -144,7 +171,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
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 => {
- this.clearDoc(which);
return addDocument(doc);
};
de.canEmbed = true;
@@ -196,27 +222,105 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
};
_closeRef = React.createRef<HTMLDivElement>();
- render() {
- const clearButton = (which: string) => (
- <div
- ref={this._closeRef}
- className={`clear-button ${which}`}
- onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
- >
- <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
- </div>
+
+ /**
+ * 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 (
+ <Tooltip title={<div className="dash-tooltip">flip</div>}>
+ <div
+ className="formattedTextBox-alternateButton"
+ onPointerDown={e =>
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => {
+ console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
+ if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
+ 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);
+ }
+ })
+ }
+ style={{
+ background: usepath === 'alternate' ? 'white' : 'black',
+ color: usepath === 'alternate' ? 'black' : 'white',
+ }}>
+ <FontAwesomeIcon icon="turn-up" size="sm" />
+ </div>
+ </Tooltip>
);
+ }
+
+ @action handleRenderGPTClick = () => {
+ // Call the GPT model and get the output
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate';
+ this.outputValue = '';
+ if (this.inputValue) this.askGPT();
+ };
- /**
- * 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
- */
+ @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<string | undefined> => {
+ 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(queryText, GPTCallType.QUIZ);
+ if (!res) {
+ console.error('GPT call failed');
+ return;
+ }
+ this.outputValue = res;
+ console.log(res);
+ } catch (err) {
+ console.error('GPT call failed');
+ }
+ };
+
+ render() {
+ const clearButton = (which: string) => {
+ return (
+ <Tooltip title={<div className="dash-tooltip">remove</div>}>
+ <div
+ ref={this._closeRef}
+ className={`clear-button ${which}`}
+ onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+ >
+ <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
+ </div>
+ </Tooltip>
+ );
+ };
const displayDoc = (whichSlot: string) => {
const whichDoc = DocCast(this.dataDoc[whichSlot]);
const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
- const layoutString = targetDoc ? '' : this.testForTextFields(whichSlot);
+ // 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 layoutString = !targetDoc && whichSlot.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined;
+
return targetDoc || layoutString ? (
<>
<DocumentView
@@ -229,8 +333,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
containerViewPath={this.DocumentView?.().docViewPath}
moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
+ NativeWidth={() => NumCast(this.layoutDoc.width, 200)}
+ NativeHeight={(): number => {
+ return NumCast(this.layoutDoc.height, 200);
+ }}
isContentActive={emptyFunction}
isDocumentActive={returnFalse}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -252,25 +358,112 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
);
- return (
- <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
- {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
- <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
- {displayBox(`${this.fieldKey}_1`, 0, 0)}
+ const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => {
+ return (
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
+ <div
+ className={`beforeBox-cont`}
+ key={which}
+ style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }}
+ onPointerDown={e => this.registerSliding(e, cover)}
+ ref={ele => this.createDropTarget(ele, which, 0)}>
+ {displayDoc(which)}
+ </div>
+ <div
+ className={`afterBox-cont`}
+ key={which2}
+ style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }}
+ onPointerDown={e => this.registerSliding(e, cover)}
+ ref={ele => this.createDropTarget(ele, which2, 1)}>
+ {displayDoc(which2)}
+ </div>
</div>
+ );
+ };
- <div
- className="slide-bar"
- style={{
- left: `calc(${this.clipWidth + '%'} - 0.5px)`,
- cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (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 */
- >
- <div className="slide-handle" />
+ if (this.Document._layout_isFlashcard) {
+ const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0;
+
+ // 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]);
+ // 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');
+ }
+
+ // render the QuizCards
+ if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') {
+ return (
+ <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}>
+ <p style={{ color: 'white', padding: 10 }}>{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}</p>
+ {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */}
+ <div className={'input-box'}>
+ {
+ <textarea
+ value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue}
+ onChange={this.handleInputChange}
+ readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'}
+ />
+ }
+ </div>
+ <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}>
+ <button onClick={this.handleRenderGPTClick}>Submit</button>
+ </div>
+ <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'flex' : 'none' }}>
+ <button onClick={this.handleRenderClick}>Edit Your Response</button>
+ </div>
+ </div>
+ );
+ }
+
+ // render a normal flashcard when not a QuizCard
+ else {
+ return (
+ <div
+ className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
+ style={{ display: 'flex', flexDirection: 'column' }}
+ onMouseEnter={() => {
+ this.hoverFlip('alternate');
+ }}
+ onMouseLeave={() => {
+ this.hoverFlip(undefined);
+ }}>
+ {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
+ {this.overlayAlternateIcon}
+ </div>
+ );
+ }
+ } else {
+ // render a comparison box that compares items side by side
+ return (
+ <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+ {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
+ <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+ {displayBox(`${this.fieldKey}_1`, 0, 0)}
+ </div>
+
+ <div
+ className="slide-bar"
+ style={{
+ left: `calc(${this.clipWidth + '%'} - 0.5px)`,
+ cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (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 */
+ >
+ <div className="slide-handle" />
+ </div>
</div>
- </div>
- );
+ );
+ }
}
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c59cd0ee4..fca6cda81 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -17,11 +17,12 @@ import { List } from '../../../fields/List';
import { PrefetchProxy } from '../../../fields/Proxy';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { AudioAnnoState } from '../../../server/SharedMediaTypes';
import { DocServer } from '../../DocServer';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
@@ -500,6 +501,21 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
input.click();
};
+ askGPT = async (): Promise<string | undefined> => {
+ const queryText = RTFCast(DocCast(this.dataDoc[this.props.fieldKey + '_1']).text)?.Text;
+ try {
+ let res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD);
+ if (!res) {
+ console.error('GPT call failed');
+ return;
+ }
+ DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res;
+ console.log(res);
+ } catch (err) {
+ console.error('GPT call failed');
+ }
+ };
+
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
@@ -558,9 +574,22 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
}
appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+ if (this.Document._layout_isFlashcard) {
+ appearanceItems.push({ description: 'Create ChatCard', event: () => this.askGPT(), icon: 'id-card' });
+ }
+
!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' });
+ // creates menu for the user to select how to reveal the flashcards
+ if (this.Document._layout_isFlashcard) {
+ const revealOptions = cm.findByDescription('Reveal Options');
+ const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
+ revealItems.push({ description: 'Hover', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'), icon: 'hand-point-up' });
+ revealItems.push({ description: 'Flip', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'), icon: 'rotate' });
+ !revealOptions && cm.addItem({ description: 'Reveal Options', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
+ }
+
if (this._props.bringToFront) {
const zorders = cm.findByDescription('ZOrder...');
const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
@@ -614,7 +643,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
}
- !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
+ !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
}
const constantItems: ContextMenuProps[] = [];
if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index de9ba87d3..542a68c3b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -181,6 +181,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@observable
private gptRes: string = '';
+ public makeAIFlashcards: () => void = unimplementedFunction;
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
+
public static PasteOnLoad: ClipboardEvent | undefined;
public static DontSelectInitialText = false; // whether initial text should be selected or not
public static SelectOnLoadChar = '';
@@ -918,6 +921,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
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',
+ });
+ }
+
!Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
!Doc.noviceMode &&
appearanceItems.push({
@@ -955,6 +967,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
icon: 'star',
});
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
+ optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' });
optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' });
this._props.renderDepth &&
optionItems.push({
@@ -1948,28 +1961,37 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
</div>
);
}
- cycleAlternateText = (skipHover?: boolean) => {
- this.layoutDoc._layout_enableAltContentUI = true;
- const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
- this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
+ cycleAlternateText = () => {
+ if (this.layoutDoc._layout_enableAltContentUI) {
+ const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+ this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
+ }
};
+ // cycleAlternateText = (skipHover?: boolean) => {
+ // this.layoutDoc._layout_enableAltContentUI = true;
+ // const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+ // this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
+ // };
@computed get overlayAlternateIcon() {
const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
return (
<Tooltip
title={
<div className="dash-tooltip">
+ flip
+ {/*
+ <div className="dash-tooltip">
toggle (%/) between
<span style={{ color: usePath === undefined ? 'black' : undefined }}>
- <em> primary, </em>
- </span>
+ <em> primary </em>
+ </span> and
<span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
- <em>alternate, </em>
+ <em>alternate </em>
</span>
and show
<span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
<em> alternate on hover</em>
- </span>
+ </span> */}
</div>
}>
<div