aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss18
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx137
-rw-r--r--src/client/views/collections/CollectionView.tsx14
-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
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx76
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx3
-rw-r--r--src/client/views/pdf/PDFViewer.tsx5
10 files changed, 683 insertions, 65 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index 130b31325..f115bb40a 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -13,7 +13,10 @@
}
}
.carouselView-back,
-.carouselView-fwd {
+.carouselView-fwd,
+.carouselView-star,
+.carouselView-remove,
+.carouselView-check {
position: absolute;
display: flex;
top: 42.5%;
@@ -34,6 +37,19 @@
.carouselView-back {
left: 20;
}
+.carouselView-star {
+ top: 0;
+ right: 20;
+}
+.carouselView-remove {
+ top: 80%;
+ left: 52%;
+}
+.carouselView-check {
+ top: 80%;
+ right: 52%;
+}
+
.carouselView-back:hover,
.carouselView-fwd:hover {
background: lightgray;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 45b64d3e6..b736c7ced 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -42,14 +42,114 @@ export class CollectionCarouselView extends CollectionSubView() {
return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
}
+ /**
+ * 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.carouselItems.length;
+ 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[`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[`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.carouselItems.length) % this.carouselItems.length;
+ this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+
+ var startInd = this.layoutDoc._carousel_index;
+
+ // if the star filter is selected
+ 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[`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();
+ 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.advance(e);
};
+
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
// first look for properties on the document in the carousel, then fallback to properties on the container
const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
@@ -107,6 +207,7 @@ export class CollectionCarouselView extends CollectionSubView() {
);
}
@computed get buttons() {
+ if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return;
return (
<>
<div key="back" className="carouselView-back" onClick={this.goback}>
@@ -115,6 +216,15 @@ export class CollectionCarouselView extends CollectionSubView() {
<div key="fwd" className="carouselView-fwd" onClick={this.advance}>
<FontAwesomeIcon icon="chevron-right" size="2x" />
</div>
+ <div key="star" className="carouselView-star" onClick={this.star}>
+ <FontAwesomeIcon icon={'star'} color={this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size={'1x'} />
+ </div>
+ <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
+ <FontAwesomeIcon icon={'xmark'} color={'red'} size={'1x'} />
+ </div>
+ <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
+ <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} />
+ </div>
</>
);
}
@@ -129,6 +239,29 @@ 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. */}
+ <p
+ style={{
+ display: !this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc[`filterOp`] == 'practice' ? 'flex' : 'none',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '100%',
+ zIndex: '-1',
+ }}>
+ Add flashcards!
+ </p>
+ {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */}
+ <p
+ style={{
+ color: 'red',
+ zIndex: '999',
+ position: 'relative',
+ left: '10px',
+ top: '10px',
+ display: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] ? (this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'block' : 'none') : 'none',
+ }}>
+ Recently missed!
+ </p>
{this.Document._chromeHidden ? null : this.buttons}
</div>
);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5c304b4a9..d084a2aec 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -52,6 +52,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
super(props);
makeObservable(this);
this._annotationKeySuffix = returnEmptyString;
+ this.layoutDoc[`filterOp`] = 'all';
}
componentDidMount() {
@@ -150,6 +151,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
return newRendition;
});
+ // creates menu option for flashcard filters
+ const revealOptions = cm.findByDescription('Filter Flashcards');
+ const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
+ 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' });
+
+ // 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 : [];
!Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore
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
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 495ea59f0..b7247a034 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -7,13 +7,15 @@ import { ColorResult } from 'react-color';
import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { gptAPICall } from '../../apis/gpt/GPT';
+import { GPTCallType } from '../../apis/gpt/setup';
import { SettingsManager } from '../../util/SettingsManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
-import './AnchorMenu.scss';
-import { GPTPopup } from './GPTPopup/GPTPopup';
import { DocumentView } from '../nodes/DocumentView';
+import './AnchorMenu.scss';
+import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { Docs } from '../../documents/Documents';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -59,6 +61,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public get Active() {
return this._left > 0;
}
+ public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
componentWillUnmount() {
this._disposer?.();
@@ -75,9 +78,63 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* Invokes the API with the selected text and stores it in the summarized text.
* @param e pointer down event
*/
- gptSummarize = async () => {
- GPTPopup.Instance?.setSelectedText(this._selectedText);
- GPTPopup.Instance.generateSummary();
+ gptSummarize = async (e: React.PointerEvent) => {
+ GPTPopup.Instance.setVisible(true);
+ GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
+ GPTPopup.Instance.setLoading(true);
+
+ try {
+ const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
+ } catch (err) {
+ console.error(err);
+ }
+ GPTPopup.Instance.setLoading(false);
+ };
+ // gptSummarize = async () => {
+ // GPTPopup.Instance?.setSelectedText(this._selectedText);
+ // GPTPopup.Instance.generateSummary();
+ // };
+
+ /**
+ * Invokes the API with the selected text and stores it in the selected text.
+ * @param e pointer down event
+ */
+ gptFlashcards = async (e: React.PointerEvent) => {
+ const queryText = this.selectedText;
+ try {
+ const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
+ console.log(res);
+ GPTPopup.Instance.setText(res || 'Something went wrong.');
+ this.transferToFlashcard(res || 'Something went wrong');
+ } catch (err) {
+ console.error(err);
+ }
+ GPTPopup.Instance.setLoading(false);
+ };
+
+ /*
+ * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them.
+ */
+ transferToFlashcard = (text: string) => {
+ // put each question generated by GPT on the front of the flashcard
+ const senArr = text.split('Question');
+ const collectionArr: Doc[] = [];
+ for (var i = 1; i < senArr.length; i++) {
+ console.log('Arr ' + i + ': ' + senArr[i]);
+ const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 });
+ newDoc.text = senArr[i];
+ collectionArr.push(newDoc);
+ }
+ // create a new carousel collection of these flashcards
+ const newCol = Docs.Create.CarouselDocument(collectionArr, {
+ _width: 250,
+ _height: 200,
+ _layout_fitWidth: false,
+ _layout_autoHeight: true,
+ });
+
+ this.addToCollection?.(newCol);
};
pointerDown = (e: React.PointerEvent) => {
@@ -162,6 +219,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
color={SettingsManager.userColor}
/>
)}
+ {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */}
+ <IconButton
+ tooltip="Create flashcards" //
+ onPointerDown={this.gptFlashcards}
+ icon={<FontAwesomeIcon icon="id-card" size="lg" />}
+ color={SettingsManager.userColor}
+ />
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
tooltip="Click to Record Annotation" //
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 8bb2e2844..cb5aad32d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -24,6 +24,7 @@ export enum GPTPopupMode {
SUMMARY,
EDIT,
IMAGE,
+ FLASHCARD,
DATA,
SORT,
}
@@ -412,7 +413,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
<div className="btns-wrapper">
{this.done ? (
<>
- <IconButton tooltip="Generate Again" onClick={this.generateSummary} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} />
+ <IconButton tooltip="Generate Again" onClick={this.generateSummary /* this.callSummaryApi */} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} />
<Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} />
</>
) : (
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 2327ee0d8..6c1617c38 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -24,11 +24,13 @@ import { FieldViewProps } from '../nodes/FieldView';
import { FocusViewOptions } from '../nodes/FocusViewOptions';
import { LinkInfo } from '../nodes/LinkDocPreview';
import { PDFBox } from '../nodes/PDFBox';
+import { ComparisonBox } from '../nodes/ComparisonBox';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProp';
import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
+import { Docs } from '../../documents/Documents';
import './PDFViewer.scss';
// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -430,9 +432,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
}
- // Changing which document to add the annotation to (the currently selected PDF)
GPTPopup.Instance.setSidebarId('data_sidebar');
GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
+ // allows for creating collection
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
};
@action