import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnEmptyFilter, returnTrue } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { CreateLinkToActiveAudio, Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { GestureOverlay } from '../GestureOverlay'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView } from '../nodes/DocumentView'; import { OpenWhere } from '../nodes/OpenWhere'; 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 { @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[] = []; @observable private static _query: string = ''; @observable private static _recs: IRecommendation[] = []; @observable private static _bounds: IBounds = emptyBounds; @observable private static _explore: Opt = false; @observable private static _sidebarStatus: Opt = ''; static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt }[] = []; private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate; 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 unknown, this._docFilters][1] as string[]) : [f]))(`cookies:${cookie}:provide`); } } public static AddDocTab = (docsIn: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => { DocumentView.DeselectAll(); const doc = toList(docsIn).lastElement(); return ( doc && NewLightboxView.SetNewLightboxDoc( doc, undefined, [...DocListCast(doc[Doc.LayoutDataKey(doc)]), ...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort( (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) ), layoutTemplate ) ); }; @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 = CreateLinkToActiveAudio(() => doc).lastElement(); DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen'); DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); // DocumentView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); if (doc !== DocumentView.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) => Doc.Links(a).length - Doc.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!); } @action public static Next() { const doc = NewLightboxView._doc!; const target = (NewLightboxView._docTarget = this._future?.pop()); const targetDocView = target && DocumentView.getLightboxDocumentView(target); if (targetDocView && target) { const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); DocCast(l?.link_anchor_2) && (DocCast(l!.link_anchor_2)!.backgroundColor = 'lightgreen'); DocumentView.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; const lightboxDoc = DocumentView.LightboxDoc(); if (lightboxDoc && saved) { lightboxDoc._freeform_panX = saved.panX; lightboxDoc._freeform_panY = saved.panY; lightboxDoc._freeform_scale = saved.scale; 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() ?? { doc: undefined, target: undefined }; const docView = DocumentView.getLightboxDocumentView(target || doc); if (docView) { NewLightboxView._docTarget = target; target && DocumentView.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 public static SetKeywords(kw: string[]) { this._keywords = kw; } @computed public static get Keywords() { return this._keywords; } @computed public static get LightboxDoc() { return this._doc; } // query @action public static SetQuery(query: string) { this._query = query; } @computed public static get Query() { return this._query; } // keywords @action public static SetRecs(recs: IRecommendation[]) { this._recs = recs; } @computed public static get Recs() { return this._recs; } // bounds @action public static SetBounds(bounds: IBounds) { this._bounds = bounds; } @computed public static get Bounds() { return this._bounds; } // newLightbox sidebar status @action public static SetSidebarStatus(sidebarStatus: Opt) { this._sidebarStatus = sidebarStatus; } // explore @action public static SetExploreMode(status: Opt) { this._explore = status; } addDocTab = NewLightboxView.AddDocTab; @computed public static get ExploreMode() { return this._explore; } @computed public static get SidebarStatus() { return this._sidebarStatus; } @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]); } @computed get documentView() { const lightboxDoc = DocumentView.LightboxDoc(); if (!lightboxDoc) return null; return ( { NewLightboxView._docView = r !== null ? r : undefined; })} Document={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={returnEmptyDocViewList} childFilters={this.docFilters} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} addDocument={undefined} removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.addDocTab} pinToPres={DocumentView.PinDoc} focus={emptyFunction} /> ); } newLightboxWidth = () => this.props.PanelWidth - 420; newLightboxHeight = () => this.props.PanelHeight - 140; newLightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1); docFilters = () => NewLightboxView._docFilters || []; render() { const newLightboxHeaderHeight = 100; let downx = 0; let downy = 0; return !DocumentView.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', () => (DocumentView.LightboxDoc() /* && this.props.future()?.length */ ? '' : 'none'), e => { e.stopPropagation(); this.props.stepInto(); }, '' ); } }