import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Toggle, ToggleType, Type } from 'browndash-components'; 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 { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils'; 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 { 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 { 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; layout_fieldKey: 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 _childFilters: 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._freeform_panX = this._savedState.panX; if (this._savedState.panY !== undefined) this.LightboxDoc._freeform_panY = this._savedState.panY; if (this._savedState.scrollTop !== undefined) this.LightboxDoc._layout_scrollTop = this._savedState.scrollTop; if (this._savedState.scale !== undefined) this.LightboxDoc._freeform_scale = this._savedState.scale; this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey ? this._savedState.layout_fieldKey : undefined; } if (!doc) { this._childFilters && (this._childFilters.length = 0); this._future = this._history = []; Doc.ActiveTool = InkTool.None; DocumentView.ExploreMode = false; } else { const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); CollectionStackedTimeline.CurrentlyPlaying?.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 = { layout_fieldKey: StrCast(doc.layout_fieldKey), panX: Cast(doc.freeform_panX, 'number', null), panY: Cast(doc.freeform_panY, 'number', null), scale: Cast(doc.freeform_scale, 'number', null), scrollTop: Cast(doc.layout_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) => 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 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._childFilters = (f => (this._childFilters ? [this._childFilters.push(f) as any, this._childFilters][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 ); }; childFilters = () => LightboxView._childFilters || []; 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.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); DocumentManager.Instance.showDocument(target, { willZoomCentered: 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._freeform_panX = saved.panX; LightboxView.LightboxDoc._freeform_panY = saved.panY; LightboxView.LightboxDoc._freeform_scale = saved.scale; LightboxView.LightboxDoc._layout_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 && DocumentManager.Instance.showDocument(target, { willZoomCentered: 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 = LinkManager.Links(coll) .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; }} style={{ background: SettingsManager.userBackgroundColor }} 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} childFilters={this.childFilters} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} bringToFront={emptyFunction} onBrowseClick={DocumentView.exploreMode} focus={emptyFunction} />
{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!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth; }} icon={} />
} onClick={e => { const lightdoc = LightboxView._docTarget || LightboxView._doc!; e.stopPropagation(); Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc); CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }} />
} onClick={e => { e.stopPropagation(); Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; }} />
} onClick={action(e => { e.stopPropagation(); DocumentView.ExploreMode = !DocumentView.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(); }, '' ); } }