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.tsx77
-rw-r--r--src/client/views/pdf/Annotation.tsx7
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss20
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx200
-rw-r--r--src/client/views/pdf/PDFViewer.scss4
-rw-r--r--src/client/views/pdf/PDFViewer.tsx84
6 files changed, 203 insertions, 189 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 9aa8fe649..eb6516403 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,24 +1,19 @@
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorResult } from 'react-color';
-import ReactLoading from 'react-loading';
import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, unimplementedFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
import { SettingsManager } from '../../util/SettingsManager';
-import { undoBatch } from '../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
-import { ComparisonBox } from '../nodes/ComparisonBox';
import { DocumentView } from '../nodes/DocumentView';
-import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import './AnchorMenu.scss';
import { GPTPopup } from './GPTPopup/GPTPopup';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -73,7 +68,6 @@ 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;
@@ -98,57 +92,11 @@ 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 = () => GPTPopup.Instance.generateSummary(this._selectedText);
-
- /*
- * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them.
- */
-
- transferToFlashcard = (text: string, x: number, y: number) => {
- ComparisonBox.createFlashcardDeck(text, 250, 200, 'data_front', 'data_back').then(
- action(newCol => {
- newCol.x = x;
- newCol.y = y;
- newCol.zIndex = 1000;
- this.addToCollection?.(newCol);
- this._loading = false;
- })
- );
- };
-
- /**
- * Creates a GPT drawing based on selected text.
- */
- gptDraw = (e: React.PointerEvent) => {
- try {
- SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation;
- runInAction(() => (this._isLoading = true));
- SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then(
- action(() => {
- this._isLoading = false;
- })
- );
- } catch (err) {
- console.error(err);
- }
+ gptAskAboutSelection = () => {
+ GPTPopup.Instance.askAIAboutSelection(this._selectedText);
+ AnchorMenu.Instance.fadeOut(true);
};
- /**
- * Defines how a GPT drawing should be added to the current document.
- */
- @undoBatch
- createDrawingAnnotation = action((drawing: Doc, opts: DrawingOptions, gptRes: string) => {
- this.AddDrawingAnnotation(drawing);
- const docData = drawing[DocData];
- docData.title = opts.text?.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
- docData.ai_drawing_input = opts.text;
- docData.ai_drawing_complexity = opts.complexity;
- docData.ai_drawing_colored = opts.autoColor;
- docData.ai_drawing_size = opts.size;
- docData.ai_drawing_data = gptRes;
- docData.ai = 'gpt';
- });
-
pointerDown = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -225,23 +173,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
{this._selectedText && (
<IconButton
- tooltip="Summarize with AI" //
- onPointerDown={this.gptSummarize}
+ tooltip="Ask AI..." //
+ onPointerDown={this.gptAskAboutSelection}
icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
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="layer-group" size="lg" />} color={SettingsManager.userColor} />
- <IconButton tooltip="Create labels" onPointerDown={this.makeLabels} icon={<FontAwesomeIcon icon="tag" size="lg" />} color={SettingsManager.userColor} />
- {this._selectedText && (
- <IconButton
- tooltip="Create drawing"
- onPointerDown={e => this.gptDraw(e)}
- icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon="paintbrush" size="lg" />}
- color={SettingsManager.userColor}
- />
- )}
+ {this.makeLabels === unimplementedFunction ? null : <IconButton tooltip="Create labels" onPointerDown={this.makeLabels} icon={<FontAwesomeIcon icon="tag" size="lg" />} color={SettingsManager.userColor} />}
{this._selectedText && RichTextMenu.Instance?.createLinkButton()}
{AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
<IconButton
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 1891cfd4c..e8a5235c9 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -50,7 +50,7 @@ interface IAnnotationProps extends FieldViewProps {
}
@observer
export class Annotation extends ObservableReactComponent<IAnnotationProps> {
- constructor(props: any) {
+ constructor(props: IAnnotationProps) {
super(props);
makeObservable(this);
}
@@ -58,7 +58,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
@computed get linkHighlighted() {
const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => {
const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc);
- return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+ return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)!);
});
return found;
}
@@ -112,6 +112,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined);
background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor));
render() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const forceRenderHack = [this.background(), this.outline(), this.opacity()]; // forces a re-render when these change -- because RegionAnnotation doesn't do this internally..
return (
<div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}>
@@ -121,7 +122,7 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
.map(([x, y, width, height]) => (
<div
key={'' + x + y + width + height}
- style={{ pointerEvents: this._props.pointerEvents?.() as any }}
+ style={{ pointerEvents: this._props.pointerEvents?.() as Property.PointerEvents }}
onPointerDown={this.onPointerDown}
onContextMenu={this.onContextMenu}
onPointerEnter={() => {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index c8903e09f..bb43291ee 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -9,18 +9,16 @@ $headingHeight: 32px;
.gptPopup-summary-box {
position: fixed;
+ padding-left: 10px;
+ padding-right: 10px;
top: 115px;
left: 75px;
width: 100%;
height: 100%;
top: 0;
left: 0;
- pointer-events: none;
border-top: solid gray 20px;
border-radius: 16px;
- padding: 16px;
- padding-bottom: 0;
- padding-top: 0px;
z-index: 999;
display: flex;
flex-direction: column;
@@ -29,11 +27,13 @@ $headingHeight: 32px;
box-shadow: 0 2px 5px #7474748d;
color: $textgrey;
- .gptPopup-sortBox {
+ .gptPopup-summaryBox-content {
+ padding-right: 16px;
+ padding-left: 16px;
+ position: relative;
+ overflow: hidden;
display: flex;
flex-direction: column;
- height: calc(100% - $inputHeight - $headingHeight);
- pointer-events: all;
}
.summary-heading {
@@ -50,7 +50,6 @@ $headingHeight: 32px;
}
label {
- color: $textgrey;
font-size: 12px;
font-weight: 400;
letter-spacing: 1px;
@@ -65,7 +64,9 @@ $headingHeight: 32px;
.gptPopup-content-wrapper {
padding-top: 10px;
min-height: 50px;
- height: calc(100% - 32px);
+ white-space: pre-line;
+ overflow: auto;
+ margin-bottom: 10px;
}
.inputWrapper {
@@ -73,7 +74,6 @@ $headingHeight: 32px;
justify-content: center;
align-items: center;
height: $inputHeight;
- background-color: white;
width: 100%;
pointer-events: all;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 4dc45e6a0..c45d8e052 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -31,6 +31,7 @@ import { OpenWhere } from '../../nodes/OpenWhere';
import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
import { ImageField } from '../../../../fields/URLField';
import { List } from '../../../../fields/List';
+import { ComparisonBox } from '../../nodes/ComparisonBox';
export enum GPTPopupMode {
SUMMARY, // summary of seleted document text
@@ -56,7 +57,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
private _dataJson: string = '';
private _documentDescriptions: Promise<string> | undefined; // a cache of the descriptions of all docs in the selected collection. makes it more efficient when asking GPT multiple questions about the collection.
private _sidebarFieldKey: string = '';
- private _textToSummarize: string = '';
+ private _aiReferenceText: string = '';
private _imageDescription: string = '';
private _textToDocMap = new Map<string, Doc>(); // when GPT answers with a doc's content, this helps us find the Doc
private _addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
@@ -79,7 +80,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
};
componentDidUpdate() {
- this._gptProcessing && this.setStopAnimatingResponse(false);
+ //this._gptProcessing && this.setStopAnimatingResponse(false);
}
componentDidMount(): void {
reaction(
@@ -100,14 +101,14 @@ export class GPTPopup extends ObservableReactComponent<object> {
);
}
+ @observable private _showOriginal = true;
+ @observable private _responseText: string = '';
@observable private _conversationArray: string[] = ['Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. '];
@observable private _fireflyArray: string[] = ['Hi! In this pop up, you can ask Firefly to create images. '];
@observable private _chatEnabled: boolean = false;
@action private setChatEnabled = (start: boolean) => (this._chatEnabled = start);
@observable private _gptProcessing: boolean = false;
@action private setGptProcessing = (loading: boolean) => (this._gptProcessing = loading);
- @observable private _responseText: string = '';
- @action private setResponseText = (text: string) => (this._responseText = text);
@observable private _imgUrls: string[][] = [];
@action private setImgUrls = (imgs: string[][]) => (this._imgUrls = imgs);
@observable private _collectionContext: Doc | undefined = undefined;
@@ -209,7 +210,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
generateFireflyImage = (imgDesc: string) => {
const selView = DocumentView.Selected().lastElement();
const selDoc = selView?.Document;
- if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) {
+ if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) {
const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title));
const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc;
return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc)
@@ -286,18 +287,30 @@ export class GPTPopup extends ObservableReactComponent<object> {
/**
* Completes an API call to generate a summary of the specified text
*
- * @param text the text to summarizz
+ * @param text the text to summarize
*/
- generateSummary = (text: string) => {
+ private generateSummary = action((text: string) => {
SnappingManager.SetChatVisible(true);
- this._textToSummarize = text;
- this.setMode(GPTPopupMode.SUMMARY);
+ this._showOriginal = false;
this.setGptProcessing(true);
return gptAPICall(text, GPTCallType.SUMMARY)
- .then(res => this.setResponseText(res || 'Something went wrong.'))
+ .then(action(res => (this._responseText = res || 'Something went wrong.')))
.catch(err => console.error(err))
.finally(() => this.setGptProcessing(false));
- };
+ });
+
+ /**
+ * Completes an API call to generate a summary of the specified text
+ *
+ * @param text the text to summarizz
+ */
+ askAIAboutSelection = action((text: string) => {
+ SnappingManager.SetChatVisible(true);
+ this._aiReferenceText = text;
+ this._responseText = '';
+ this._showOriginal = true;
+ this.setMode(GPTPopupMode.SUMMARY);
+ });
/**
* Completes an API call to generate an analysis of
@@ -306,14 +319,16 @@ export class GPTPopup extends ObservableReactComponent<object> {
generateDataAnalysis = () => {
this.setGptProcessing(true);
return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt)
- .then(res => {
- const json = JSON.parse(res! as string);
- const keys = Object.keys(json);
- this._correlatedColumns = [];
- this._correlatedColumns.push(json[keys[0]]);
- this._correlatedColumns.push(json[keys[1]]);
- this.setResponseText(json[keys[2]] || 'Something went wrong.');
- })
+ .then(
+ action(res => {
+ const json = JSON.parse(res! as string);
+ const keys = Object.keys(json);
+ this._correlatedColumns = [];
+ this._correlatedColumns.push(json[keys[0]]);
+ this._correlatedColumns.push(json[keys[1]]);
+ this._responseText = json[keys[2]] || 'Something went wrong.';
+ })
+ )
.catch(err => console.error(err))
.finally(() => this.setGptProcessing(false));
};
@@ -336,6 +351,24 @@ export class GPTPopup extends ObservableReactComponent<object> {
});
}
};
+ /**
+ * Create Flashcards for the selected text
+ */
+ private createFlashcards = action(
+ () =>
+ this.setGptProcessing(true) &&
+ gptAPICall(this._aiReferenceText, GPTCallType.FLASHCARD, undefined, true)
+ .then(res =>
+ ComparisonBox.createFlashcardDeck(res, 250, 200, 'data_front', 'data_back').then(
+ action(newCol => {
+ newCol.zIndex = 1000;
+ DocumentViewInternal.addDocTabFunc(newCol, OpenWhere.addRight);
+ })
+ )
+ )
+ .catch(console.error)
+ .finally(action(() => (this._gptProcessing = false)))
+ );
/**
* Creates a histogram to show the correlation relationship that was found
@@ -378,7 +411,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Ask Firefly to create images"
text="Ask Firefly"
onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -393,7 +427,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
text="Ask GPT"
onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -412,7 +447,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
this.setMode(GPTPopupMode.QUIZ_RESPONSE);
this.onQuizRandom?.();
}}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
type={Type.TERT}
style={{
width: '100%',
@@ -484,6 +520,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
onChange={e => onChange(e.target.value)}
onKeyDown={e => this.handleKeyPress(e, this._mode)}
type="text"
+ style={{ color: SnappingManager.userColor }}
placeholder={placeholder}
/>
<Button //
@@ -491,7 +528,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
type={Type.TERT}
icon={<AiOutlineSend />}
iconPlacement="right"
- color={SnappingManager.userVariantColor}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
onClick={() => this.callGpt(this._mode)}
/>
<DictationButton ref={r => (this._askDictation = r)} setInput={onChange} />
@@ -518,7 +556,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
</div>
</div>
<div key={rawSrc[0] + i + 'btn'} className="btn-container">
- <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+ <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
</>
))}
@@ -528,7 +566,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
tooltip="Generate Again"
onClick={() => this._imgTargetDoc && this.generateImage(this._imageDescription, this._imgTargetDoc, this._addToCollection)}
icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
- color={StrCast(Doc.UserDoc().userVariantColor)}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
/>
)}
</div>
@@ -536,37 +575,86 @@ export class GPTPopup extends ObservableReactComponent<object> {
summaryBox = () => (
<>
- <div style={{ height: 'calc(100% - 60px)', overflow: 'auto' }}>
- {this.heading('SUMMARY')}
+ <div className="gptPopup-summaryBox-content">
+ <div onClick={action(() => (this._showOriginal = !this._showOriginal))}>{this.heading(this._showOriginal ? 'SELECTION' : 'SUMMARY')}</div>
<div className="gptPopup-content-wrapper">
- {!this._gptProcessing &&
- (!this._stopAnimatingResponse ? (
- <TypeAnimation
- speed={50}
- sequence={[
- this._responseText,
- () => {
- setTimeout(() => this.setStopAnimatingResponse(true), 500);
- },
- ]}
- />
+ {!this._gptProcessing && !this._stopAnimatingResponse && this._responseText ? (
+ <TypeAnimation
+ speed={50}
+ sequence={[
+ this._responseText,
+ () => {
+ setTimeout(() => this.setStopAnimatingResponse(true), 500);
+ },
+ ]}
+ />
+ ) : this._showOriginal ? (
+ this._gptProcessing ? (
+ '...generating cards...'
) : (
- this._responseText
- ))}
+ this._aiReferenceText
+ )
+ ) : (
+ this._responseText || (this._gptProcessing ? '...generating summary...' : '-no ai summary-')
+ )}
</div>
</div>
- {!this._gptProcessing && (
- <div className="btns-wrapper" style={{ position: 'absolute', bottom: 0, width: 'calc(100% - 32px)' }}>
- {this._stopAnimatingResponse ? (
- <>
- <IconButton tooltip="Generate Again" onClick={() => this.generateSummary(this._textToSummarize + ' ')} 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} />
- </>
+ {this._gptProcessing ? null : (
+ <div className="btns-wrapper" style={{ position: 'relative', width: 'calc(100% - 32px)' }}>
+ {this._stopAnimatingResponse || !this._responseText ? (
+ <div style={{ display: 'flex' }}>
+ {!this._showOriginal ? (
+ <>
+ <Button
+ tooltip="Show originally selected text" //
+ text="Selection"
+ onClick={action(() => (this._showOriginal = true))}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ <Button
+ tooltip="Create a text Doc with this text and link to the text selection" //
+ text="Transfer To Text"
+ onClick={this.transferToText}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ </>
+ ) : (
+ <>
+ <Button
+ tooltip="Show AI summary of original selection text (Shift+Click to regenerate)"
+ text="Summary"
+ onClick={action(e => {
+ if (e.shiftKey) {
+ this.setStopAnimatingResponse(false);
+ this._aiReferenceText += ' ';
+ this._responseText = '';
+ }
+ this.generateSummary(this._aiReferenceText);
+ })}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ <Button
+ tooltip="Create Flashcards" //
+ text="Create Flashcards"
+ onClick={this.createFlashcards}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userVariantColor}
+ type={Type.TERT}
+ />
+ </>
+ )}
+ </div>
) : (
<div className="summarizing">
- <span>Summarizing</span>
+ <span>{this._showOriginal ? 'Creating Cards...' : 'Summarizing'}</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SettingsManager.userVariantColor)} type={Type.TERT} />
+ <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
)}
</div>
@@ -611,19 +699,19 @@ export class GPTPopup extends ObservableReactComponent<object> {
placeholder="Ask GPT a question about the data..."
id="search-input"
className="searchBox-input"
- style={{ width: '100%' }}
+ style={{ width: '100%', color: SnappingManager.userColor }}
/>
) : (
<>
- <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
- <Button tooltip="Chat with AI" text="Chat with AI" onClick={() => this.setChatEnabled(true)} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
+ <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
+ <Button tooltip="Chat with AI" text="Chat with AI" onClick={() => this.setChatEnabled(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</>
)
) : (
<div className="summarizing">
<span>Summarizing</span>
<ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
- <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={StrCast(SnappingManager.userVariantColor)} type={Type.TERT} />
+ <Button text="Stop Animation" onClick={() => this.setStopAnimatingResponse(true)} color={SettingsManager.userColor} background={SettingsManager.userVariantColor} type={Type.TERT} />
</div>
)}
</div>
@@ -640,7 +728,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
);
heading = (headingText: string) => (
- <div className="summary-heading">
+ <div className="summary-heading" style={{ color: SnappingManager.userBackgroundColor }}>
<label className="summary-text">{headingText}</label>
{this._gptProcessing ? (
<ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
@@ -656,7 +744,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')}
/>
{[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && (
- <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+ <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
)}
</>
)}
@@ -665,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
render() {
return (
- <div className="gptPopup-summary-box" style={{ display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
+ <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
{(() => {
//prettier-ignore
switch (this._mode) {
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 030251762..1aab2b853 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -29,7 +29,11 @@
.textLayer {
opacity: unset;
mix-blend-mode: multiply; // bcz: makes text fuzzy!
+ // all these below fix issues with PDFjs
transform: scale(var(--devicePixelRatio));
+ --total-scale-factor: var(--scale-factor); // these 3 are used by PDFjs but not defined. why???
+ --scale-round-x: 1px;
+ --scale-round-y: 1px;
}
[data-main-rotation='90'] {
transform: scale(var(--devicePixelRatio)) rotate(90deg) translateY(-100%);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 167421a4a..fc2567fbc 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,9 +1,10 @@
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as Pdfjs from 'pdfjs-dist';
+import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf.mjs';
import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
-import 'pdfjs-dist/webpack.mjs'; // sets the PDF workerSrc
import * as React from 'react';
+import ReactLoading from 'react-loading';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
import { CreateLinkToActiveAudio, Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData, Height } from '../../../fields/DocSymbols';
@@ -11,9 +12,10 @@ import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, numberRange } from '../../../Utils';
+import { emptyFunction, numberRange, unimplementedFunction } from '../../../Utils';
import { DocUtils } from '../../documents/DocUtils';
import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -28,13 +30,13 @@ 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 { Transform } from '../../util/Transform';
+import { DocumentViewProps } from '../nodes/DocumentContentsView';
+if (window?.Worker) GlobalWorkerOptions.workerSrc = 'files/node_modules/pdfjs-dist/build/pdf.worker.min.mjs'; // npm start/etc use copyfiles to copy the worker from the pdfjs-dist package to the public folder
+export * from 'pdfjs-dist/build/pdf.mjs';
interface IViewerProps extends FieldViewProps {
pdfBox: PDFBox;
- Document: Doc;
+ Doc: Doc;
dataDoc: Doc;
layoutDoc: Doc;
fieldKey: string;
@@ -53,7 +55,7 @@ interface IViewerProps extends FieldViewProps {
*/
@observer
export class PDFViewer extends ObservableReactComponent<IViewerProps> {
- static _annotationStyle = addStyleSheet();
+ static _annotationStyle = addStyleSheet().sheet;
constructor(props: IViewerProps) {
super(props);
@@ -112,8 +114,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
() => this._props.layoutDoc._layout_autoHeight,
layoutAutoHeight => {
if (layoutAutoHeight) {
- this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
- this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
+ this._props.layoutDoc._nativeHeight = NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']);
+ this._props.setHeight?.(NumCast(this._props.Doc[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
}
}
);
@@ -124,7 +126,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{ fireImmediately: true }
);
this._disposers.curPage = reaction(
- () => Cast(this._props.Document._layout_curPage, 'number', null),
+ () => Cast(this._props.Doc._layout_curPage, 'number', null),
page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
{ fireImmediately: true }
);
@@ -132,7 +134,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
componentWillUnmount = () => {
Object.values(this._disposers).forEach(disposer => disposer?.());
- document.removeEventListener('copy', this.copy);
+ document.removeEventListener('copy', this.copy, true);
};
copy = (e: ClipboardEvent) => {
@@ -144,6 +146,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
e.clipboardData.setData('dash/pdfAnchor', anchor[DocData][Id]);
}
e.preventDefault();
+ e.stopPropagation();
}
};
@@ -180,7 +183,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
scrollFocus = (doc: Doc, scrollTop: number, options: FocusViewOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
- if (doc !== this._props.Document && mainCont) {
+ if (doc !== this._props.Doc && mainCont) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
const scrollTo = ClientUtils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
@@ -215,11 +218,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{ fireImmediately: true }
);
this._disposers.scroll = reaction(
- () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
+ () => Math.abs(NumCast(this._props.Doc._layout_scrollTop)),
pos => {
if (!this._ignoreScroll) {
this._showWaiting && this.setupPdfJsViewer();
- const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
+ const viewTrans = quickScroll?.loc ?? StrCast(this._props.Doc._viewTransition);
const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -260,16 +263,11 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
return;
}
- document.removeEventListener('copy', this.copy);
- document.addEventListener('copy', this.copy);
+ document.removeEventListener('copy', this.copy, true);
+ document.addEventListener('copy', this.copy, true);
const eventBus = new PDFJSViewer.EventBus();
eventBus._on('pagesinit', this.pagesinit);
- eventBus._on(
- 'pagerendered',
- action(() => {
- this._showWaiting = false;
- })
- );
+ eventBus._on('pagerendered',action(() => (this._showWaiting = false))); // prettier-ignore
const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
@@ -370,9 +368,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
- if ((this._props.Document._freeform_scale || 1) !== 1) return;
+ if ((this._props.Doc._freeform_scale || 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this._props.isContentActive()) {
- this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
+ this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Doc);
}
if (!e.altKey && e.button === 0 && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
this._props.select(false);
@@ -392,25 +390,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
}
};
- /**
- * Create a flashcard pile based on the selected text of a pdf.
- */
- gptPDFFlashcards = async () => {
- const queryText = this._selectionText;
- this._loading = true;
- try {
- const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
-
- 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 */) => {
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
this.isAnnotating = false;
this._marqueeref.current?.onTerminateSelection();
@@ -429,7 +411,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
if (sel) {
AnchorMenu.Instance.setSelectedText(sel.toString());
- AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc['x']), NumCast(this._props.layoutDoc['y']));
+ AnchorMenu.Instance.setLocation(NumCast(this._props.layoutDoc.x), NumCast(this._props.layoutDoc.y));
}
if (sel?.type === 'Range') {
@@ -441,14 +423,14 @@ 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;
+ AnchorMenu.Instance.makeLabels = unimplementedFunction;
AnchorMenu.Instance.AddDrawingAnnotation = this.addDrawingAnnotation;
};
addDrawingAnnotation = (drawing: Doc) => {
- // drawing[DocData].x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX
+ // drawing.x = this._props.pdfBox.ScreenToLocalBoxXf().TranslateX
// const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- drawing.y = NumCast(drawing.y) + NumCast(this._props.Document.layout_scrollTop);
+ drawing.y = NumCast(drawing.y) + NumCast(this._props.Doc.layout_scrollTop);
this._props.addDocument?.(drawing);
};
@@ -493,7 +475,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick = (e: React.MouseEvent) => {
this._scrollStopper?.();
if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < ClientUtils.DRAG_THRESHOLD) {
- this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
+ this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Doc);
}
// e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks
};
@@ -521,7 +503,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
@computed get annotationLayer() {
const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
return (
- <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+ <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Doc), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
{inlineAnnos.map(anno => (
<Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} />
))}
@@ -536,7 +518,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
opaqueFilter = () => [...this._props.childFilters(), ClientUtils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [ClientUtils.OpaqueBackgroundFilter])];
- childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps & DocumentViewProps>, property: string) => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
@@ -612,7 +594,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
onClick={this.onClick}
style={{
overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
- height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
+ height: !this._props.Doc._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Doc) : `100%`,
}}>
{this.pdfViewerDiv}
{this.annotationLayer}
@@ -621,12 +603,12 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
{!this._mainCont.current || !this._annotationLayer.current || !this.props.pdfBox.DocumentView ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
- Document={this._props.Document}
+ Document={this._props.Doc}
getPageFromScroll={this.getPageFromScroll}
anchorMenuClick={this._props.anchorMenuClick}
scrollTop={0}
annotationLayerScaling={() => Pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS}
- annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
+ annotationLayerScrollTop={NumCast(this._props.Doc._layout_scrollTop)}
addDocument={this.addDocumentWrapper}
docView={this.props.pdfBox.DocumentView}
screenTransform={this.screenToMarqueeXf}