diff options
Diffstat (limited to 'src/client/views/pdf')
-rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 71 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.scss | 19 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 21 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 169 |
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> ); } |