aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx71
-rw-r--r--src/client/views/pdf/Annotation.scss19
-rw-r--r--src/client/views/pdf/PDFViewer.scss21
-rw-r--r--src/client/views/pdf/PDFViewer.tsx169
4 files changed, 258 insertions, 22 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 03585a8b7..6dd036cf6 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -15,6 +15,7 @@ import { LinkPopup } from '../linking/LinkPopup';
import { DocumentView } from '../nodes/DocumentView';
import './AnchorMenu.scss';
import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import ReactLoading from 'react-loading';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -24,6 +25,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
private _disposer: IReactionDisposer | undefined;
private _commentRef = React.createRef<HTMLDivElement>();
private _cropRef = React.createRef<HTMLDivElement>();
+ @observable private _loading = false;
+ // @observable protected _top: number = -300;
+ // @observable protected _left: number = -300;
constructor(props: AntimodeMenuProps) {
super(props);
@@ -38,11 +42,21 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
// GPT additions
@observable private _selectedText: string = '';
+ @observable private _x: number = 0;
+ @observable private _y: number = 0;
@action
public setSelectedText = (txt: string) => {
this._selectedText = txt.trim();
};
+ @action
+ public setLocation = (x: number, y: number) => {
+ this._x = x;
+ this._y = y;
+ };
+ @computed public get selectedText() {
+ return this._selectedText;
+ }
public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
@@ -57,6 +71,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public MakeTargetToggle: () => void = unimplementedFunction;
public ShowTargetTrail: () => void = unimplementedFunction;
public IsTargetToggler: () => boolean = returnFalse;
+ public gptFlashcards: () => void = unimplementedFunction;
+ public makeLabels: () => void = unimplementedFunction;
+ public marqueeWidth = 0;
+ public marqueeHeight = 0;
public get Active() {
return this._left > 0;
}
@@ -99,30 +117,33 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
* Invokes the API with the selected text and stores it in the selected text.
* @param e pointer down event
*/
- gptFlashcards = async () => {
- 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);
- };
+ // gptPDFFlashcards = async () => {
+ // const queryText = this._selectedText;
+ // this._loading = true;
+ // 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) => {
+
+ transferToFlashcard = (text: string, x: number, y: number) => {
// put each question generated by GPT on the front of the flashcard
- const senArr = text.split('Question');
- const collectionArr: Doc[] = [];
+ var senArr = text.trim().split('Question: ');
+ var collectionArr: Doc[] = [];
for (let 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
@@ -133,7 +154,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
_layout_autoHeight: true,
});
+ console.log(collectionArr);
+ newCol.x = x;
+ newCol.y = y;
+ console.log(this._x);
+ newCol.zIndex = 1000;
+
this.addToCollection?.(newCol);
+ this._loading = false;
};
pointerDown = (e: React.PointerEvent) => {
@@ -219,12 +247,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
)}
{/* 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}
- />
+ <IconButton tooltip="Create flashcards" onPointerDown={this.gptFlashcards} icon={<FontAwesomeIcon icon="id-card" size="lg" />} color={SettingsManager.userColor} />
+ <IconButton tooltip="Create labels" onPointerDown={this.makeLabels} icon={<FontAwesomeIcon icon="tag" size="lg" />} color={SettingsManager.userColor} />
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
tooltip="Click to Record Annotation" //
@@ -250,6 +274,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
/>
</div>
)}
+ {/* {this._loading ? (
+ <div className="loading-spinner" style={{ position: 'absolute' }}>
+ <ReactLoading type="spin" height={30} width={30} color={'white'} />
+ </div>
+ ) : null} */}
</>
) : (
<>
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
index 1de60ffed..26856b74e 100644
--- a/src/client/views/pdf/Annotation.scss
+++ b/src/client/views/pdf/Annotation.scss
@@ -7,4 +7,21 @@
&:hover {
cursor: pointer;
}
-} \ No newline at end of file
+}
+// .loading-spinner {
+// display: flex;
+// justify-content: center;
+// align-items: center;
+// height: 90%;
+// width: 93%;
+// left: 10;
+// font-size: 20px;
+// font-weight: bold;
+// color: #0b0a0a;
+// }
+
+// @keyframes spin {
+// to {
+// transform: rotate(360deg);
+// }
+// }
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index d3dd9f727..e70102ce9 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -107,3 +107,24 @@
.pdfViewerDash-interactive {
pointer-events: all;
}
+
+.loading-spinner {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ // left: 50%;
+ // top: 50%;
+ z-index: 200;
+ font-size: 20px;
+ font-weight: bold;
+ color: #17175e;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index dee0edfae..02d310f7d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -28,6 +28,12 @@ import { AnchorMenu } from './AnchorMenu';
import { Annotation } from './Annotation';
import { GPTPopup } from './GPTPopup/GPTPopup';
import './PDFViewer.scss';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
+import ReactLoading from 'react-loading';
+// import html2canvas from 'html2canvas';
+// import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
+
+// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
// The workerSrc property shall be specified.
// Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.mjs';
@@ -58,12 +64,50 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
super(props);
makeObservable(this);
}
+ // @observable transcriptRef = React.createRef();
+ // @observable startBtnRef = React.createRef();
+ // @observable stopBtnRef = React.createRef();
+ // @observable transcriptElement = '';
+
+ // handleResult = (e: SpeechRecognitionEvent) => {
+ // let interimTranscript = '';
+ // let finalTranscript = '';
+ // console.log('H');
+ // for (let i = e.resultIndex; i < e.results.length; i++) {
+ // const transcript = e.results[i][0].transcript;
+ // if (e.results[i].isFinal) {
+ // finalTranscript += transcript;
+ // } else {
+ // interimTranscript += transcript;
+ // }
+ // }
+ // console.log(interimTranscript);
+ // this.transcriptElement = finalTranscript || interimTranscript;
+ // };
+
+ // startListening = () => {
+ // const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+ // if (SpeechRecognition) {
+ // console.log('here');
+ // const recognition = new SpeechRecognition();
+ // recognition.continuous = true; // Continue listening even if the user pauses
+ // recognition.interimResults = true; // Show interim results
+ // recognition.lang = 'en-US'; // Set language (optional)
+ // recognition.onresult = this.handleResult.bind(this);
+ // // recognition.onend = this.handleEnd.bind(this);
+
+ // recognition.start();
+ // // this.handleResult;
+ // // recognition.stop();
+ // }
+ // };
@observable _pageSizes: { width: number; height: number }[] = [];
@observable _savedAnnotations = new ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]>();
@observable _textSelecting = true;
@observable _showWaiting = true;
@observable Index: number = -1;
+ @observable private _loading = false;
private _pdfViewer!: PDFJSViewer.PDFViewer;
private _styleRule: number | undefined; // stylesheet rule for making hyperlinks clickable
@@ -394,6 +438,122 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
};
+ gptPDFFlashcards = async () => {
+ // const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+ // if (SpeechRecognition) {
+ // this.recognition = new SpeechRecognition();
+ // this.recognition.continuous = true; // Continue listening even if the user pauses
+ // this.recognition.interimResults = true; // Show interim results
+ // this.recognition.lang = 'en-US'; // Set language (optional)
+
+ // this.recognition.onresult = this.handleResult;
+ // this.recognition.onerror = this.handleError;
+ // this.recognition.onend = this.handleEnd;
+ // } else {
+ // console.error("Browser doesn't support Speech Recognition API");
+ // }
+ // const Dictaphone = () => {
+ // const { transcript, resetTranscript } = useSpeechRecognition();
+
+ // if (!SpeechRecognition.browserSupportsSpeechRecognition()) {
+ // return null;
+ // }
+
+ // return (
+ // <div>
+ // <button onClick={e => SpeechRecognition.startListening}>Start</button>
+ // <button onClick={e => SpeechRecognition.stopListening}>Stop</button>
+ // <button onClick={resetTranscript}>Reset</button>
+ // <p>{transcript}</p>
+ // </div>
+ // );
+ // };
+ // const grammar =
+ // '#JSGF V1.0; grammar colors; public <color> = aqua | azure | beige | bisque | black | blue | brown | chocolate | coral | crimson | cyan | fuchsia | ghostwhite | gold | goldenrod | gray | green | indigo | ivory | khaki | lavender | lime | linen | magenta | maroon | moccasin | navy | olive | orange | orchid | peru | pink | plum | purple | red | salmon | sienna | silver | snow | tan | teal | thistle | tomato | turquoise | violet | white | yellow ;';
+ // const recognition = new SpeechRecognition();
+ // const speechRecognitionList = new SpeechGrammarList();
+ // speechRecognitionList.addFromString(grammar, 1);
+ // recognition.grammars = speechRecognitionList;
+ // recognition.continuous = false;
+ // recognition.lang = 'en-US';
+ // recognition.interimResults = false;
+ // recognition.maxAlternatives = 1;
+
+ // const diagnostic = document.querySelector('.output');
+ // const bg = document.querySelector('html');
+
+ // document.body.onclick = () => {
+ // recognition.start();
+ // console.log('Ready to receive a color command.');
+ // };
+
+ // recognition.onresult = event => {
+ // const color = event.results[0][0].transcript;
+ // diagnostic!.textContent = `Result received: ${color}`;
+ // bg!.style.backgroundColor = color;
+ // };
+
+ //const SpeechRecognition = SpeechRecognition || webkitSpeechRecognition;
+
+ // recognition.continous = true;
+ // recognition.interimResults = true;
+ // recognition.lang = 'en-US';
+
+ const queryText = this._selectionText;
+
+ // const canvas = await html2canvas();
+ // const image = canvas.toDataURL("image/png", 1.0);
+ // (window as any)
+ // .html2canvas(this._marqueeref, {
+ // x: 100,
+ // y: 100,
+ // width: 100,
+ // height: 100,
+ // })
+ // .then((canvas: HTMLCanvasElement) => {
+ // const img = canvas.toDataURL('image/png');
+
+ // const link = document.createElement('a');
+ // link.href = img;
+ // link.download = 'screenshot.png';
+
+ // document.body.appendChild(link);
+ // link.click();
+ // link.remove();
+ // });
+
+ // var range = window.getSelection()?.getRangeAt(0);
+ // var selectionContents = range?.extractContents();
+ // var div = document.createElement("div");
+ // div.style.color = "yellow";
+ // div.appendChild(selectionContents!);
+ // range!.insertNode(div);
+
+ // const canvas = document.createElement('canvas');
+ // const scaling = 1 / (this._props.NativeDimScaling?.() || 1);
+ // const w = AnchorMenu.Instance.marqueeWidth * scaling;
+ // const h = AnchorMenu.Instance.marqueeHeight * scaling;
+ // canvas.width = w;
+ // canvas.height = h;
+ // const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions
+ // if (ctx) {
+ // this._marqueeref && ctx.drawImage(div, NumCast(this._marqueeref.current?.left) * scaling, NumCast(this._marqueeref.current?.top) * scaling, w, h, 0, 0, w, h);
+ // }
+ this._loading = true;
+ try {
+ if (this._selectionText === '') {
+ }
+ const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
+
+ console.log(res);
+ AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y']));
+ this._selectionText = '';
+ } catch (err) {
+ console.error(err);
+ }
+ this._loading = false;
+ };
+
@action
finishMarquee = (/* x?: number, y?: number */) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
@@ -411,8 +571,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
document.removeEventListener('pointerup', this.onSelectEnd);
const sel = window.getSelection();
+
if (sel) {
AnchorMenu.Instance.setSelectedText(sel.toString());
+ AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y']));
}
if (sel?.type === 'Range') {
@@ -424,6 +586,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
// allows for creating collection
AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
+ AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards;
};
@action
@@ -451,6 +614,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
this._mainCont.current!.style.transform = '';
}
this._selectionContent = selRange.cloneContents();
+
this._selectionText = this._selectionContent?.textContent || '';
// clear selection
@@ -612,6 +776,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
/>
)}
</div>
+ {this._loading ? (
+ <div className="loading-spinner" style={{ position: 'absolute' }}>
+ <ReactLoading type="spin" height={80} width={80} color={'blue'} />
+ </div>
+ ) : null}
</div>
);
}