import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { TabDocView } from './collections/TabDocView'; import { GestureOverlay } from './GestureOverlay'; import './LightboxView.scss'; import { MainView } from './MainView'; import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; interface LightboxViewProps { PanelWidth: number; PanelHeight: number; maxBorder: number[]; } type LightboxSavedState = { panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt; layoutKey: Opt; }; @observer export class LightboxView extends React.Component { @computed public static get LightboxDoc() { return this._doc; } private static LightboxDocTemplate = () => LightboxView._layoutTemplate; @observable private static _layoutTemplate: Opt; @observable private static _layoutTemplateString: Opt; @observable private static _doc: Opt; @observable private static _docTarget: Opt; @observable private static _docFilters: string[] = []; // filters private static _savedState: Opt; private static _history: Opt<{ doc: Doc; target?: Doc }[]> = []; @observable private static _future: Opt = []; @observable private static _docView: Opt; static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt }[] = []; @action public static SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) { if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) { if (this._savedState.panX !== undefined) this.LightboxDoc._panX = this._savedState.panX; if (this._savedState.panY !== undefined) this.LightboxDoc._panY = this._savedState.panY; if (this._savedState.scrollTop !== undefined) this.LightboxDoc._scrollTop = this._savedState.scrollTop; if (this._savedState.scale !== undefined) this.LightboxDoc._viewScale = this._savedState.scale; this.LightboxDoc.layoutKey = this._savedState.layoutKey; } if (!doc) { this._docFilters && (this._docFilters.length = 0); this._future = this._history = []; Doc.ActiveTool = InkTool.None; MainView.Instance._exploreMode = false; } else { if (doc) { const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); } CollectionStackedTimeline.CurrentlyPlaying?.forEach(doc => { DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => { dv.ComponentView?.Pause?.(); }); }); //TabDocView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); if (doc !== LightboxView.LightboxDoc) { this._savedState = { layoutKey: StrCast(doc.layoutKey), panX: Cast(doc.panX, 'number', null), panY: Cast(doc.panY, 'number', null), scale: Cast(doc.viewScale, 'number', null), scrollTop: Cast(doc.scrollTop, 'number', null), }; } } if (future) { this._future = [ ...(this._future ?? []), ...(this.LightboxDoc ? [this.LightboxDoc] : []), ...future .slice() .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)) .sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length), ]; } this._doc = doc; this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined; if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) { doc.layoutKey = layoutTemplate; } this._docTarget = target || doc; return true; } public static IsLightboxDocView(path: DocumentView[]) { return (path ?? []).includes(this._docView!); } @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); } @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); } lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2; lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2; lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1); navBtn = (left: Opt, bottom: Opt, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => { return (
{color}
); }; public static GetSavedState(doc: Doc) { return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined; } // adds a cookie to the lightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie @action public static SetCookie(cookie: string) { if (this.LightboxDoc && cookie) { this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`); } } public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => { SelectionManager.DeselectAll(); return LightboxView.SetLightboxDoc( doc, undefined, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']).filter(anno => anno.annotationOn !== doc), ...(LightboxView._future ?? [])].sort( (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) ), layoutTemplate ); }; docFilters = () => LightboxView._docFilters || []; addDocTab = LightboxView.AddDocTab; @action public static Next() { const doc = LightboxView._doc!; const target = (LightboxView._docTarget = this._future?.pop()); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); targetDocView.focus(target, { originalTarget: target, willPanZoom: true, zoomScale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); } else { if (!target && LightboxView.path.length) { const saved = LightboxView._savedState; if (LightboxView.LightboxDoc && saved) { LightboxView.LightboxDoc._panX = saved.panX; LightboxView.LightboxDoc._panY = saved.panY; LightboxView.LightboxDoc._viewScale = saved.scale; LightboxView.LightboxDoc._scrollTop = saved.scrollTop; } const pop = LightboxView.path.pop(); if (pop) { LightboxView._doc = pop.doc; LightboxView._docTarget = pop.target; LightboxView._future = pop.future; LightboxView._history = pop.history; LightboxView._savedState = pop.saved; } } else { LightboxView.SetLightboxDoc(target); } } } @action public static Previous() { const previous = LightboxView._history?.pop(); if (!previous || !LightboxView._history?.length) { LightboxView.SetLightboxDoc(undefined); return; } const { doc, target } = LightboxView._history?.lastElement(); const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { LightboxView._docTarget = target; target && docView.focus(target, { willPanZoom: true, zoomScale: 0.9 }); } else { LightboxView.SetLightboxDoc(doc, target); } if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc); } @action stepInto = () => { LightboxView.path.push({ doc: LightboxView.LightboxDoc, target: LightboxView._docTarget, future: LightboxView._future, history: LightboxView._history, saved: LightboxView._savedState, }); const coll = LightboxView._docTarget; if (coll) { const fieldKey = Doc.LayoutFieldKey(coll); const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])]; const links = DocListCast(coll.links) .map(link => LinkManager.getOppositeAnchor(link, coll)) .filter(doc => doc) .map(doc => doc!); LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); } }; future = () => LightboxView._future; render() { let downx = 0, downy = 0; return !LightboxView.LightboxDoc ? null : (
{ downx = e.clientX; downy = e.clientY; }} onClick={e => { if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) { LightboxView.SetLightboxDoc(undefined); } }}>
{/* TODO:glr This is where it would go*/} (LightboxView._docView = r !== null ? r : undefined))} Document={LightboxView.LightboxDoc} DataDoc={undefined} PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} LayoutTemplate={LightboxView.LightboxDocTemplate} isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected. isContentActive={returnTrue} styleProvider={DefaultStyleProvider} ScreenToLocalTransform={this.lightboxScreenToLocal} renderDepth={0} rootSelected={returnTrue} docViewPath={returnEmptyDoclist} docFilters={this.docFilters} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} onBrowseClick={MainView.Instance.exploreMode} focus={DocUtils.DefaultFocus} />
{this.navBtn( 0, undefined, this.props.PanelHeight / 2 - 12.5, 'chevron-left', () => (LightboxView.LightboxDoc && LightboxView._history?.length ? '' : 'none'), e => { e.stopPropagation(); LightboxView.Previous(); } )} {this.navBtn( this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.5, 'chevron-right', () => (LightboxView.LightboxDoc && LightboxView._future?.length ? '' : 'none'), e => { e.stopPropagation(); LightboxView.Next(); }, this.future()?.length.toString() )}
{ e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}>
{ e.stopPropagation(); CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}>
{ e.stopPropagation(); Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; }}>
{ e.stopPropagation(); MainView.Instance._exploreMode = !MainView.Instance._exploreMode; })}>
); } } interface LightboxTourBtnProps { navBtn: (left: Opt, bottom: Opt, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element; future: () => Opt; stepInto: () => void; } @observer export class LightboxTourBtn extends React.Component { render() { return this.props.navBtn( '50%', 0, 0, 'chevron-down', () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'), e => { e.stopPropagation(); this.props.stepInto(); }, '' ); } }