aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/RecordingApi.ts269
-rw-r--r--src/client/util/ReplayMovements.ts208
-rw-r--r--src/client/util/TrackMovements.ts257
3 files changed, 465 insertions, 269 deletions
diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts
deleted file mode 100644
index 021feee9a..000000000
--- a/src/client/util/RecordingApi.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
-import { IReactionDisposer, observable, reaction } from "mobx";
-import { NumCast } from "../../fields/Types";
-import { Doc } from "../../fields/Doc";
-import { VideoBox } from "../views/nodes/VideoBox";
-import { scaleDiverging } from "d3-scale";
-import { Transform } from "./Transform";
-
-type Movement = {
- time: number,
- panX: number,
- panY: number,
- scale: number,
-}
-
-type Presentation = {
- movements: Array<Movement> | null
- meta: Object,
-}
-
-export class RecordingApi {
-
- private static NULL_PRESENTATION: Presentation = {
- movements: null,
- meta: {},
- }
-
- // instance variables
- private currentPresentation: Presentation;
- private isRecording: boolean;
- private absoluteStart: number;
-
-
- // create static instance and getter for global use
- @observable static _instance: RecordingApi;
- public static get Instance(): RecordingApi { return RecordingApi._instance }
- public constructor() {
- // init the global instance
- RecordingApi._instance = this;
-
- // init the instance variables
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
- this.isRecording = false;
- this.absoluteStart = -1;
-
- // used for tracking movements in the view frame
- this.disposeFunc = null;
- this.recordingFFView = null;
-
- // for now, set playFFView
- this.playFFView = null;
- this.timers = null;
- }
-
- // little helper :)
- private get isInitPresenation(): boolean {
- return this.currentPresentation.movements === null
- }
-
- public start = (meta?: Object): Error | undefined => {
- // check if already init a presentation
- if (!this.isInitPresenation) {
- console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.')
- return new Error('[recordingApi.ts] start()')
- }
-
- // update the presentation mode
- Doc.UserDoc().presentationMode = 'recording'
-
- // (1a) get start date for presenation
- const startDate = new Date()
- // (1b) set start timestamp to absolute timestamp
- this.absoluteStart = startDate.getTime()
-
- // (2) assign meta content if it exists
- this.currentPresentation.meta = meta || {}
- // (3) assign start date to currentPresenation
- this.currentPresentation.movements = []
- // (4) set isRecording true to allow trackMovements
- this.isRecording = true
- }
-
- public clear = (): Error | Presentation => {
- // TODO: maybe archive the data?
- if (this.isRecording) {
- console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first')
- return new Error('[recordingApi.ts] clear()')
- }
-
- // update the presentation mode
- Doc.UserDoc().presentationMode = 'none'
- // set the previus recording view to the play view
- this.playFFView = this.recordingFFView
-
- const presCopy = { ...this.currentPresentation }
-
- // clear presenation data
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
- // clear isRecording
- this.isRecording = false
- // clear absoluteStart
- this.absoluteStart = -1
- // clear the disposeFunc
- this.removeRecordingFFView()
-
- return presCopy;
- }
-
- public pause = (): Error | undefined => {
- if (this.isInitPresenation) {
- console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first')
- return new Error('[recordingApi.ts] pause(): no presentation')
- }
- // don't allow track movments
- this.isRecording = false
-
- // set adjust absoluteStart to add the time difference
- const timestamp = new Date().getTime()
- this.absoluteStart = timestamp - this.absoluteStart
- }
-
- public resume = () => {
- this.isRecording = true
- // set absoluteStart to the difference in time
- this.absoluteStart = new Date().getTime() - this.absoluteStart
- }
-
- private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => {
- // ensure we are recording
- if (!this.isRecording) {
- return new Error('[recordingApi.ts] trackMovements()')
- }
- // check to see if the presetation is init
- if (this.isInitPresenation) {
- return new Error('[recordingApi.ts] trackMovements(): no presentation')
- }
-
- // get the time
- const time = new Date().getTime() - this.absoluteStart
- // make new movement object
- const movement: Movement = { time, panX, panY, scale }
-
- // add that movement to the current presentation data's movement array
- this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
- }
-
- // instance variable for the FFView
- private disposeFunc: IReactionDisposer | null;
- private recordingFFView: CollectionFreeFormView | null;
-
- // set the FFView that will be used in a reaction to track the movements
- public setRecordingFFView = (view: CollectionFreeFormView): void => {
- // set the view to the current view
- if (view === this.recordingFFView || view == null) return;
-
- // this.recordingFFView = view;
- // set the reaction to track the movements
- this.disposeFunc = reaction(
- () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }),
- (res) => (res.x !== -1 && res.y !== -1 && this.isRecording) && this.trackMovements(res.x, res.y, res.scale)
- )
-
- // for now, set the most recent recordingFFView to the playFFView
- this.recordingFFView = view;
- }
-
- // call on dispose function to stop tracking movements
- public removeRecordingFFView = (): void => {
- this.disposeFunc?.();
- this.disposeFunc = null;
- }
-
- // TODO: extract this into different class with pause and resume recording
- // TODO: store the FFview with the movements
- private playFFView: CollectionFreeFormView | null;
- private timers: NodeJS.Timeout[] | null;
-
- public setPlayFFView = (view: CollectionFreeFormView): void => {
- this.playFFView = view
- }
-
- // pausing movements will dispose all timers that are planned to replay the movements
- // play movemvents will recreate them when the user resumes the presentation
- public pauseMovements = (): undefined | Error => {
- if (this.playFFView === null) {
- return new Error('[recordingApi.ts] pauseMovements() failed: no view')
- }
-
- if (!this._isPlaying) {
- //return new Error('[recordingApi.ts] pauseMovements() failed: not playing')
- return
- }
- this._isPlaying = false
- // TODO: set userdoc presentMode to browsing
- this.timers?.map(timer => clearTimeout(timer))
-
- // this.videoBox = null;
- }
-
- private videoBox: VideoBox | null = null;
-
- // by calling pause on the VideoBox, the pauseMovements will be called
- public pauseVideoAndMovements = (): boolean => {
- this.videoBox?.Pause()
-
- this.pauseMovements()
- return this.videoBox == null
- }
-
- public _isPlaying = false;
-
- public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => {
- if (presentation.movements === null || this.playFFView === null) {
- return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view')
- }
- if(this._isPlaying) return;
-
- this._isPlaying = true;
- Doc.UserDoc().presentationMode = 'watching';
-
- // TODO: consider this bug at the end of the clip on seek
- this.videoBox = videoBox || null;
-
- // only get the movements that are remaining in the video time left
- const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000)
-
- // helper to replay a movement
- const document = this.playFFView
- let preScale = -1;
- const zoomAndPan = (movement: Movement) => {
- const { panX, panY, scale } = movement;
- (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
- document.Document._panX = panX;
- document.Document._panY = panY;
-
- preScale = scale;
- }
-
- // set the first frame to be at the start of the pres
- zoomAndPan(filteredMovements[0]);
-
- // make timers that will execute each movement at the correct replay time
- this.timers = filteredMovements.map(movement => {
- const timeDiff = movement.time - timeViewed*1000
- return setTimeout(() => {
- // replay the movement
- zoomAndPan(movement)
- // if last movement, presentation is done -> set the instance var
- if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false;
- }, timeDiff)
- })
- }
-
- // Unfinished code for tracing multiple free form views
- // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()
-
- // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
- // pres.set(ffView,
- // reaction(() => ({ x: ffView.panX, y: ffView.panY }),
- // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
- // )
- // }
-
- // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
- // const disposer = pres.get(ffView);
- // disposer?.();
- // pres.delete(ffView)
- // }
-}
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
new file mode 100644
index 000000000..86bc4c5de
--- /dev/null
+++ b/src/client/util/ReplayMovements.ts
@@ -0,0 +1,208 @@
+import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
+import { IReactionDisposer, observable, observe, reaction } from "mobx";
+import { Doc } from "../../fields/Doc";
+import { VideoBox } from "../views/nodes/VideoBox";
+import { DocumentManager } from "./DocumentManager";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocServer } from "../DocServer";
+import { Movement, Presentation } from "./TrackMovements";
+
+export class ReplayMovements {
+ private timers: NodeJS.Timeout[] | null;
+ private videoBoxDisposeFunc: IReactionDisposer | null;
+ private videoBox: VideoBox | null;
+ private isPlaying: boolean;
+
+
+ // create static instance and getter for global use
+ @observable static _instance: ReplayMovements;
+ static get Instance(): ReplayMovements { return ReplayMovements._instance }
+ constructor() {
+ // init the global instance
+ ReplayMovements._instance = this;
+
+ // instance vars for replaying
+ this.timers = null;
+ this.videoBoxDisposeFunc = null;
+ this.videoBox = null;
+ this.isPlaying = false;
+ }
+
+ // pausing movements will dispose all timers that are planned to replay the movements
+ // play movemvents will recreate them when the user resumes the presentation
+ pauseMovements = (): undefined | Error => {
+ if (!this.isPlaying) {
+ // console.warn('[recordingApi.ts] pauseMovements(): already on paused');
+ return;
+ }
+ Doc.UserDoc().presentationMode = 'none';
+
+ this.isPlaying = false
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer))
+ }
+
+ setVideoBox = async (videoBox: VideoBox) => {
+ // console.info('setVideoBox', videoBox);
+ if (this.videoBox !== null) { console.warn('setVideoBox on already videoBox'); }
+ if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox dispose func'); this.videoBoxDisposeFunc(); }
+
+
+ const { presentation } = videoBox;
+ if (presentation == null) { console.warn('setVideoBox on null videoBox presentation'); return; }
+
+ let docIdtoDoc: Map<string, Doc> = new Map();
+ try {
+ docIdtoDoc = await this.loadPresentation(presentation);
+ } catch {
+ console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements');
+ throw 'error loading docs from server';
+ }
+
+
+ this.videoBoxDisposeFunc =
+ reaction(() => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }),
+ ({ playing, timeViewed }) =>
+ playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements()
+ );
+ this.videoBox = videoBox;
+ }
+
+ removeVideoBox = () => {
+ if (this.videoBoxDisposeFunc == null) { console.warn('removeVideoBox on null videoBox'); return; }
+ this.videoBoxDisposeFunc();
+
+ this.videoBox = null;
+ this.videoBoxDisposeFunc = null;
+ }
+
+ // should be called from interacting with the screen
+ pauseFromInteraction = () => {
+ this.videoBox?.Pause();
+
+ this.pauseMovements();
+ }
+
+ loadPresentation = async (presentation: Presentation) => {
+ const { movements } = presentation;
+ if (movements === null) {
+ throw '[recordingApi.ts] followMovements() failed: no presentation data';
+ }
+
+ // generate a set of all unique docIds
+ const docIds = new Set<string>();
+ for (const {docId} of movements) {
+ if (!docIds.has(docId)) docIds.add(docId);
+ }
+
+ const docIdtoDoc = new Map<string, Doc>();
+
+ let refFields = await DocServer.GetRefFields([...docIds.keys()]);
+ for (const docId in refFields) {
+ if (!refFields[docId]) {
+ throw `one field was undefined`;
+ }
+ docIdtoDoc.set(docId, refFields[docId] as Doc);
+ }
+ // console.info('loadPresentation refFields', refFields, docIdtoDoc);
+
+ return docIdtoDoc;
+ }
+
+ // returns undefined if the docView isn't open on the screen
+ getCollectionFFView = (docId: string) => {
+ const isInView = DocumentManager.Instance.getDocumentViewById(docId);
+ if (isInView) { return isInView.ComponentView as CollectionFreeFormView; }
+ }
+
+ // will open the doc in a tab then return the CollectionFFView that holds it
+ openTab = (docId: string, docIdtoDoc: Map<string, Doc>) => {
+ const doc = docIdtoDoc.get(docId);
+ if (doc == undefined) {
+ console.error(`docIdtoDoc did not contain docId ${docId}`)
+ return undefined;
+ }
+ // console.log('openTab', docId, doc);
+ CollectionDockingView.AddSplit(doc, 'right');
+ const docView = DocumentManager.Instance.getDocumentView(doc);
+ // BUG - this returns undefined if the doc is already open
+ return docView?.ComponentView as CollectionFreeFormView;
+ }
+
+ // helper to replay a movement
+ zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => {
+ const { panX, panY, scale } = movement;
+ scale !== 0 && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
+ document.Document._panX = panX;
+ document.Document._panY = panY;
+ }
+
+ getFirstMovements = (movements: Movement[]): Map<string, Movement> => {
+ if (movements === null) return new Map();
+ // generate a set of all unique docIds
+ const docIdtoFirstMove = new Map();
+ for (const move of movements) {
+ const { docId } = move;
+ if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move);
+ }
+ return docIdtoFirstMove;
+ }
+
+ endPlayingPresentation = () => {
+ this.isPlaying = false;
+ Doc.UserDoc().presentationMode = 'none';
+ }
+
+ public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
+ // console.info('playMovements', presentation, timeViewed, docIdtoDoc);
+
+ if (presentation.movements === null || presentation.movements.length === 0) { //|| this.playFFView === null) {
+ return new Error('[recordingApi.ts] followMovements() failed: no presentation data')
+ }
+ if (this.isPlaying) return;
+
+ this.isPlaying = true;
+ Doc.UserDoc().presentationMode = 'watching';
+
+ // only get the movements that are remaining in the video time left
+ const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000)
+
+ const handleFirstMovements = () => {
+ // if the first movement is a closed tab, open it
+ const firstMovement = filteredMovements[0];
+ const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined;
+ if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc);
+
+ // for the open tabs, set it to the first move
+ const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
+ for (const [docId, firstMove] of docIdtoFirstMove) {
+ const colFFView = this.getCollectionFFView(docId);
+ if (colFFView) this.zoomAndPan(firstMove, colFFView);
+ }
+ }
+ handleFirstMovements();
+
+
+ // make timers that will execute each movement at the correct replay time
+ this.timers = filteredMovements.map(movement => {
+ const timeDiff = movement.time - timeViewed * 1000
+
+ return setTimeout(() => {
+ const collectionFFView = this.getCollectionFFView(movement.docId);
+ if (collectionFFView) {
+ this.zoomAndPan(movement, collectionFFView);
+ } else {
+ // tab wasn't open - open it and play the movement
+ const openedColFFView = this.openTab(movement.docId, docIdtoDoc);
+ console.log('openedColFFView', openedColFFView);
+ openedColFFView && this.zoomAndPan(movement, openedColFFView);
+ }
+
+ // if last movement, presentation is done -> cleanup :)
+ if (movement === filteredMovements[filteredMovements.length - 1]) {
+ this.endPlayingPresentation();
+ }
+ }, timeDiff);
+ });
+ }
+}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
new file mode 100644
index 000000000..d512e4802
--- /dev/null
+++ b/src/client/util/TrackMovements.ts
@@ -0,0 +1,257 @@
+import { IReactionDisposer, observable, observe, reaction } from "mobx";
+import { NumCast } from "../../fields/Types";
+import { Doc, DocListCast } from "../../fields/Doc";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { Id } from "../../fields/FieldSymbols";
+
+export type Movement = {
+ time: number,
+ panX: number,
+ panY: number,
+ scale: number,
+ docId: string,
+}
+
+export type Presentation = {
+ movements: Movement[] | null,
+ totalTime: number,
+ meta: Object | Object[],
+}
+
+export class TrackMovements {
+
+ private static get NULL_PRESENTATION(): Presentation {
+ return { movements: null, meta: {}, totalTime: -1, }
+ }
+
+ // instance variables
+ private currentPresentation: Presentation;
+ private tracking: boolean;
+ private absoluteStart: number;
+ // instance variable for holding the FFViews and their disposers
+ private recordingFFViews: Map<string, IReactionDisposer> | null;
+ private tabChangeDisposeFunc: IReactionDisposer | null;
+
+
+ // create static instance and getter for global use
+ @observable static _instance: TrackMovements;
+ static get Instance(): TrackMovements { return TrackMovements._instance }
+ constructor() {
+ // init the global instance
+ TrackMovements._instance = this;
+
+ // init the instance variables
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION
+ this.tracking = false;
+ this.absoluteStart = -1;
+
+ // used for tracking movements in the view frame
+ this.recordingFFViews = null;
+ this.tabChangeDisposeFunc = null;
+ }
+
+ // little helper :)
+ private get nullPresentation(): boolean {
+ return this.currentPresentation.movements === null
+ }
+
+ private addRecordingFFView(doc: Doc, key: string = doc[Id]): void {
+ // console.info('adding dispose func : docId', key, 'doc', doc);
+
+ if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; }
+ if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; }
+
+ const disposeFunc = reaction(
+ () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}),
+ (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale),
+ );
+ this.recordingFFViews?.set(key, disposeFunc);
+ }
+
+ private removeRecordingFFView = (key: string) => {
+ // console.info('removing dispose func : docId', key);
+ if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; }
+ this.recordingFFViews.get(key)?.();
+ this.recordingFFViews.delete(key);
+ }
+
+ // in the case where only one tab was changed (updates not across dashboards), set only one to true
+ private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => {
+ if (this.recordingFFViews === null) return;
+
+ // so that the size comparisons are correct, we must filter to only the FFViews
+ const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform';
+ const tabbedFFViews = new Set<string>();
+ for (const DashDoc of tabbedDocs) {
+ if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]);
+ }
+
+
+ // new tab was added - need to add it
+ if (tabbedFFViews.size > this.recordingFFViews.size) {
+ for (const DashDoc of tabbedDocs) {
+ if (!this.recordingFFViews.has(DashDoc[Id])) {
+ if (isFFView(DashDoc)) {
+ this.addRecordingFFView(DashDoc);
+
+ // only one max change, so return
+ if (onlyOne) return;
+ }
+ }
+ }
+ }
+ // tab was removed - need to remove it from recordingFFViews
+ else if (tabbedFFViews.size < this.recordingFFViews.size) {
+ for (const [key] of this.recordingFFViews) {
+ if (!tabbedFFViews.has(key)) {
+ this.removeRecordingFFView(key);
+ if (onlyOne) return;
+ }
+ }
+ }
+ }
+
+ private initTabTracker = () => {
+ if (this.recordingFFViews === null) {
+ this.recordingFFViews = new Map();
+ }
+
+ // init the dispose funcs on the page
+ const docList = DocListCast(CollectionDockingView.Instance.props.Document.data);
+ this.updateRecordingFFViewsFromTabs(docList);
+
+ // create a reaction to monitor changes in tabs
+ this.tabChangeDisposeFunc =
+ reaction(() => CollectionDockingView.Instance.props.Document.data,
+ (change) => {
+ // TODO: consider changing between dashboards
+ // console.info('change in tabs', change);
+ this.updateRecordingFFViewsFromTabs(DocListCast(change), true);
+ });
+ }
+
+ start = (meta?: Object) => {
+ this.initTabTracker();
+
+ // update the presentation mode
+ Doc.UserDoc().presentationMode = 'recording';
+
+ // (1a) get start date for presenation
+ const startDate = new Date();
+ // (1b) set start timestamp to absolute timestamp
+ this.absoluteStart = startDate.getTime();
+
+ // (2) assign meta content if it exists
+ this.currentPresentation.meta = meta || {}
+ // (3) assign start date to currentPresenation
+ this.currentPresentation.movements = []
+ // (4) set tracking true to allow trackMovements
+ this.tracking = true
+ }
+
+ /* stops the video and returns the presentatation; if no presentation, returns undefined */
+ yieldPresentation(clearData: boolean = true): Presentation | null {
+ // if no presentation or done tracking, return null
+ if (this.nullPresentation || !this.tracking) return null;
+
+ // set the previus recording view to the play view
+ // this.playFFView = this.recordingFFView;
+
+ // ensure we add the endTime now that they are done recording
+ const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart };
+
+ // reset the current presentation
+ clearData && this.clear();
+
+ // console.info('yieldPresentation', cpy);
+ return cpy;
+ }
+
+ finish = (): void => {
+ // make is tracking false
+ this.tracking = false
+ // reset the RecordingApi instance
+ this.clear();
+ }
+
+ private clear = (): void => {
+ // clear the disposeFunc if we are done (not tracking)
+ if (!this.tracking) {
+ this.removeAllRecordingFFViews();
+ this.tabChangeDisposeFunc?.();
+ // update the presentation mode now that we are done tracking
+ Doc.UserDoc().presentationMode = 'none';
+
+ this.recordingFFViews = null;
+ this.tabChangeDisposeFunc = null;
+ }
+
+ // clear presenation data
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION
+ // clear absoluteStart
+ this.absoluteStart = -1
+ }
+
+ removeAllRecordingFFViews = () => {
+ if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; }
+
+ for (const [id, disposeFunc] of this.recordingFFViews) {
+ // console.info('calling dispose func : docId', id);
+ disposeFunc();
+ this.recordingFFViews.delete(id);
+ }
+ }
+
+ private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => {
+ // ensure we are recording to track
+ if (!this.tracking) {
+ console.error('[recordingApi.ts] trackMovements(): tracking is false')
+ return;
+ }
+ // check to see if the presetation is init - if not, we are between segments
+ // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres)
+ // bacuse tracking should be false inbetween segments high key
+ if (this.nullPresentation) {
+ console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments')
+ return;
+ }
+
+ // get the time
+ const time = new Date().getTime() - this.absoluteStart
+ // make new movement object
+ const movement: Movement = { time, panX, panY, scale, docId }
+
+ // add that movement to the current presentation data's movement array
+ this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
+ }
+
+
+ // method that concatenates an array of presentatations into one
+ public concatPresentations = (presentations: Presentation[]): Presentation => {
+ // these three will lead to the combined presentation
+ let combinedMovements: Movement[] = [];
+ let sumTime = 0;
+ let combinedMetas: any[] = [];
+
+ presentations.forEach((presentation) => {
+ const { movements, totalTime, meta } = presentation;
+
+ // update movements if they had one
+ if (movements) {
+ // add the summed time to the movements
+ const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } });
+ // concat the movements already in the combined presentation with these new ones
+ combinedMovements.push(...addedTimeMovements);
+ }
+
+ // update the totalTime
+ sumTime += totalTime;
+
+ // concatenate the metas
+ combinedMetas.push(meta);
+ });
+
+ // return the combined presentation with the updated total summed time
+ return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas };
+ }
+}