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 { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager, dropActionType } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { LightboxView } from './LightboxView'; import { ObservableReactComponent } from './ObservableReactComponent'; import './OverlayView.scss'; import { DefaultStyleProvider } from './StyleProvider'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; const _global = (window /* browser */ || global) /* node */ as any; 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 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 = (_: 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 ObservableReactComponent<{}> { public static Instance: OverlayView; @observable.shallow _elements: JSX.Element[] = []; constructor(props: any) { super(props); makeObservable(this); if (!OverlayView.Instance) { OverlayView.Instance = this; new _global.ResizeObserver( action((entries: any) => { for (const entry of entries) { Doc.MyOverlayDocs.forEach(doc => { 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(() => { 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 Doc.MyOverlayDocs.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 = dropActionType.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}
); } }