From f61f6be6771415391d9e6733e4cdb1cde32e626d Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 22 Mar 2022 17:36:35 -0400 Subject: Stop drag in present mode if element isn't specifically selected to drag. --- src/client/util/DragManager.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c9c499fff..8d4b98d88 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -214,6 +214,10 @@ export namespace DragManager { options?: DragOptions, dropEvent?: () => any ) { + // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode + // note: dragData.dropAction is only undefined when the element itself being dragged without being selected + if (Doc.UserDoc()?.isPresenting && dragData.dropAction === undefined) return false; + const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); -- cgit v1.2.3-70-g09d2 From 605137223d8afa0eaf8818de5b556601fca8441f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 14 Apr 2022 17:23:49 -0400 Subject: Create smooth record and playback of tracking. --- src/client/util/DragManager.ts | 2 +- src/client/views/MainView.scss | 5 ++++ src/client/views/MainView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src/client') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 8d4b98d88..db9986a41 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -216,7 +216,7 @@ export namespace DragManager { ) { // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode // note: dragData.dropAction is only undefined when the element itself being dragged without being selected - if (Doc.UserDoc()?.isPresenting && dragData.dropAction === undefined) return false; + if (Doc.UserDoc()?.presentationMode === 'recording' && dragData.dropAction === undefined) return false; const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 15cd2c144..3799b6b13 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -29,6 +29,11 @@ left: calc(100% + 5px); z-index: 1; pointer-events: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 10px; } .mainView-snapLines { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8c0795881..b98c80068 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -528,6 +528,7 @@ export class MainView extends React.Component { searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> + {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
{this.userDoc?.presentationMode}
: <>} ; } @computed get snapLines() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..75855f5d9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -50,6 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import e = require("connect-flash"); export const panZoomSchema = createSchema({ _panX: "number", @@ -117,6 +118,8 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @@ -956,8 +959,37 @@ export class CollectionFreeFormView extends CollectionSubView { + // need the first for subtraction + let first = null; + + this.storedMovements.forEach(movement => { + if (first === null) first = movement.time; + + // set the pan to what was stored + setTimeout(() => { + this.Document._panX = movement.panX; + this.Document._panY = movement.panY; + }, movement.time - first) + }) + + // for now, clear the movements + this.storedMovements = [] + } + @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { + // if not presenting, just retrace the movements + if (Doc.UserDoc()?.presentationMode === 'watching') { + this.followMovements() + return; + } + + if (Doc.UserDoc()?.presentationMode === 'recording') { + // store as many movments as possible + this.storedMovements.push({time: new Date().getTime(), panX, panY}) + } + if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); -- cgit v1.2.3-70-g09d2 From c1aead50030121554bf95ad392c80e042ec9c4d6 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 19 Apr 2022 14:53:16 -0400 Subject: Extracted view tracking into api class --- src/client/apis/recording/recordingApi.tsx | 151 +++++++++++++++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/client/apis/recording/recordingApi.tsx (limited to 'src/client') diff --git a/src/client/apis/recording/recordingApi.tsx b/src/client/apis/recording/recordingApi.tsx new file mode 100644 index 000000000..55714f03b --- /dev/null +++ b/src/client/apis/recording/recordingApi.tsx @@ -0,0 +1,151 @@ +import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; +import React, { useState } from "react"; + +export function RecordingApi() { + + type Movement = { + time: number, + panX: number, + panY: number, + } + + type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, + } + + const NULL_PRESENTATION = { + movements: [], + meta: {}, + startDate: null, + } + + const [currentPresentation, setCurrentPresenation] = useState(NULL_PRESENTATION) + const [isRecording, setIsRecording] = useState(false) + const [absoluteStart, setAbsoluteStart] = useState(-1) + + const initAndStart = (view: CollectionFreeFormView, meta?: Object): Error | undefined => { + // check if already init a presentation + if (currentPresentation.startDate !== null) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + return new Error('[recordingApi.ts] start()') + } + + // (1a) get start date for presenation + const startDate = new Date() + // (1b) set start timestamp to absolute timestamp + setAbsoluteStart(startDate.getTime()) + + // TODO: (2) assign meta content + + // (3) assign init values to currentPresenation + setCurrentPresenation({ ...currentPresentation, startDate }) + + // (4) set isRecording true to allow trackMovements + setIsRecording(true) + } + + const clear = (): Error | undefined => { + // TODO: maybe archive the data? + if (isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') + return new Error('[recordingApi.ts] clear()') + } + // clear presenation data + setCurrentPresenation(NULL_PRESENTATION) + + // clear isRecording + setIsRecording(false) + + // clear absoluteStart + setAbsoluteStart(-1) + } + + const pause = (): Error | undefined => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause(): no presenation') + } + // don't allow track movments + setIsRecording(false) + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + setAbsoluteStart(timestamp) + } + + const resume = () => { + if (currentPresentation.startDate === null) { + console.error('[recordingApi.ts] resume() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] resume()') + } + + const timestamp = new Date().getTime() + const startTimestamp = currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] pause()') + } + + setAbsoluteStart(prevTime => { + // const relativeUnpause = timestamp - absoluteStart + // const timePaused = relativeUnpause - prevTime + // return timePaused + absoluteStart + const absoluteTimePaused = timestamp - prevTime + return absoluteTimePaused + }) + } + + const finish = (): Error | Presentation => { + if (currentPresentation.movements === null) { + console.error('[recordingApi.ts] finish() failed: no presentation data. try calling init() first') + return new Error('[recordingApi.ts] finish()') + } + + // make copy and clear this class's data + const returnCopy = { ...currentPresentation } + clear() + + // return the copy + return returnCopy + } + + const trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement struct to the current presentation data + setCurrentPresenation(prevPres => { + const movements = [...prevPres.movements, movement] + return {...prevPres, movements} + }) + } + + // TOOD: need to pause all intervals if possible lol + // TODO: extract this into different class with pause and resume recording + const followMovements = (presentation: Presentation, docView: CollectionFreeFormView): void => { + const document = docView.Document + + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 75855f5d9..1db90a65a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @@ -959,6 +959,7 @@ export class CollectionFreeFormView extends CollectionSubView { // need the first for subtraction let first = null; -- cgit v1.2.3-70-g09d2