import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { LinkManager } from '../../util/LinkManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { TabDocView } from '../collections/TabDocView'; import { DocumentView, OpenWhere } from '../nodes/DocumentView'; import { ExploreView } from './ExploreView'; import { IBounds, emptyBounds } from './ExploreView/utils'; import { NewLightboxHeader } from './Header'; import './NewLightboxView.scss'; import { RecommendationList } from './RecommendationList'; import { IRecommendation } from './components'; enum LightboxStatus { RECOMMENDATIONS = 'recommendations', ANNOTATIONS = 'annotations', NONE = 'none', } interface LightboxViewProps { PanelWidth: number; PanelHeight: number; maxBorder: number[]; } type LightboxSavedState = { panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt; layout_fieldKey: Opt; }; @observer export class NewLightboxView extends React.Component { @computed public static get LightboxDoc() { return this._doc; } private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate; @observable private static _layoutTemplate: Opt = undefined; @observable private static _layoutTemplateString: Opt = undefined; @observable private static _doc: Opt = undefined; @observable private static _docTarget: Opt = undefined; @observable private static _docFilters: string[] = []; // filters private static _savedState: Opt = undefined; private static _history: Opt<{ doc: Doc; target?: Doc }[]> = []; @observable private static _future: Opt = []; @observable private static _docView: Opt = undefined; // keywords @observable private static _keywords: string[] = []; @action public static SetKeywords(kw: string[]) { this._keywords = kw; } @computed public static get Keywords() { return this._keywords; } // query @observable private static _query: string = ''; @action public static SetQuery(query: string) { this._query = query; } @computed public static get Query() { return this._query; } // keywords @observable private static _recs: IRecommendation[] = []; @action public static SetRecs(recs: IRecommendation[]) { this._recs = recs; } @computed public static get Recs() { return this._recs; } // bounds @observable private static _bounds: IBounds = emptyBounds; @action public static SetBounds(bounds: IBounds) { this._bounds = bounds; } @computed public static get Bounds() { return this._bounds; } // explore @observable private static _explore: Opt = false; @action public static SetExploreMode(status: Opt) { this._explore = status; } @computed public static get ExploreMode() { return this._explore; } // newLightbox sidebar status @observable private static _sidebarStatus: Opt = ''; @action public static SetSidebarStatus(sidebarStatus: Opt) { this._sidebarStatus = sidebarStatus; } @computed public static get SidebarStatus() { return this._sidebarStatus; } static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt }[] = []; @action public static SetNewLightboxDoc(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; } if (!doc) { this._docFilters && (this._docFilters.length = 0); this._future = this._history = []; Doc.ActiveTool = InkTool.None; SnappingManager.SetExploreMode(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 IsNewLightboxDocView(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]); } newLightboxWidth = () => this.props.PanelWidth - 420; newLightboxHeight = () => this.props.PanelHeight - 140; newLightboxScreenToLocal = () => 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 newLightbox 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 NewLightboxView.SetNewLightboxDoc( doc, undefined, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort( (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) ), layoutTemplate ); }; docFilters = () => NewLightboxView._docFilters || []; addDocTab = NewLightboxView.AddDocTab; @action public static Next() { const doc = NewLightboxView._doc!; const target = (NewLightboxView._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 (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target }); } else { if (!target && NewLightboxView.path.length) { const saved = NewLightboxView._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 = NewLightboxView.path.pop(); if (pop) { NewLightboxView._doc = pop.doc; NewLightboxView._docTarget = pop.target; NewLightboxView._future = pop.future; NewLightboxView._history = pop.history; NewLightboxView._savedState = pop.saved; } } else { NewLightboxView.SetNewLightboxDoc(target); } } } @action public static Previous() { const previous = NewLightboxView._history?.pop(); if (!previous || !NewLightboxView._history?.length) { NewLightboxView.SetNewLightboxDoc(undefined); return; } const { doc, target } = NewLightboxView._history?.lastElement(); const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { NewLightboxView._docTarget = target; target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); } else { NewLightboxView.SetNewLightboxDoc(doc, target); } if (NewLightboxView._future?.lastElement() !== previous.target || previous.doc) NewLightboxView._future?.push(previous.target || previous.doc); } @action stepInto = () => { NewLightboxView.path.push({ doc: LightboxView.LightboxDoc, target: NewLightboxView._docTarget, future: NewLightboxView._future, history: NewLightboxView._history, saved: NewLightboxView._savedState, }); const coll = NewLightboxView._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!); NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links); } }; @computed get documentView() { if (!LightboxView.LightboxDoc) return null; else return ( (NewLightboxView._docView = r !== null ? r : undefined))} Document={LightboxView.LightboxDoc} PanelWidth={this.newLightboxWidth} PanelHeight={this.newLightboxHeight} LayoutTemplate={NewLightboxView.LightboxDocTemplate} isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected. isContentActive={returnTrue} styleProvider={DefaultStyleProvider} ScreenToLocalTransform={this.newLightboxScreenToLocal} renderDepth={0} containerViewPath={returnEmptyDoclist} childFilters={this.docFilters} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} onBrowseClickScript={DocumentView.exploreMode} focus={emptyFunction} /> ); } future = () => NewLightboxView._future; render() { let newLightboxHeaderHeight = 100; 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) { NewLightboxView.SetNewLightboxDoc(undefined); } }}>
{!NewLightboxView._explore ? (
{this.documentView}
) : (
)}
); } } interface NewLightboxTourBtnProps { 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 NewLightboxTourBtn 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(); }, '' ); } }