aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/SnappingManager.ts3
-rw-r--r--src/client/views/MainView.tsx3
-rw-r--r--src/client/views/OverlayView.scss13
-rw-r--r--src/client/views/OverlayView.tsx18
-rw-r--r--src/client/views/ScriptBox.tsx1
-rw-r--r--src/client/views/global/globalScripts.ts8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx4
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.scss69
-rw-r--r--src/client/views/pdf/GPTPopup/GPTPopup.tsx62
9 files changed, 98 insertions, 83 deletions
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 2a150dc5a..9d8a41844 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -32,6 +32,7 @@ export class SnappingManager {
@observable _hideDecorations: boolean = false;
@observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _inkShape: Gestures | undefined = undefined;
+ @observable _chatVisible: boolean = false;
private constructor() {
SnappingManager._manager = this;
@@ -66,6 +67,7 @@ export class SnappingManager {
public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore
public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore
public static get InkShape() { return this.Instance._inkShape; } // prettier-ignore
+ public static get ChatVisible() { return this.Instance._chatVisible; } // prettier-ignore
public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore
public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
@@ -85,6 +87,7 @@ export class SnappingManager {
public static SetHideDecorations= (state:boolean) =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore
public static SetKeepGestureMode= (state:boolean) =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore
public static SetInkShape = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore
+ public static SetChatVisible = (vis:boolean) =>runInAction(() => {this.Instance._chatVisible = vis}); // prettier-ignore
public static userColor: string | undefined;
public static userVariantColor: string | undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d748b70ae..195b1c572 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -78,6 +78,7 @@ import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
import { TopBar } from './topbar/TopBar';
+import { OverlayView } from './OverlayView';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@@ -168,6 +169,7 @@ export class MainView extends ObservableReactComponent<object> {
mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
componentDidMount() {
+ OverlayView.Instance.addWindow(<GPTPopup />, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false) });
// Utils.TraceConsoleLog();
reaction(
// when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
@@ -1154,7 +1156,6 @@ export class MainView extends ObservableReactComponent<object> {
<InkTranscription />
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
- <GPTPopup key="gptpopup" />
<SchemaCSVPopUp key="schemacsvpopup" />
<ImageEditorBox imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
</div>
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 33a297fd4..f4998efa1 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -4,7 +4,7 @@
top: 0;
width: 100vw;
height: 100vh;
- z-index: 1001; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
+ z-index: 2002; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
/* background-color: pink; */
user-select: none;
}
@@ -26,27 +26,30 @@
}
.overlayWindow-titleBar {
- flex: 0 1 30px;
+ flex: 0 1 20px;
background: darkslategray;
color: whitesmoke;
text-align: center;
cursor: move;
+ z-index: 1;
}
.overlayWindow-content {
flex: 1 1 auto;
display: flex;
flex-direction: column;
+ z-index: 0;
}
.overlayWindow-closeButton {
float: right;
- height: 30px;
- width: 30px;
+ height: 20px;
+ width: 20px;
+ padding: 0;
+ background-color: inherit;
}
.overlayWindow-resizeDragger {
- background-color: rgb(0, 0, 0);
position: absolute;
right: 0px;
bottom: 0px;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 5e9677b45..20931fc3d 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
import './OverlayView.scss';
import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider';
import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+import { SnappingManager } from '../util/SnappingManager';
export type OverlayDisposer = () => void;
@@ -27,12 +28,17 @@ export type OverlayElementOptions = {
width?: number;
height?: number;
title?: string;
+ onClick?: (e: React.MouseEvent) => void;
+ isHidden?: () => boolean;
+ backgroundColor?: string;
};
export interface OverlayWindowProps {
children: JSX.Element;
overlayOptions: OverlayElementOptions;
- onClick: () => void;
+ onClick: (e: React.MouseEvent) => void;
+ isHidden?: () => boolean;
+ backgroundColor?: string;
}
@observer
@@ -93,15 +99,17 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
render() {
return (
- <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
- <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
+ <div
+ className="overlayWindow-outerDiv"
+ style={{ display: this.props.isHidden?.() ? 'none' : undefined, backgroundColor: this._props.backgroundColor, transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
+ <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown} style={{ backgroundColor: SnappingManager.userVariantColor, color: SnappingManager.userColor }}>
{this._props.overlayOptions.title || 'Untitled'}
<button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
X
</button>
</div>
<div className="overlayWindow-content">{this.props.children}</div>
- <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
+ <div className="overlayWindow-resizeDragger" style={{ backgroundColor: SnappingManager.userVariantColor }} onPointerDown={this.onResizerPointerDown} />
</div>
);
}
@@ -166,7 +174,7 @@ export class OverlayView extends ObservableReactComponent<object> {
if (index !== -1) this._elements.splice(index, 1);
});
const wincontents = (
- <OverlayWindow onClick={() => remove(wincontents)} key={Utils.GenerateGuid()} overlayOptions={options}>
+ <OverlayWindow isHidden={options.isHidden} backgroundColor={options.backgroundColor} onClick={options.onClick ?? (() => remove(wincontents))} key={Utils.GenerateGuid()} overlayOptions={options}>
{contents}
</OverlayWindow>
);
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 9c36e6d26..d05b0a6b6 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 790ebeabe..2bc0e3338 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -223,13 +223,13 @@ ScriptingGlobals.add(function showFreeform(
setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; },
}],
['toggle-chat', {
- checkResult: (doc: Doc) => GPTPopup.Instance.Visible,
+ checkResult: (doc: Doc) => SnappingManager.ChatVisible,
setDoc: (doc: Doc, dv: DocumentView) => {
- if (GPTPopup.Instance.Visible){
+ if (SnappingManager.ChatVisible){
doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
- GPTPopup.Instance.setVisible(false);
+ SnappingManager.SetChatVisible(false);
} else {
- GPTPopup.Instance.setVisible(true);
+ SnappingManager.SetChatVisible(true);
GPTPopup.Instance.setMode(GPTPopupMode.GPT_MENU);
}
},
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6960247e9..3abb39ff2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -977,7 +977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
},
icon: 'star',
});
- optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), 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 &&
@@ -1061,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
}
});
- generateImage = async () => {
+ generateImage = () => {
GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
GPTPopup.Instance.generateImage((this.dataDoc.text as RichTextField)?.Text, this.Document, this._props.addDocument);
};
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 9cf318dc0..0b832f64c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -4,19 +4,23 @@ $greyborder: #d3d3d3;
$lightgrey: #ececec;
$button: #5b97ff;
$highlightedText: #82e0ff;
+$inputHeight: 60px;
+$headingHeight: 32px;
.gptPopup-summary-box {
position: fixed;
top: 115px;
left: 75px;
- width: 250px;
- height: 200px;
- min-height: 200px;
- min-width: 180px;
-
+ 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;
@@ -24,17 +28,12 @@ $highlightedText: #82e0ff;
background-color: #ffffff;
box-shadow: 0 2px 5px #7474748d;
color: $textgrey;
- resize: both; /* Allows resizing */
- overflow: auto;
-
- .resize-handle {
- width: 10px;
- height: 10px;
- background: #ccc;
- position: absolute;
- right: 0;
- bottom: 0;
- cursor: se-resize;
+
+ .gptPopup-sortBox {
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - $inputHeight - $headingHeight);
+ pointer-events: all;
}
.summary-heading {
@@ -42,7 +41,7 @@ $highlightedText: #82e0ff;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid $greyborder;
- padding-bottom: 5px;
+ height: $headingHeight;
.summary-text {
font-size: 12px;
@@ -66,28 +65,17 @@ $highlightedText: #82e0ff;
.gptPopup-content-wrapper {
padding-top: 10px;
min-height: 50px;
- // max-height: 150px;
- overflow-y: auto;
- height: 100%;
+ height: calc(100% - 32px);
}
- .btns-wrapper-gpt {
- height: 100%;
+ .inputWrapper {
display: flex;
justify-content: center;
align-items: center;
- flex-direction: column;
-
- .inputWrapper {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 60px;
- position: absolute;
- bottom: 0;
- width: 100%;
- background-color: white;
- }
+ height: $inputHeight;
+ background-color: white;
+ width: 100%;
+ pointer-events: all;
.searchBox-input {
height: 40px;
@@ -97,14 +85,21 @@ $highlightedText: #82e0ff;
border-color: #5b97ff;
width: 90%;
}
+ }
+ .btns-wrapper-gpt {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
.chat-wrapper {
display: flex;
flex-direction: column;
width: 100%;
- max-height: calc(100vh - 80px);
+ height: 100%;
overflow-y: auto;
- padding-bottom: 60px;
+ padding-right: 5px;
}
.chat-bubbles {
@@ -194,7 +189,7 @@ $highlightedText: #82e0ff;
.image-content-wrapper {
display: flex;
flex-direction: column;
- align-items: flex-start;
+ align-items: center;
gap: 8px;
padding-bottom: 16px;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index f09d786d0..72381cfad 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { CgClose, CgCornerUpLeft } from 'react-icons/cg';
+import { CgCornerUpLeft } from 'react-icons/cg';
import ReactLoading from 'react-loading';
import { TypeAnimation } from 'react-type-animation';
import { ClientUtils } from '../../../../ClientUtils';
@@ -108,8 +108,6 @@ export class GPTPopup extends ObservableReactComponent<object> {
@observable private _mode: GPTPopupMode = GPTPopupMode.SUMMARY;
@action public setMode = (mode: GPTPopupMode) => (this._mode = mode);
- @observable public Visible: boolean = false;
- @action public setVisible = (vis: boolean) => (this.Visible = vis);
onQuizRandom?: () => void;
onGptResponse?: (sortResult: string, questionType: GPTTypeStyle, tag?: string) => void;
@@ -233,10 +231,10 @@ export class GPTPopup extends ObservableReactComponent<object> {
*/
generateImage = (imgDesc: string, imgTarget: Doc, addToCollection?: (doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) => {
this._imgTargetDoc = imgTarget;
+ SnappingManager.SetChatVisible(true);
this.addDoc = addToCollection;
this.setImgUrls([]);
this.setMode(GPTPopupMode.IMAGE);
- this.setVisible(true);
this.setGptProcessing(true);
this._imageDescription = imgDesc;
@@ -259,8 +257,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
* @param text the text to summarizz
*/
generateSummary = (text: string) => {
+ SnappingManager.SetChatVisible(true);
this._textToSummarize = text;
- this.setVisible(true);
this.setMode(GPTPopupMode.SUMMARY);
this.setGptProcessing(true);
return gptAPICall(text, GPTCallType.SUMMARY)
@@ -274,7 +272,6 @@ export class GPTPopup extends ObservableReactComponent<object> {
* this.dataJson in the popup.
*/
generateDataAnalysis = () => {
- this.setVisible(true);
this.setGptProcessing(true);
return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt)
.then(res => {
@@ -398,7 +395,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
}
};
- gptUserInput = (isUserPrompt: boolean) => (
+ gptUserInput = () => (
<div className="btns-wrapper-gpt">
<div className="chat-wrapper">
<div className="chat-bubbles">
@@ -412,6 +409,15 @@ export class GPTPopup extends ObservableReactComponent<object> {
<div ref={this._messagesEndRef} style={{ height: '100px' }} />
</div>
+ </div>
+ );
+
+ promptBox = (isUserPrompt: boolean) => (
+ <>
+ <div className="gptPopup-sortBox">
+ {this.heading(isUserPrompt ? 'ASK' : 'QUIZ')}
+ {this.gptUserInput()}
+ </div>
<div className="inputWrapper">
<input
className="searchBox-input"
@@ -423,29 +429,31 @@ export class GPTPopup extends ObservableReactComponent<object> {
placeholder={`${isUserPrompt ? 'Have ChatGPT sort, tag, define, or filter your documents for you!' : 'Describe/answer the selected document!'}`}
/>
</div>
- </div>
+ </>
);
- promptBox = (isUserPrompt: boolean) => (
- <div className="gptPopup-sortBox" style={{ height: '80%' }}>
- {this.heading(isUserPrompt ? 'SORTING' : 'QUIZ')}
- {this._mode === GPTPopupMode.GPT_MENU ? this.gptMenu() : this.gptUserInput(isUserPrompt)}
+ menuBox = () => (
+ <div className="gptPopup-sortBox">
+ {this.heading('CHOOSE')}
+ {this.gptMenu()}
</div>
);
imageBox = () => (
- <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', overflow: 'auto', height: '100%', pointerEvents: 'all' }}>
{this.heading('GENERATED IMAGE')}
<div className="image-content-wrapper">
{this._imgUrls.map((rawSrc, i) => (
- <div key={rawSrc[0] + i} className="img-wrapper">
- <div className="img-container">
- <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+ <>
+ <div key={rawSrc[0] + i} className="img-wrapper">
+ <div className="img-container">
+ <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+ </div>
</div>
<div className="btn-container">
<Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
</div>
- </div>
+ </>
))}
</div>
{this._gptProcessing ? null : (
@@ -461,7 +469,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
summaryBox = () => (
<>
- <div>
+ <div style={{ height: 'calc(100% - 60px)', overflow: 'auto' }}>
{this.heading('SUMMARY')}
<div className="gptPopup-content-wrapper">
{!this._gptProcessing &&
@@ -481,7 +489,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
</div>
</div>
{!this._gptProcessing && (
- <div className="btns-wrapper">
+ <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)} />
@@ -571,19 +579,18 @@ export class GPTPopup extends ObservableReactComponent<object> {
<ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
) : (
<>
- {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && (
- <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} style={{ right: '50px', position: 'absolute' }} />
- )}
<Toggle
tooltip="Clear Chat filter"
toggleType={ToggleType.BUTTON}
type={Type.PRIM}
toggleStatus={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat')}
text={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat') ? 'filtered' : ''}
- color="red"
+ color={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat') ? 'red' : 'transparent'}
onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', '#chat', 'remove')}
/>
- <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />
+ {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && (
+ <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+ )}
</>
)}
</div>
@@ -591,20 +598,19 @@ export class GPTPopup extends ObservableReactComponent<object> {
render() {
return (
- <div className="gptPopup-summary-box" style={{ display: this.Visible ? 'flex' : 'none' }}>
+ <div className="gptPopup-summary-box" style={{ display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
{(() => {
//prettier-ignore
switch (this._mode) {
- case GPTPopupMode.GPT_MENU:
- case GPTPopupMode.USER_PROMPT:
+ case GPTPopupMode.USER_PROMPT:
case GPTPopupMode.QUIZ_RESPONSE: return this.promptBox(this._mode === GPTPopupMode.USER_PROMPT);
+ case GPTPopupMode.GPT_MENU: return this.menuBox();
case GPTPopupMode.SUMMARY: return this.summaryBox();
case GPTPopupMode.DATA: return this.dataAnalysisBox();
case GPTPopupMode.IMAGE: return this.imageBox();
default: return null;
}
})()}
- <div className="resize-handle" />
</div>
);
}