From 98fba8bdb0fe81d6f71d0ae6018fcaaf7d8897df Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 15:39:55 -0400 Subject: Refactor RecordingApi into two main files - TrackMovements and ReplayMovements. TrackMovements is for recording presentations and ReplayMovments is for replaying them. --- src/client/util/TrackMovements.ts | 257 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 src/client/util/TrackMovements.ts (limited to 'src/client/util/TrackMovements.ts') diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts new file mode 100644 index 000000000..342bb440e --- /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 | 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(); + 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.log('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.log('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.log('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 }; + } +} -- cgit v1.2.3-70-g09d2 From 485e154c899f2249000cf1ddd03de975b4177679 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 15:42:40 -0400 Subject: comment out debugging console.infos --- src/client/util/ReplayMovements.ts | 8 ++++---- src/client/util/TrackMovements.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/client/util/TrackMovements.ts') diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index e46810b52..8e8bfca02 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -32,7 +32,7 @@ export class ReplayMovements { // 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'); + // console.warn('[recordingApi.ts] pauseMovements(): already on paused'); return; } @@ -42,7 +42,7 @@ export class ReplayMovements { } setVideoBox = async (videoBox: VideoBox) => { - console.log('setVideoBox', videoBox); + // console.info('setVideoBox', videoBox); if (videoBox !== null) { console.warn('setVideoBox on already videoBox'); } if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox dispose func'); this.videoBoxDisposeFunc(); } @@ -104,7 +104,7 @@ export class ReplayMovements { } docIdtoDoc.set(docId, refFields[docId] as Doc); } - console.log('loadPresentation refFields', refFields, docIdtoDoc); + // console.info('loadPresentation refFields', refFields, docIdtoDoc); return docIdtoDoc; } @@ -151,7 +151,7 @@ export class ReplayMovements { } public playMovements = (presentation: Presentation, docIdtoDoc: Map, timeViewed: number = 0) => { - console.log('playMovements', presentation, timeViewed, docIdtoDoc); + // 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') diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index 342bb440e..d512e4802 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -56,7 +56,7 @@ export class TrackMovements { } private addRecordingFFView(doc: Doc, key: string = doc[Id]): void { - console.info('adding dispose func : docId', key, 'doc', doc); + // 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; } @@ -69,7 +69,7 @@ export class TrackMovements { } private removeRecordingFFView = (key: string) => { - console.info('removing dispose func : docId', key); + // 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); @@ -125,7 +125,7 @@ export class TrackMovements { reaction(() => CollectionDockingView.Instance.props.Document.data, (change) => { // TODO: consider changing between dashboards - console.log('change in tabs', change); + // console.info('change in tabs', change); this.updateRecordingFFViewsFromTabs(DocListCast(change), true); }); } @@ -163,7 +163,7 @@ export class TrackMovements { // reset the current presentation clearData && this.clear(); - console.log('yieldPresentation', cpy); + // console.info('yieldPresentation', cpy); return cpy; } @@ -196,7 +196,7 @@ export class TrackMovements { if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; } for (const [id, disposeFunc] of this.recordingFFViews) { - console.log('calling dispose func : docId', id); + // console.info('calling dispose func : docId', id); disposeFunc(); this.recordingFFViews.delete(id); } -- cgit v1.2.3-70-g09d2