diff options
Diffstat (limited to 'src')
22 files changed, 266 insertions, 268 deletions
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index a01b64eda..23ef9bba0 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -115,11 +115,11 @@ export class RTFMarkup extends React.Component<object> { {` display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`} </p> <p> - <b style={{ fontSize: 'larger' }}>{`[@fieldname:value] `}</b> + <b style={{ fontSize: 'larger' }}>{`@fieldname:value `}</b> {` assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`} </p> <p> - <b style={{ fontSize: 'larger' }}>{`[@fieldname:=expression] `}</b> + <b style={{ fontSize: 'larger' }}>{`@fieldname:=expression `}</b> {` assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`} </p> </div> diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 2d342d1b1..37060d20c 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -109,20 +109,6 @@ export class KeyManager { preventDefault: false, }; switch (keyname) { - case 'u': - if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const ungroupings = DocumentView.Selected(); - undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup'); - DocumentView.DeselectAll(); - } - break; - case 'g': - if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const selected = DocumentView.Selected(); - const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView); - cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping'); - } - break; case ' ': // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx index acda38dd7..f9ee8d3db 100644 --- a/src/client/views/StyleProviderQuiz.tsx +++ b/src/client/views/StyleProviderQuiz.tsx @@ -9,7 +9,7 @@ import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { NumCast, StrCast } from '../../fields/Types'; import { Networking } from '../Network'; -import { GPTCallType, gptAPICall, gptImageLabel } from '../apis/gpt/GPT'; +import { GPTCallType, gptAPICall } from '../apis/gpt/GPT'; import { Docs } from '../documents/Documents'; import { ContextMenu } from './ContextMenu'; import { ContextMenuProps } from './ContextMenuItem'; @@ -110,20 +110,21 @@ export namespace styleProviderQuiz { const blob = await ImageUtility.canvasToBlob(canvas); return selectUrlToBase64(blob); } - /** - * Create flashcards from an image. - */ - async function makeFlashcardsForImage(img: ImageBox) { - img.Loading = true; - try { - const hrefBase64 = await createCanvas(img); - const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: '); - AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y)); - } catch (error) { - console.log('Error', error); - } - img.Loading = false; - } + + // /** + // * Create flashcards from an image. + // */ + // async function makeFlashcardsForImage(img: ImageBox) { + // img.Loading = true; + // try { + // const hrefBase64 = await createCanvas(img); + // const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: '); + // AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y)); + // } catch (error) { + // console.log('Error', error); + // } + // img.Loading = false; + // } /** * Calls the createCanvas and pushInfo methods to convert the diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index c3047e5fb..a7a9f2114 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -187,13 +187,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @computed get rangeClick() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.clickAnchor(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */ } - )!; + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable } @computed get rangePlay() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.playOnClick(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */})!; + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable } rangeClickScript = () => this.rangeClick; rangePlayScript = () => this.rangePlay; @@ -268,13 +267,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action(() => { + action(movEv => { if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; this._timelineWrapper && (this._timelineWrapper.style.cursor = 'ew-resize'); } - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + this._markerEnd = this.toTimeline(movEv.clientX - rect.x, rect.width); return false; }), action((upEvent, movement, isClick) => { @@ -844,7 +843,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch styleProvider={this._props.styleProvider} renderDepth={this._props.renderDepth + 1} LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutString('data')} + LayoutTemplateString={LabelBox.LayoutString('title')} isDocumentActive={this._props.isDocumentActive} PanelWidth={width} PanelHeight={height} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 241a56a88..4ea1de680 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -107,7 +107,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc } function toNumber(val: FieldResult<FieldType>) { - return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getMilliseconds() : NumCast(val, Number(StrCast(val))); + return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getTime() : NumCast(val, Number(StrCast(val))); } export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 00d7ea451..ad52db496 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -113,6 +113,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps // tslint:disable-next-line:prefer-const const cm = ContextMenu.Instance; const [x, y] = this.Transform.transformPoint(this._downX, this._downY); + if (e.key === '?') { cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight) diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 0bf78f57c..53c0823ea 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -224,6 +224,7 @@ display: none; } +.schema-table-cell-selected, .schema-table-cell, .row-menu { border: 1px solid global.$medium-gray; @@ -232,6 +233,14 @@ display: inline-flex; padding: 0; align-items: center; + input[type='text'] { + border: unset; + } +} +.schema-table-cell-selected { + input[type='text'] { + background: lightgray; + } } .schemaRTFCell { @@ -310,4 +319,7 @@ .schemaField-editing { outline: none; height: 100%; + cursor: text; + outline: none; + overflow: auto; } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 8e9e8e1cc..05670562e 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -252,7 +252,8 @@ export class CollectionSchemaView extends CollectionSubView() { @action onKeyDown = (e: KeyboardEvent) => { if (this._selectedDocs.length > 0) { - switch (e.key) { + switch (e.key + (e.shiftKey ? 'Shift' : '')) { + case 'Enter': case 'ArrowDown': { const lastDoc = this._selectedDocs.lastElement(); @@ -272,6 +273,7 @@ export class CollectionSchemaView extends CollectionSubView() { e.preventDefault(); } break; + case 'EnterShift': case 'ArrowUp': { const firstDoc = this._selectedDocs.lastElement(); @@ -291,6 +293,7 @@ export class CollectionSchemaView extends CollectionSubView() { e.preventDefault(); } break; + case 'Tab': case 'ArrowRight': if (this._selectedCells) { this._selectedCol = Math.min(this._colEles.length - 1, this._selectedCol + 1); @@ -298,6 +301,7 @@ export class CollectionSchemaView extends CollectionSubView() { this.selectCell(this._selectedDocs[0], 0, false, false); } break; + case 'TabShift': case 'ArrowLeft': if (this._selectedCells) { this._selectedCol = Math.max(0, this._selectedCol - 1); diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx index e6acff061..e89822b4c 100644 --- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -86,15 +86,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro editing => { if (editing) { this.setupRefSelect(this.refSelectConditionMet); - setTimeout(() => { - if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) { - this._overlayDisposer?.(); - this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); - this._props.highlightCells?.(this._unrenderedContent); - this.setContent(this._unrenderedContent); - setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); - } - }); } else { this._overlayDisposer?.(); this._overlayDisposer = undefined; @@ -192,6 +183,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setIsFocused = (value: boolean) => { const wasFocused = this._editing; this._editing = value; + this._editing && setTimeout(() => this._inputref?.focus()); return wasFocused !== this._editing; }; @@ -272,8 +264,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here - switch (e.key) { case 'Tab': e.stopPropagation(); @@ -284,9 +274,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro break; case 'Enter': e.stopPropagation(); - if (!e.ctrlKey) { - this.finalizeEdit(e.shiftKey, false, true); - } + !e.ctrlKey && this.finalizeEdit(e.shiftKey, false, true); break; case 'Escape': e.stopPropagation(); @@ -297,7 +285,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro case 'ArrowLeft': case 'ArrowRight': // prettier-ignore e.stopPropagation(); - setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet)); break; case ' ': { @@ -306,18 +294,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setTimeout(() => { this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(cursorPos)); - }, 0); + }); } break; - case 'u': // for some reason 'u' otherwise exits the editor - e.stopPropagation(); - break; case 'Shift': case 'Alt': case 'Meta': case 'Control': - case ':': // prettier-ignore - break; + case ':': default: break; } @@ -361,12 +345,9 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro <div contentEditable className="schemaField-editing" - ref={r => { - this._inputref = r; - }} - style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} + ref={r => (this._inputref = r)} + style={{ minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))} - autoFocus onInput={this.onChange} onKeyDown={this.onKeyDown} onPointerDown={e => { @@ -383,14 +364,37 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro ); }; + onFocus = () => { + if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) { + this._overlayDisposer?.(); + this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); + this._props.highlightCells?.(this._unrenderedContent); + this.setContent(this._unrenderedContent); + setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); + } + }; + + onBlur = action(() => { + this._editing = false; + }); + render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); if (this._editing && gval !== undefined) { - return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>; + return ( + <div + className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} // + onFocus={this.onFocus} + onBlur={this.onBlur}> + {this.renderEditor()} + </div> + ); } else return this._props.contents instanceof ObjectField ? null : ( <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} + onFocus={this.onFocus} + onBlur={this.onBlur} style={{ minHeight: '10px', whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line', diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 941402d6d..1b4f200a3 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -102,8 +102,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return true; }; public static renderProps(props: SchemaTableCellProps) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props; + const { Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props; let protoCount = 0; let doc: Doc | undefined = Document; while (doc) { @@ -187,7 +186,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return ( <div className="schemacell-edit-wrapper" - // onContextMenu={} + tabIndex={1} style={{ color, textDecoration, @@ -261,7 +260,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro render() { return ( <div - className="schema-table-cell" + className={`schema-table-cell${selectedCell(this._props) ? '-selected' : ''}`} onContextMenu={e => StopEvent(e)} onPointerDown={action(e => { if (this.lockedInteraction) { @@ -397,10 +396,10 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp const { pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <> - <div style={{ pointerEvents: 'none' }}> + <div style={{ pointerEvents: 'none' }} tabIndex={1}> <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} /> </div> - {pointerEvents === 'none' ? null : ( + {pointerEvents === 'none' || !selectedCell(this._props) ? null : ( <Popup icon={<FontAwesomeIcon size="xs" icon="caret-down" />} size={Size.XSMALL} @@ -495,14 +494,22 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC <div style={{ width: '100%' }}> <Select styles={{ + dropdownIndicator: base => ({ + ...base, + display: selectedCell(this._props) ? 'unset' : 'none', + }), container: base => ({ ...base, height: 20, + border: 'unset !important', + pointerEvents: selectedCell(this._props) ? 'all' : 'none', }), control: base => ({ ...base, height: 20, minHeight: 20, + border: 'unset !important', + background: selectedCell(this._props) ? 'lightgray' : undefined, }), placeholder: base => ({ ...base, @@ -518,6 +525,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC ...base, height: 20, transform: 'scale(0.75)', + border: 'unset !important', }), menuPortal: base => ({ ...base, diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 7fb83571f..b08ed84b7 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -13,11 +13,11 @@ import { undoable } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; +import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import './LabelBox.scss'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextMenu } from './formattedText/RichTextMenu'; -import { DocumentView } from './DocumentView'; @observer export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -103,11 +103,11 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { }; if (r) { if (!r.offsetHeight || !r.offsetWidth) { - //console.log("CAN'T FIT TO EMPTY BOX"); - this._timeout && clearTimeout(this._timeout); + r.style.opacity = '0'; this._timeout = setTimeout(() => this.fitTextToBox(r)); return textfitParams; } + r.style.opacity = '1'; r.style.whiteSpace = ''; // textfit sets to nowrap if not multiline, but doesn't reeset if it becomes multiline r.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align']); // textfit doesn't reset textAlign if it has been set to center, so we just set it to what we want r.firstChild instanceof HTMLElement && (r.firstChild.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align'])); @@ -222,7 +222,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { FormattedTextBox.LiveTextUndo = undefined; }} dangerouslySetInnerHTML={{ - __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title.startsWith('#') ? null : (this.Title ?? '')}</span>`, + __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title?.startsWith('#') ? '' : (this.Title ?? '')}</span>`, }} contentEditable={this._props.onClickScript?.() ? undefined : true} ref={r => { @@ -239,7 +239,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { if (this.Title) { this.resetCursor(); } - } + } else this._timeout && clearTimeout(this._timeout); }} /> </div> diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 78bbb520e..2e2e1d41c 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -3,7 +3,7 @@ .dashFieldView-active, .dashFieldView { position: relative; - display: inline-flex; + display: contents; align-items: center; .dashFieldView-enumerables { @@ -33,8 +33,11 @@ margin-left: 2px; margin-right: 5px; padding-left: 2px; - display: inline-block; - background-color: rgba(155, 155, 155, 0.24); + font-size: smaller; + display: contents; + > div { + background-color: rgba(155, 155, 155, 0.24); + } span { user-select: all; min-width: 100%; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index d6d15054b..aa2829aaf 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,8 +1,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import { Node } from 'prosemirror-model'; import { NodeSelection } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; @@ -13,6 +15,7 @@ import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast } from '../../../../fields/Types'; import { emptyFunction } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; +import { DocumentOptions, FInfo } from '../../../documents/Documents'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { Transform } from '../../../util/Transform'; import { undoable, undoBatch } from '../../../util/UndoManager'; @@ -23,9 +26,6 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../OpenWhere'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; -import { Node } from 'prosemirror-model'; -import { EditorView } from 'prosemirror-view'; -import { DocumentOptions, FInfo } from '../../../documents/Documents'; @observer export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -99,7 +99,6 @@ interface IDashFieldViewInternal { width: number; height: number; editable: boolean; - nodeSelected: () => boolean; node: Node; getPos: () => number; unclickable: () => boolean; @@ -112,7 +111,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi _fieldKey: string; _fieldRef = React.createRef<HTMLDivElement>(); @observable _dashDoc: Doc | undefined = undefined; - @observable _expanded = this._props.nodeSelected(); + @observable _expanded = false; constructor(props: IDashFieldViewInternal) { super(props); @@ -140,7 +139,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi componentWillUnmount() { this._reactionDisposer?.(); } - isRowActive = () => (this._props.nodeSelected() || this._expanded) && this._props.editable; + isRowActive = () => this._props.tbox._props.isContentActive() && this._props.editable; finishEdit = action(() => { if (this._expanded) { this._expanded = false; @@ -149,7 +148,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.()); } }); - selectedCells = () => (this._dashDoc ? [this._dashDoc] : undefined); + selectedCells = () => (this._dashDoc && this._expanded ? [this._dashDoc] : undefined); columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey finfo = (fieldKey: string) => (new DocumentOptions() as Record<string, FInfo>)[fieldKey]; @@ -158,15 +157,16 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi @computed get fieldValueContent() { return !this._dashDoc ? null : ( <div + className="dashFieldView-fieldSpan" onPointerDown={action(() => { this._expanded = !this._props.editable ? false : !this._expanded; - })} - style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}> + })}> <SchemaTableCell Document={this._dashDoc} col={0} deselectCell={emptyFunction} - selectCell={emptyFunction} + selectCell={() => (this._expanded ? true : undefined)} + autoFocus={true} maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth} columnWidth={returnZero} selectedCells={this.selectedCells} @@ -184,11 +184,10 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi getFinfo={this.finfo} setColumnValues={returnFalse} allowCRs - oneLine={!this._expanded && !this._props.nodeSelected()} + oneLine={!this._expanded} finishEdit={this.finishEdit} transform={Transform.Identity} menuTarget={null} - autoFocus rootSelected={this._props.tbox._props.rootSelected} /> </div> @@ -233,7 +232,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi } @computed get _hideValue() { - return this._props.hideValue && !this._props.nodeSelected(); + return this._props.hideValue; } // clicking on the label creates a pivot view collection of all documents @@ -255,7 +254,6 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi }; @computed get values() { - if (this._props.nodeSelected()) return []; const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []); return vals.strings.map(facet => ({ value: facet, label: facet })); @@ -297,8 +295,6 @@ export class DashFieldView { node: Node; tbox: FormattedTextBox; getpos: () => number | undefined; - @observable _nodeSelected = false; - NodeSelected = () => this._nodeSelected; unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some(m => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); constructor(node: Node, view: EditorView, getPos: () => number | undefined, tbox: FormattedTextBox) { @@ -311,26 +307,13 @@ export class DashFieldView { this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; this.dom.style.position = 'relative'; - this.dom.style.display = 'inline-block'; + this.dom.style.display = 'inline-flex'; this.dom.onkeypress = function (e: KeyboardEvent) { e.stopPropagation(); }; - this.dom.onkeydown = (e: KeyboardEvent) => { + this.dom.onkeydown = action((e: KeyboardEvent) => { e.stopPropagation(); - if (e.key === 'Tab') { - e.preventDefault(); - const editor = tbox.EditorView; - if (editor) { - const { state } = editor; - for (let i = getPosition() + 1; i < state.doc.content.size; i++) { - if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) { - editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i)))); - return; - } - } - } - } - }; + }); this.dom.onkeyup = function (e: KeyboardEvent) { e.stopPropagation(); }; @@ -351,7 +334,6 @@ export class DashFieldView { hideKey={node.attrs.hideKey} hideValue={node.attrs.hideValue} editable={node.attrs.editable} - nodeSelected={this.NodeSelected} tbox={tbox} /> ); @@ -365,19 +347,6 @@ export class DashFieldView { } }); } - deselectNode() { - runInAction(() => { - this._nodeSelected = false; - }); - this.dom.classList.remove('ProseMirror-selectednode'); - } - selectNode() { - setTimeout( - action(() => { - this._nodeSelected = true; - }), - 100 - ); - this.dom.classList.add('ProseMirror-selectednode'); - } + deselectNode() {} + selectNode() {} } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 38817ac6d..7b24125e7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti import { EditorView, NodeViewConstructor } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, imageUrlToBase64, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; @@ -26,7 +26,7 @@ import { ComputedField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { emptyFunction, numberRange, unimplementedFunction, Utils } from '../../../../Utils'; -import { gptAPICall, GPTCallType, gptImageLabel } from '../../../apis/gpt/GPT'; +import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -226,18 +226,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return anchor; }; - selectionToFlashcards = async () => { - const queryText = window.getSelection()?.toString() ?? ''; - try { - if (queryText) { - const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); - AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)); - } - } catch (err) { - console.error(err); - } - }; - @action setupAnchorMenu = () => { AnchorMenu.Instance.Status = 'marquee'; @@ -1012,31 +1000,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); }; - findImageTags = async () => { - const c = this.ProseRef?.getElementsByTagName('img'); - if (c) { - for (const i of c) { - // console.log(canvas.toDataURL()); - // canvas.style.zIndex = '2000000'; - // document.body.appendChild(canvas); - if (i.className !== 'ProseMirror-separator') this.getImageDesc(i.src); - } - } - }; - - getImageDesc = async (u: string) => { - try { - const hrefBase64 = await imageUrlToBase64(u); - const response = await gptImageLabel( - hrefBase64, - 'Make flashcards out of this text and image with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' + (this.dataDoc.text as RichTextField)?.Text - ); - AnchorMenu.Instance.transferToFlashcard(response || 'Something went wrong', NumCast(this.dataDoc['x']), NumCast(this.dataDoc['y'])); - } catch (error) { - console.log('Error', error); - } - }; - animateRes = (resIndex: number, newText: string) => { if (resIndex < newText.length) { const marks = this.EditorView?.state.storedMarks ?? []; @@ -2113,7 +2076,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); const scrSize = (which: number, view = this._props.docViewPath().slice(-which)[0]) => - [view._props.PanelWidth() / view.screenToLocalScale(), view._props.PanelHeight() / view.screenToLocalScale()]; // prettier-ignore + [view?._props.PanelWidth() /(view?.screenToLocalScale()??1), view?._props.PanelHeight() / (view?.screenToLocalScale()??1)]; // prettier-ignore const scrMargin = [Math.max(0, (scrSize(2)[0] - scrSize(1)[0]) / 2), Math.max(0, (scrSize(2)[1] - scrSize(1)[1]) / 2)]; const paddingX = Math.max(NumCast(this.layoutDoc._xMargin), this._props.xPadding ?? 0, 0, ((this._props.screenXPadding?.() ?? 0) - scrMargin[0]) * this.ScreenToLocalBoxXf().Scale); const paddingY = Math.max(NumCast(this.layoutDoc._yMargin), 0, ((this._props.yPadding ?? 0) - scrMargin[1]) * this.ScreenToLocalBoxXf().Scale); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index c332c592b..f3ec6cc9d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -319,10 +319,10 @@ export class RichTextRules { }), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document - // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value] - // [@{this,doctitle,}.fieldKey] + // @{this,doctitle,}.fieldKey{:,=,:=,=:=}value + // @{this,doctitle,}.fieldKey new InputRule( - /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/, + /(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\s/, (state, match, start, end) => { const docTitle = match[1].substring(1).replace(/\.$/, ''); const fieldKey = match[2]; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 9ab5fb1bd..f818c6e20 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -210,6 +210,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } componentDidMount() { + this._props.setContentViewBox?.(this); this._disposers.pause = reaction( () => SnappingManager.UserPanned, () => this.pauseAutoPres() diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index b0732c281..31cd1603f 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -52,7 +52,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { // the presentation view that renders this slide @computed get presBoxView() { - return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as PresBox; + return this.DocumentView?.() + .containerViewPath?.() + .slice() + .reverse() + .find(dv => dv?.ComponentView instanceof PresBox)?.ComponentView as PresBox; } // the presentation view document that renders this slide @@ -235,9 +239,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { e.clientY, undefined, action(() => { - Array.from(classesToRestore).forEach(pair => { - pair[0].className = pair[1]; - }); + Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1])); this._dragging = false; }) ); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index eaaeb8d97..eb6516403 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,8 +10,8 @@ import { Doc, Opt } from '../../../fields/Doc'; import { SettingsManager } from '../../util/SettingsManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; -import { ComparisonBox } from '../nodes/ComparisonBox'; import { DocumentView } from '../nodes/DocumentView'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; @@ -68,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; @@ -93,22 +92,9 @@ 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; - }) - ); + gptAskAboutSelection = () => { + GPTPopup.Instance.askAIAboutSelection(this._selectedText); + AnchorMenu.Instance.fadeOut(true); }; pointerDown = (e: React.PointerEvent) => { @@ -187,15 +173,15 @@ 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. */} - {this.gptFlashcards === unimplementedFunction ? null : <IconButton tooltip="Create flashcards" onPointerDown={this.gptFlashcards} icon={<FontAwesomeIcon icon="layer-group" 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 tooltip="Click to Record Annotation" // diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index c8903e09f..18441f76e 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -15,12 +15,8 @@ $headingHeight: 32px; 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 +25,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 { @@ -65,7 +63,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 { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 4dc45e6a0..67213382d 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; @@ -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 @@ -536,35 +569,80 @@ 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={StrCast(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={StrCast(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={StrCast(SettingsManager.userVariantColor)} + type={Type.TERT} + /> + <Button + tooltip="Create Flashcards" // + text="Create Flashcards" + onClick={this.createFlashcards} + color={StrCast(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} /> </div> diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a50526223..83b3dffe5 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -28,7 +28,6 @@ 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'; @@ -392,23 +391,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { } }; - /** - * Create a flashcard pile based on the selected text of a pdf. - */ - gptPDFFlashcards = async () => { - const queryText = this._selectionText; - runInAction(() => (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); - } - runInAction(() => (this._loading = false)); - }; - @action finishMarquee = (/* x?: number, y?: number */) => { AnchorMenu.Instance.makeLabels = unimplementedFunction; @@ -430,7 +412,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') { @@ -442,7 +424,6 @@ 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; }; diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts index f0a851ce6..5db201c72 100644 --- a/src/fields/DateField.ts +++ b/src/fields/DateField.ts @@ -39,6 +39,6 @@ export class DateField extends ObjectField { } // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function d(...dateArgs: any[]) { - return new DateField(new (Date as any)(...dateArgs)); +ScriptingGlobals.add(function d(...dateArgs: ConstructorParameters<typeof Date>) { + return new DateField(new Date(...dateArgs)); }); |