aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/ReplayMovements.ts204
-rw-r--r--src/client/util/TrackMovements.ts (renamed from src/client/util/RecordingApi.ts)236
2 files changed, 220 insertions, 220 deletions
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
new file mode 100644
index 000000000..e46810b52
--- /dev/null
+++ b/src/client/util/ReplayMovements.ts
@@ -0,0 +1,204 @@
+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;
+ }
+
+ this.isPlaying = false
+ // TODO: set userdoc presentMode to browsing
+ this.timers?.map(timer => clearTimeout(timer))
+ }
+
+ setVideoBox = async (videoBox: VideoBox) => {
+ console.log('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(); }
+
+
+ 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 = () => {
+ Doc.UserDoc().presentationMode = 'none';
+ 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.log('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;
+ }
+ CollectionDockingView.AddSplit(doc, 'right');
+ const docView = DocumentManager.Instance.getDocumentViewById(docId);
+ 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[], timeViewed: number): 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;
+ }
+
+ public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
+ console.log('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, timeViewed);
+ 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);
+ 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/RecordingApi.ts b/src/client/util/TrackMovements.ts
index 87cb85497..342bb440e 100644
--- a/src/client/util/RecordingApi.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,20 +1,10 @@
-import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
import { IReactionDisposer, observable, observe, reaction } from "mobx";
import { NumCast } from "../../fields/Types";
import { Doc, DocListCast } from "../../fields/Doc";
-import { VideoBox } from "../views/nodes/VideoBox";
-import { isArray } from "lodash";
-import { SelectionManager } from "./SelectionManager";
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import { DocumentManager } from "./DocumentManager";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { Id } from "../../fields/FieldSymbols";
-import { returnAll } from "../../Utils";
-import { ContextExclusionPlugin } from "webpack";
-import { DocServer } from "../DocServer";
-import { DocumentView } from "../views/nodes/DocumentView";
-type Movement = {
+export type Movement = {
time: number,
panX: number,
panY: number,
@@ -22,14 +12,13 @@ type Movement = {
docId: string,
}
-
export type Presentation = {
movements: Movement[] | null,
totalTime: number,
meta: Object | Object[],
}
-export class RecordingApi {
+export class TrackMovements {
private static get NULL_PRESENTATION(): Presentation {
return { movements: null, meta: {}, totalTime: -1, }
@@ -45,24 +34,20 @@ export class RecordingApi {
// create static instance and getter for global use
- @observable static _instance: RecordingApi;
- public static get Instance(): RecordingApi { return RecordingApi._instance }
- public constructor() {
+ @observable static _instance: TrackMovements;
+ static get Instance(): TrackMovements { return TrackMovements._instance }
+ constructor() {
// init the global instance
- RecordingApi._instance = this;
+ TrackMovements._instance = this;
// init the instance variables
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
+ 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;
-
- // for now, set playFFView
- // this.playFFView = null;
- this.timers = null;
}
// little helper :)
@@ -78,7 +63,7 @@ export class RecordingApi {
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.trackMovements(res.x, res.y, key, res.scale),
+ (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale),
);
this.recordingFFViews?.set(key, disposeFunc);
}
@@ -126,7 +111,7 @@ export class RecordingApi {
}
}
- public initTabTracker = () => {
+ private initTabTracker = () => {
if (this.recordingFFViews === null) {
this.recordingFFViews = new Map();
}
@@ -145,7 +130,7 @@ export class RecordingApi {
});
}
- public start = (meta?: Object) => {
+ start = (meta?: Object) => {
this.initTabTracker();
// update the presentation mode
@@ -165,7 +150,7 @@ export class RecordingApi {
}
/* stops the video and returns the presentatation; if no presentation, returns undefined */
- public yieldPresentation(clearData: boolean = true): Presentation | null {
+ yieldPresentation(clearData: boolean = true): Presentation | null {
// if no presentation or done tracking, return null
if (this.nullPresentation || !this.tracking) return null;
@@ -182,14 +167,14 @@ export class RecordingApi {
return cpy;
}
- public finish = (): void => {
+ finish = (): void => {
// make is tracking false
this.tracking = false
// reset the RecordingApi instance
this.clear();
}
- public clear = (): void => {
+ private clear = (): void => {
// clear the disposeFunc if we are done (not tracking)
if (!this.tracking) {
this.removeAllRecordingFFViews();
@@ -202,12 +187,12 @@ export class RecordingApi {
}
// clear presenation data
- this.currentPresentation = RecordingApi.NULL_PRESENTATION
+ this.currentPresentation = TrackMovements.NULL_PRESENTATION
// clear absoluteStart
this.absoluteStart = -1
}
- private removeAllRecordingFFViews = () => {
+ removeAllRecordingFFViews = () => {
if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; }
for (const [id, disposeFunc] of this.recordingFFViews) {
@@ -217,7 +202,7 @@ export class RecordingApi {
}
}
- private trackMovements = (panX: number, panY: number, docId: string, scale: number = 0) => {
+ 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')
@@ -240,195 +225,6 @@ export class RecordingApi {
this.currentPresentation.movements && this.currentPresentation.movements.push(movement)
}
- // TODO: extract this into different class with pause and resume recording
- // TODO: store the FFview with the movements
- private timers: NodeJS.Timeout[] | null;
-
- // 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._isPlaying) { console.warn('[recordingApi.ts] pauseMovements(): already on paused'); return;}
- this._isPlaying = false
- // TODO: set userdoc presentMode to browsing
- this.timers?.map(timer => clearTimeout(timer))
-
- // this.videoBox = null;
- }
-
- private videoBoxDisposeFunc: IReactionDisposer | null = null;
- private videoBox: VideoBox | null = null;
-
- setVideoBox = async (videoBox: VideoBox) => {
- console.log('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(); }
-
-
- 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;
- }
-
-
- // by calling pause on the VideoBox, the pauseMovements will be called
- public pauseFromInteraction = () => {
- Doc.UserDoc().presentationMode = 'none';
- this.videoBox?.Pause();
-
- this.pauseMovements();
- // return this.videoBox == null
- }
-
-
-
- public _isPlaying = false;
-
- 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.log('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;
- }
- CollectionDockingView.AddSplit(doc, 'right');
- const docView = DocumentManager.Instance.getDocumentViewById(docId);
- return docView?.ComponentView as CollectionFreeFormView;
- }
-
- // helper to replay a movement
- private preScale = -1;
- zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => {
- const { panX, panY, scale } = movement;
- (scale !== 0 && this.preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0);
- document.Document._panX = panX;
- document.Document._panY = panY;
-
- this.preScale = scale;
- }
-
- getFirstMovements = (movements: Movement[], timeViewed: number): 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.preScale = -1;
- RecordingApi.Instance._isPlaying = false;
- }
-
- public playMovements = (presentation: Presentation, docIdtoDoc: Map<string, Doc>, timeViewed: number = 0) => {
- console.log('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, timeViewed);
- 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);
- openedColFFView && this.zoomAndPan(movement, openedColFFView);
- }
-
- // if last movement, presentation is done -> cleanup :)
- if (movement === filteredMovements[filteredMovements.length - 1]) {
- this.endPlayingPresentation();
- }
- }, timeDiff);
- });
- }
// method that concatenates an array of presentatations into one
public concatPresentations = (presentations: Presentation[]): Presentation => {