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 } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorResult } from 'react-color'; import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { SettingsManager } from '../../util/SettingsManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { DocumentView } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; @observer export class AnchorMenu extends AntimodeMenu { // eslint-disable-next-line no-use-before-define static Instance: AnchorMenu; private _disposer: IReactionDisposer | undefined; private _commentRef = React.createRef(); private _cropRef = React.createRef(); @observable private _loading = false; constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); AnchorMenu.Instance = this; AnchorMenu.Instance._canFade = false; } @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)'; @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions @observable private _selectedText: string = ''; @observable private _x: number = 0; @observable private _y: number = 0; @observable private _isLoading: boolean = false; @action public setSelectedText = (txt: string) => { this._selectedText = txt.trim(); }; @action public setLocation = (x: number, y: number) => { this._x = x; this._y = y; }; @computed public get selectedText() { return this._selectedText; } public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string) => void = emptyFunction; public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = emptyFunction; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; public MakeTargetToggle: () => void = unimplementedFunction; public ShowTargetTrail: () => void = unimplementedFunction; public IsTargetToggler: () => boolean = returnFalse; public makeLabels: () => void = unimplementedFunction; public marqueeWidth = 0; public marqueeHeight = 0; public get Active() { return this._left > 0; } public AddDrawingAnnotation: (doc: Doc) => void = unimplementedFunction; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; componentWillUnmount() { this._disposer?.(); } componentDidMount() { this._disposer = reaction( () => DocumentView.Selected().slice(), () => AnchorMenu.Instance.fadeOut(true) ); } /** * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ gptAskAboutSelection = () => { GPTPopup.Instance.askAIAboutSelection(this._selectedText); AnchorMenu.Instance.fadeOut(true); }; pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, (moveEv: PointerEvent) => { this.StartDrag(moveEv, this._commentRef.current!); return true; }, returnFalse, clickEv => this.OnClick?.(clickEv) ); }; audioDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, clickEv => this.OnAudio?.(clickEv)); }; cropDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, e, (moveEv: PointerEvent) => { this.StartCropDrag(moveEv, this._cropRef.current!); return true; }, returnFalse, clickev => this.OnCrop?.(clickev) ); }; @action highlightClicked = () => { this.Highlight(this.highlightColor); AnchorMenu.Instance.fadeOut(true); }; @computed get highlighter() { return ( } tooltip="Click to Highlight" onClick={this.highlightClicked} colorPicker={this.highlightColor} color={SettingsManager.userColor} /> ); } @action changeHighlightColor = (color: string) => { const col: ColorResult = { hex: color, hsl: { a: 0, h: 0, s: 0, l: 0 }, rgb: { a: 0, r: 0, b: 0, g: 0 }, }; this.highlightColor = ClientUtils.colorString(col); }; render() { const buttons = this.Status === 'marquee' ? ( <> {this.highlighter}
} color={SettingsManager.userColor} />
{/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */} {this._selectedText && ( } color={SettingsManager.userColor} /> )} {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */} {this.makeLabels === unimplementedFunction ? null : } color={SettingsManager.userColor} />} {this._selectedText && RichTextMenu.Instance?.createLinkButton()} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( } color={SettingsManager.userColor} /> )} } popup={} color={SettingsManager.userColor} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
} color={SettingsManager.userColor} />
)} ) : ( <> {this.Delete !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.PinToPres !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.ShowTargetTrail !== returnFalse && ( } color={SettingsManager.userColor} /> )} {this.IsTargetToggler !== returnFalse && ( } color={SettingsManager.userColor} /> )} ); return this.getElement(buttons); } }