aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/recording/recordingApi.tsx151
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/views/MainView.scss5
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx33
5 files changed, 194 insertions, 0 deletions
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<Movement>
+ meta: Object,
+ startDate: Date | null,
+ }
+
+ const NULL_PRESENTATION = {
+ movements: [],
+ meta: {},
+ startDate: null,
+ }
+
+ const [currentPresentation, setCurrentPresenation] = useState<Presentation>(NULL_PRESENTATION)
+ const [isRecording, setIsRecording] = useState(false)
+ const [absoluteStart, setAbsoluteStart] = useState<number>(-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/util/DragManager.ts b/src/client/util/DragManager.ts
index 411fc6d11..47b7ce0e7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -211,6 +211,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()?.presentationMode === 'recording' && dragData.dropAction === undefined) return false;
+
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
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 a67cb3014..609af568e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -540,6 +540,7 @@ export class MainView extends React.Component {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined} />
+ {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ? <div style={{ border: '.5rem solid green', padding: '5px' }}>{this.userDoc?.presentationMode}</div>: <></>}
</div>;
}
@computed get snapLines() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2697cf08..6db2269c4 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<Partial<collection
@observable _keyframeEditing = false;
@observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
+ @observable storedMovements: object[] = []; // stores the movement if in recoding mode
+
@computed get views() { return this._layoutElements.filter(ele => 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,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
+
+ followMovements = (): void => {
+ // 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);