import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import ReactLoading from 'react-loading'; import { Doc, DocListCast } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import './OverlayView.scss'; import { DefaultStyleProvider } from './StyleProvider'; export type OverlayDisposer = () => void; export type OverlayElementOptions = { x: number; y: number; width?: number; height?: number; title?: string; }; export interface OverlayWindowProps { children: JSX.Element; overlayOptions: OverlayElementOptions; onClick: () => void; } @observer export class OverlayWindow extends React.Component { @observable x: number; @observable y: number; @observable width: number; @observable height: number; constructor(props: OverlayWindowProps) { super(props); const opts = props.overlayOptions; this.x = opts.x; this.y = opts.y; this.width = opts.width || 200; this.height = opts.height || 200; } onPointerDown = (_: React.PointerEvent) => { document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); document.addEventListener('pointermove', this.onPointerMove); document.addEventListener('pointerup', this.onPointerUp); }; onResizerPointerDown = (_: React.PointerEvent) => { 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 = (e: PointerEvent) => { document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); }; onResizerPointerUp = (e: PointerEvent) => { document.removeEventListener('pointermove', this.onResizerPointerMove); document.removeEventListener('pointerup', this.onResizerPointerUp); }; render() { return LightboxView.LightboxDoc ? null : (
{this.props.overlayOptions.title || 'Untitled'}
{this.props.children}
); } } @observer export class OverlayView extends React.Component { public static Instance: OverlayView; @observable.shallow private _elements: JSX.Element[] = []; constructor(props: any) { super(props); if (!OverlayView.Instance) { OverlayView.Instance = this; } } @action addElement(ele: JSX.Element, options: OverlayElementOptions): OverlayDisposer { const remove = action(() => { const index = this._elements.indexOf(ele); if (index !== -1) this._elements.splice(index, 1); }); ele = (
{ele}
); this._elements.push(ele); return remove; } @action addWindow(contents: JSX.Element, options: OverlayElementOptions): OverlayDisposer { const remove = action(() => { const index = this._elements.indexOf(contents); if (index !== -1) this._elements.splice(index, 1); }); contents = ( {contents} ); this._elements.push(contents); return remove; } removeOverlayDoc = (doc: Doc | Doc[]) => { (doc instanceof Doc ? [doc] : doc).forEach(Doc.RemFromMyOverlay); return true; }; docScreenToLocalXf = computedFn( function docScreenToLocalXf(this: any, doc: Doc) { return () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1); }.bind(this) ); @computed get overlayDocs() { return DocListCast(Doc.MyOverlayDocs?.data) .filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES) .map(d => { let offsetx = 0, offsety = 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 = 'move'; dragData.removeDocument = this.removeOverlayDoc; dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { return 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}
); } }