import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import ReactLoading from 'react-loading'; import ResizeObserver from 'resize-observer-polyfill'; import { returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../ClientUtils'; import { Utils, emptyFunction } from '../../Utils'; import { Doc, returnEmptyDoclist } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast, toList } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; import { Transform } from '../util/Transform'; import { ObservableReactComponent } from './ObservableReactComponent'; import './OverlayView.scss'; import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import { SnappingManager } from '../util/SnappingManager'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; export type OverlayDisposer = () => void; export type OverlayElementOptions = { x: number; y: number; width?: number; height?: number; title?: string; onClick?: (e: React.MouseEvent) => void; isHidden?: () => boolean; backgroundColor?: string; }; export interface OverlayWindowProps { children: JSX.Element; overlayOptions: OverlayElementOptions; onClick: (e: React.MouseEvent) => void; isHidden?: () => boolean; backgroundColor?: string; } @observer export class OverlayWindow extends ObservableReactComponent { @observable x: number = 0; @observable y: number = 0; @observable width: number = 0; @observable height: number = 0; constructor(props: OverlayWindowProps) { super(props); makeObservable(this); const opts = props.overlayOptions; this.x = opts.x; this.y = opts.y; this.width = opts.width || 200; this.height = opts.height || 200; } onPointerDown = () => { document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); document.addEventListener('pointermove', this.onPointerMove); document.addEventListener('pointerup', this.onPointerUp); }; onResizerPointerDown = () => { document.removeEventListener('pointermove', this.onResizerPointerMove); document.removeEventListener('pointerup', this.onResizerPointerUp); document.addEventListener('pointermove', this.onResizerPointerMove); document.addEventListener('pointerup', this.onResizerPointerUp); }; @action onPointerMove = (e: PointerEvent) => { this.x += e.movementX; this.x = Math.max(Math.min(this.x, window.innerWidth - this.width), 0); this.y += e.movementY; this.y = Math.max(Math.min(this.y, window.innerHeight - this.height), 0); }; @action onResizerPointerMove = (e: PointerEvent) => { this.width += e.movementX; this.width = Math.max(this.width, 30); this.height += e.movementY; this.height = Math.max(this.height, 30); }; onPointerUp = () => { document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); }; onResizerPointerUp = () => { document.removeEventListener('pointermove', this.onResizerPointerMove); document.removeEventListener('pointerup', this.onResizerPointerUp); }; render() { return (
{this._props.overlayOptions.title || 'Untitled'}
{this.props.children}
); } } @observer export class OverlayView extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define public static Instance: OverlayView; @observable.shallow _elements: JSX.Element[] = []; constructor(props: object) { super(props); makeObservable(this); if (!OverlayView.Instance) { OverlayView.Instance = this; this.addWindow(, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', // backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false), }); new ResizeObserver( action(entries => { Array.from(entries).forEach(entry => { Doc.MyOverlayDocs.forEach(docIn => { const doc = docIn; if (NumCast(doc.overlayX) > entry.contentRect.width - 10) { doc.overlayX = entry.contentRect.width - 10; } if (NumCast(doc.overlayY) > entry.contentRect.height - 10) { doc.overlayY = entry.contentRect.height - 10; } }); }); }) ).observe(window.document.body); } } @action addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer { const div = (
{ele}
); this._elements.push(div); return action(() => { const index = this._elements.indexOf(div); if (index !== -1) this._elements.splice(index, 1); }); } @action addWindow(contents: JSX.Element, options: OverlayElementOptions): OverlayDisposer { const remove = action((wincontents: JSX.Element) => { const index = this._elements.indexOf(wincontents); if (index !== -1) this._elements.splice(index, 1); }); const wincontents = ( remove(wincontents))} key={Utils.GenerateGuid()} overlayOptions={options}> {contents} ); this._elements.push(wincontents); return () => remove(wincontents); } removeOverlayDoc = (docs: Doc | Doc[]) => toList(docs).every(Doc.RemFromMyOverlay); docScreenToLocalXf = computedFn((doc: Doc) => () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1)); @computed get overlayDocs() { return Doc.MyOverlayDocs.map(d => { let [offsetx, offsety] = [0, 0]; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped if (e.buttons === 1) { d.overlayX = e.clientX + offsetx; d.overlayY = e.clientY + offsety; } if (e.metaKey) { const dragData = new DragManager.DocumentDragData([d]); dragData.offset = [-offsetx, -offsety]; dragData.dropAction = dropActionType.move; dragData.removeDocument = this.removeOverlayDoc; dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => (dragData.removeDocument?.(doc) ? addDocument(doc) : false); DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]); return true; } return false; }); const onPointerDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false); offsetx = NumCast(d.overlayX) - e.clientX; offsety = NumCast(d.overlayY) - e.clientY; }; return (
); }); } public static ShowSpinner() { return OverlayView.Instance.addElement(, { x: 300, y: 200 }); } render() { return (
{this._elements}
{this.overlayDocs}
); } }