diff options
Diffstat (limited to 'src/client/views/LightboxView.tsx')
| -rw-r--r-- | src/client/views/LightboxView.tsx | 226 |
1 files changed, 191 insertions, 35 deletions
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 4e9491ec6..af07ead97 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -1,11 +1,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable, computed } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; -import { Doc, Opt } from '../../fields/Doc'; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { 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 { Transform } from '../util/Transform'; +import { TabDocView } from './collections/TabDocView'; import "./LightboxView.scss"; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider } from './StyleProvider'; @@ -18,88 +24,238 @@ interface LightboxViewProps { @observer export class LightboxView extends React.Component<LightboxViewProps> { - @observable public static LightboxDoc: Opt<Doc>; - public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); } - public static LightboxHistory: (Opt<Doc>)[] = []; - public static LightboxFuture: (Opt<Doc>)[] = []; - public static LightboxDocView = React.createRef<DocumentView>(); + @computed public static get LightboxDoc() { return this._doc; } + @observable private static _doc: Opt<Doc>; + @observable private static _docTarget: Opt<Doc>; + @observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target + @observable private static _docFilters: string[] = []; // filters + private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>; + private static _history: Opt<{ doc: Doc, target?: Doc }[]> = []; + private static _future: Opt<Doc[]> = []; + private static _docView: Opt<DocumentView>; + static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = []; + @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[]) { + if (!doc) { + this._docFilters && (this._docFilters.length = 0); + if (this.LightboxDoc) { + this.LightboxDoc._panX = this._savedState?.panX; + this.LightboxDoc._panY = this._savedState?.panY; + this.LightboxDoc._viewScale = this._savedState?.scale; + this.LightboxDoc._viewTransition = this._savedState?.transition; + } + this._future = this._history = []; + } else { + this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }]; + this._savedState = { + panX: Cast(doc._panX, "number", null), + panY: Cast(doc._panY, "number", null), + scale: Cast(doc._viewScale, "number", null), + transition: Cast(doc._viewTransition, "string", null) + }; + } + if (future) { + this._future = 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._docTarget = target || doc; + this._tourMap = DocListCast(doc?.links).map(link => { + const opp = LinkManager.getOppositeAnchor(link, doc!); + return opp?.TourMap ? opp : undefined; + }).filter(m => m).map(m => m!); + + 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<number>, icon: string, display: () => string, click: (e: React.MouseEvent) => void) => { + navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => { return <div className="lightboxView-navBtn-frame" style={{ display: display(), left, - width: Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]) + width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), + bottom }}> - <div className="lightboxView-navBtn" style={{ top: this.props.PanelHeight / 2 - 12.50 }} + <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? "red" : "white", background: color ? "white" : undefined }} onClick={click}> + <div style={{ height: 10 }}>{color}</div> <FontAwesomeIcon icon={icon as any} size="3x" /> </div> </div>; } + public static GetSavedState(doc: Doc) { + return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined; + } + public static SetDocFilter(filter: string) { + if (this.LightboxDoc && filter) { + this._docFilters = [`cookies:${filter}:match`]; + } + } + public static AddDocTab = (doc: Doc, location: string) => { + SelectionManager.DeselectAll(); + return LightboxView.SetLightboxDoc(doc, undefined, + [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), + ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]), + ...(LightboxView._future ?? []) + ] + .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))); + } + docFilters = () => LightboxView._docFilters || []; + addDocTab = LightboxView.AddDocTab; + @action + stepForward = () => { + const doc = LightboxView._doc!; + const target = LightboxView._docTarget = LightboxView._future?.pop(); + const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); + if (docView && target) { + docView.focus(target, { willZoom: true, scale: 0.9 }); + if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target: LightboxView._docTarget }); + } else { + if (!target && LightboxView.path.length) { + const saved = LightboxView._savedState; + if (LightboxView.LightboxDoc) { + LightboxView.LightboxDoc._panX = saved?.panX; + LightboxView.LightboxDoc._panY = saved?.panY; + LightboxView.LightboxDoc._viewScale = saved?.scale; + LightboxView.LightboxDoc._viewTransition = saved?.transition; + } + 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); + } + } + LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => { + const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); + return opp?.TourMap ? opp : undefined; + }).filter(m => m).map(m => m!); + } + @action + stepBackward = () => { + const previous = LightboxView._history?.pop(); + if (!previous || !LightboxView._history?.length) { + LightboxView.SetLightboxDoc(undefined); + return; + } + const { doc, target } = LightboxView._history?.lastElement()!; + const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); + if (docView && target) { + LightboxView._doc = doc; + LightboxView._docTarget = target || doc; + if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc); + docView.focus(target, { willZoom: true, scale: 0.9 }); + } else { + LightboxView._doc = doc; + LightboxView._docTarget = target || doc; + } + LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => { + const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); + return opp?.TourMap ? opp : undefined; + }).filter(m => m).map(m => m!); + } + @action + stepInto = () => { + LightboxView.path.push({ + doc: LightboxView.LightboxDoc, + target: LightboxView._docTarget, + future: LightboxView._future, + history: LightboxView._history, + saved: LightboxView._savedState + }); + const tours = LightboxView._tourMap; + if (tours && tours.length) { + const fieldKey = Doc.LayoutFieldKey(tours[0]); + LightboxView._future?.push(...DocListCast(tours[0][fieldKey]).reverse()); + } else { + const coll = LightboxView._docTarget; + if (coll) { + const fieldKey = Doc.LayoutFieldKey(coll); + LightboxView.SetLightboxDoc(coll, undefined, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]); + TabDocView.PinDoc(coll, { hidePresBox: true }); + } + } + setTimeout(() => this.stepForward()); + } + fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc; render() { - if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc); let downx = 0, downy = 0; return !LightboxView.LightboxDoc ? (null) : <div className="lightboxView-frame" onPointerDown={e => { downx = e.clientX; downy = e.clientY; }} - onClick={action(e => { + onClick={e => { if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) { - LightboxView.LightboxHistory = []; - LightboxView.LightboxFuture = []; - LightboxView.LightboxDoc = undefined; + LightboxView.SetLightboxDoc(undefined); } - })} > + }} > <div className="lightboxView-contents" style={{ left: this.leftBorder, top: this.topBorder, width: this.lightboxWidth(), height: this.lightboxHeight() }}> - <DocumentView ref={LightboxView.LightboxDocView} + <DocumentView ref={action((r: DocumentView | null) => { + LightboxView._docView = r !== null ? r : undefined; + setTimeout(action(() => { + const vals = r?.ComponentView?.freeformData?.(); + if (vals && r) { + r.layoutDoc._panX = vals.panX; + r.layoutDoc._panY = vals.panY; + r.layoutDoc._viewScale = vals.scale; + } + r && (LightboxView._docTarget = undefined); + })); + })} Document={LightboxView.LightboxDoc} DataDoc={undefined} addDocument={undefined} - addDocTab={returnFalse} - pinToPres={emptyFunction} + fitContentsToDoc={this.fitToBox} + addDocTab={this.addDocTab} + pinToPres={TabDocView.PinDoc} rootSelected={returnTrue} docViewPath={returnEmptyDoclist} + docFilters={this.docFilters} removeDocument={undefined} styleProvider={DefaultStyleProvider} layerProvider={returnTrue} ScreenToLocalTransform={this.lightboxScreenToLocal} PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} - focus={emptyFunction} + focus={DocUtils.DefaultFocus} parentActive={returnTrue} whenActiveChanged={emptyFunction} bringToFront={emptyFunction} - docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} renderDepth={0} /> </div> - {this.navBtn(undefined, "chevron-left", - () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none", - action(e => { + {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left", + () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => { e.stopPropagation(); - const popped = LightboxView.LightboxHistory.pop(); - if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxFuture.lastElement()) LightboxView.LightboxFuture.push(popped); - LightboxView.LightboxDoc = LightboxView.LightboxHistory.lastElement(); - }))} - {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right", - () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none", - action(e => { + this.stepBackward(); + })} + {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.50, "chevron-right", + () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => { e.stopPropagation(); - LightboxView.LightboxDoc = LightboxView.LightboxFuture.pop(); - }))} - + this.stepForward(); + })} + {this.navBtn("50%", 0, 0, "chevron-down", + () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => { + e.stopPropagation(); + this.stepInto(); + }, + StrCast(LightboxView._tourMap?.lastElement()?.TourMap) + )} </div>; } }
\ No newline at end of file |
