import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils'; import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { Cast, NumCast } from '../../fields/Types'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; import { GestureOverlay } from './GestureOverlay'; import './LightboxView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { TabDocView } from './collections/TabDocView'; import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; interface LightboxViewProps { PanelWidth: number; PanelHeight: number; maxBorder: number[]; } const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_scrollTop', 'layout_fieldKey']; type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore @observer export class LightboxView extends ObservableReactComponent { /** * Determines whether a DocumentView is descendant of the lightbox view * @param view * @returns true if a DocumentView is descendant of the lightbox view */ public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView && (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore static Instance: LightboxView; private _path: { doc: Opt; // target: Opt; history: { doc: Doc; target?: Doc }[]; future: Doc[]; saved: LightboxSavedState; }[] = []; private _savedState: LightboxSavedState = {}; private _history: { doc: Doc; target?: Doc }[] = []; @observable private _future: Doc[] = []; @observable private _layoutTemplate: Opt = undefined; @observable private _layoutTemplateString: Opt = undefined; @observable private _doc: Opt = undefined; @observable private _docTarget: Opt = undefined; @observable private _docView: Opt = undefined; @computed get leftBorder() { return Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]); } // prettier-ignore @computed get topBorder() { return Math.min(this._props.PanelHeight / 4, this._props.maxBorder[1]); } // prettier-ignore constructor(props: any) { super(props); makeObservable(this); LightboxView.Instance = this; } @action public SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) { const lightDoc = this._doc; lightDoc && lightDoc !== doc && savedKeys.forEach(key => (lightDoc[key] = this._savedState[key])); this._savedState = {}; if (doc) { lightDoc !== doc && savedKeys.map(key => (this._savedState[key] = Doc.Get(doc, key, true))); const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); this._history.push({ doc, target }); } else { this._future = []; this._history = []; Doc.ActiveTool = InkTool.None; SnappingManager.SetExploreMode(false); } SelectionManager.DeselectAll(); if (future) { this._future.push( ...(this._doc ? [this._doc] : []), ...future .slice() .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)) .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length) ); } this._doc = doc; this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined; if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) { doc.layout_fieldKey = layoutTemplate; } this._docTarget = target ?? doc; return true; } public AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => this.SetLightboxDoc( doc, undefined, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort( (a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) ), layoutTemplate ); @action next = () => { const lightDoc = this._doc; if (!lightDoc) return; const target = (this._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.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target }); } else if (!target && this._path.length) { savedKeys.forEach(key => (lightDoc[key] = this._savedState[key])); this._path.pop(); } else { this.SetLightboxDoc(target); } }; @action previous = () => { const previous = this._history.pop(); if (!previous || !this._history.length) { this.SetLightboxDoc(undefined); return; } const { doc, target } = this._history.lastElement(); const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { this._docTarget = target; target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); } else { this.SetLightboxDoc(doc, target); } if (this._future.lastElement() !== previous.target || previous.doc) this._future.push(previous.target || previous.doc); }; @action stepInto = () => { this._path.push({ doc: this._doc, target: this._docTarget, future: this._future, history: this._history, saved: this._savedState, }); if (this._docTarget) { const fieldKey = Doc.LayoutFieldKey(this._docTarget); const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])]; const links = LinkManager.Links(this._docTarget) .map(link => LinkManager.getOppositeAnchor(link, this._docTarget!)!) .filter(doc => doc); this.SetLightboxDoc(this._docTarget, undefined, contents.length ? contents : links); } }; downloadDoc = () => { const lightDoc = this._docTarget ?? this._doc; if (lightDoc) { Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightDoc); CollectionDockingView.AddSplit(lightDoc, OpenWhereMod.none); this.SetLightboxDoc(undefined); } }; toggleFitWidth = () => this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth); togglePen = () => (Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen); toggleExplore = () => SnappingManager.SetExploreMode(!SnappingManager.ExploreMode); lightboxDoc = () => this._doc; lightboxWidth = () => this._props.PanelWidth - this.leftBorder * 2; lightboxHeight = () => this._props.PanelHeight - this.topBorder * 2; lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1); lightboxDocTemplate = () => this._layoutTemplate; future = () => this._future; renderNavBtn = (left: Opt, bottom: Opt, top: number, icon: IconProp, display: any, click: () => void, color?: string) => { return (
{ e.stopPropagation(); click(); }}>
{color}
); }; render() { let downx = 0, downy = 0; const toggleBtn = (classname: string, tooltip: string, toggleBackground: any, icon: IconProp, icon2: IconProp | string, onClick: () => void) => (
} onClick={e => { e.stopPropagation(); runInAction(onClick); }} />
); return !this._doc ? null : (
{ downx = e.clientX; downy = e.clientY; }} onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
(this._docView = r !== null ? r : undefined))} Document={this._doc} PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} LayoutTemplate={this.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} suppressSetHeight={this._doc._layout_fitWidth ? true : false} containerViewPath={returnEmptyDoclist} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.AddDocTab} pinToPres={TabDocView.PinDoc} onBrowseClickScript={DocumentView.exploreMode} focus={emptyFunction} />
{this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length, this.previous)} {this.renderNavBtn( this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]), undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-right', this._doc && this._future.length, this.next, this.future().length.toString() )} {toggleBtn('lightboxView-navBtn', 'toggle reading view', this._doc?._layout_fitWidth, 'book-open', 'book', this.toggleFitWidth)} {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-download', '', this.downloadDoc)} {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Pen, 'pen', '', this.togglePen)} {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
); } } interface LightboxTourBtnProps { navBtn: (left: Opt, bottom: Opt, top: number, icon: IconProp, display: any, click: () => void, color?: string) => JSX.Element; future: () => Opt; stepInto: () => void; lightboxDoc: () => Opt; } @observer export class LightboxTourBtn extends React.Component { render() { return this.props.navBtn('50%', 0, 0, 'chevron-down', this.props.lightboxDoc(), this.props.stepInto, ''); } }