From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 8 Jul 2022 00:17:26 -0400 Subject: restructured currentUserUtils to avoid having import cycles. --- src/client/views/nodes/ScreenshotBox.tsx | 489 ++++++++++++++++--------------- 1 file changed, 250 insertions(+), 239 deletions(-) (limited to 'src/client/views/nodes/ScreenshotBox.tsx') diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 0c7d7dc31..942072524 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -1,32 +1,31 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; // import { Canvas } from '@react-three/fiber'; -import { computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; +import { computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; -import { DateField } from "../../../fields/DateField"; -import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast } from "../../../fields/Types"; -import { AudioField, VideoField } from "../../../fields/URLField"; -import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, OmitKeys, returnFalse, returnOne } from "../../../Utils"; -import { DocUtils } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Networking } from "../../Network"; -import { CaptureManager } from "../../util/CaptureManager"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; -import { ContextMenu } from "../ContextMenu"; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; +import { DateField } from '../../../fields/DateField'; +import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { ComputedField } from '../../../fields/ScriptField'; +import { Cast, NumCast } from '../../../fields/Types'; +import { AudioField, VideoField } from '../../../fields/URLField'; +import { TraceMobx } from '../../../fields/util'; +import { emptyFunction, OmitKeys, returnFalse, returnOne } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Networking } from '../../Network'; +import { CaptureManager } from '../../util/CaptureManager'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; +import { ContextMenu } from '../ContextMenu'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; -import "./ScreenshotBox.scss"; -import { VideoBox } from "./VideoBox"; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import './ScreenshotBox.scss'; +import { VideoBox } from './VideoBox'; declare class MediaRecorder { - constructor(e: any, options?: any); // whatever MediaRecorder has + constructor(e: any, options?: any); // whatever MediaRecorder has } // interface VideoTileProps { @@ -107,226 +106,238 @@ declare class MediaRecorder { @observer export class ScreenshotBox extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } - private _audioRec: any; - private _videoRec: any; - @observable private _videoRef: HTMLVideoElement | null = null; - @observable _screenCapture = false; - @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ScreenshotBox, fieldKey); + } + private _audioRec: any; + private _videoRec: any; + @observable private _videoRef: HTMLVideoElement | null = null; + @observable _screenCapture = false; + @computed get recordingStart() { + return Cast(this.dataDoc[this.props.fieldKey + '-recordingStart'], DateField)?.date.getTime(); + } - constructor(props: any) { - super(props); - if (this.rootDoc.videoWall) { - this.rootDoc.nativeWidth = undefined; - this.rootDoc.nativeHeight = undefined; - this.layoutDoc.popOff = 0; - this.layoutDoc.popOut = 1; - } else { - this.setupDictation(); - } - } - getAnchor = () => { - const startTime = Cast(this.layoutDoc._currentTimecode, "number", null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow", "_timecodeToHide", - startTime, startTime === undefined ? undefined : startTime + 3) - || this.rootDoc; - } + constructor(props: any) { + super(props); + if (this.rootDoc.videoWall) { + this.rootDoc.nativeWidth = undefined; + this.rootDoc.nativeHeight = undefined; + this.layoutDoc.popOff = 0; + this.layoutDoc.popOut = 1; + } else { + this.setupDictation(); + } + } + getAnchor = () => { + const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3) || this.rootDoc; + }; - videoLoad = () => { - const aspect = this._videoRef!.videoWidth / this._videoRef!.videoHeight; - const nativeWidth = Doc.NativeWidth(this.layoutDoc); - const nativeHeight = Doc.NativeHeight(this.layoutDoc); - if (!nativeWidth || !nativeHeight) { - if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200); - Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect); - this.layoutDoc._height = (this.layoutDoc[WidthSym]() || 0) / aspect; - } - } + videoLoad = () => { + const aspect = this._videoRef!.videoWidth / this._videoRef!.videoHeight; + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); + if (!nativeWidth || !nativeHeight) { + if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200); + Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect); + this.layoutDoc._height = (this.layoutDoc[WidthSym]() || 0) / aspect; + } + }; - componentDidMount() { - this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0; - this.props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - // this.rootDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }), - // ({ width, height }) => { - // if (this._camera) { - // const angle = -Math.abs(1 - width / height); - // const xz = [0, (this._numScreens - 2) / Math.abs(1 + angle)]; - // this._camera.position.set(this._numScreens / 2 + xz[1] * Math.sin(angle), this._numScreens / 2, xz[1] * Math.cos(angle)); - // this._camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); - // (this._camera as any).updateProjectionMatrix(); - // } - // }); - } - componentWillUnmount() { - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1)); - } - - specificContextMenu = (e: React.MouseEvent): void => { - const subitems = [{ description: "Screen Capture", event: this.toggleRecording, icon: "expand-arrows-alt" as any }]; - ContextMenu.Instance.addItem({ description: "Options...", subitems, icon: "video" }); - } + componentDidMount() { + this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0; + this.props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + // this.rootDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }), + // ({ width, height }) => { + // if (this._camera) { + // const angle = -Math.abs(1 - width / height); + // const xz = [0, (this._numScreens - 2) / Math.abs(1 + angle)]; + // this._camera.position.set(this._numScreens / 2 + xz[1] * Math.sin(angle), this._numScreens / 2, xz[1] * Math.cos(angle)); + // this._camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); + // (this._camera as any).updateProjectionMatrix(); + // } + // }); + } + componentWillUnmount() { + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + } - @computed get content() { - if (this.rootDoc.videoWall) return (null); - return ; - } + specificContextMenu = (e: React.MouseEvent): void => { + const subitems = [{ description: 'Screen Capture', event: this.toggleRecording, icon: 'expand-arrows-alt' as any }]; + ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' }); + }; - // _numScreens = 5; - // _camera: Camera | undefined; - // @observable _raised = [] as { coord: Vector2, off: Vector3 }[]; - // @action setRaised = (r: { coord: Vector2, off: Vector3 }[]) => this._raised = r; - @computed get threed() { - // if (this.rootDoc.videoWall) { - // const screens: any[] = []; - // const colors = ["yellow", "red", "orange", "brown", "maroon", "gray"]; - // let count = 0; - // numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push( - // ))); - // return { - // this._camera = props.camera; - // props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2); - // props.camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); - // }}> - // {/* */} - // - // {screens} - // ; - // } - return (null); - } - toggleRecording = async () => { - if (!this._screenCapture) { - this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true })); - const aud_chunks: any = []; - this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data); - this._audioRec.onstop = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(aud_chunks); - if (!(result instanceof Error)) { - this.dataDoc[this.props.fieldKey + "-audio"] = new AudioField(result.accessPaths.agnostic.client); + @computed get content() { + if (this.rootDoc.videoWall) return null; + return ( + + ); + } + + // _numScreens = 5; + // _camera: Camera | undefined; + // @observable _raised = [] as { coord: Vector2, off: Vector3 }[]; + // @action setRaised = (r: { coord: Vector2, off: Vector3 }[]) => this._raised = r; + @computed get threed() { + // if (this.rootDoc.videoWall) { + // const screens: any[] = []; + // const colors = ["yellow", "red", "orange", "brown", "maroon", "gray"]; + // let count = 0; + // numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push( + // ))); + // return { + // this._camera = props.camera; + // props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2); + // props.camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); + // }}> + // {/* */} + // + // {screens} + // ; + // } + return null; + } + toggleRecording = async () => { + if (!this._screenCapture) { + this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true })); + const aud_chunks: any = []; + this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data); + this._audioRec.onstop = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(aud_chunks); + if (!(result instanceof Error)) { + this.dataDoc[this.props.fieldKey + '-audio'] = new AudioField(result.accessPaths.agnostic.client); + } + }; + this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true }); + this._videoRec = new MediaRecorder(this._videoRef!.srcObject); + const vid_chunks: any = []; + this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '-recordingStart'] = new DateField(new Date())); + this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data); + this._videoRec.onstop = async (e: any) => { + console.log('screenshotbox: upload'); + const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() }); + const [{ result }] = await Networking.UploadFilesToServer(file); + this.dataDoc[this.fieldKey + '-duration'] = (new Date().getTime() - this.recordingStart!) / 1000; + if (!(result instanceof Error)) { + // convert this screenshotBox into normal videoBox + this.dataDoc.type = DocumentType.VID; + this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey); + this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined; + this.layoutDoc._fitWidth = undefined; + this.dataDoc[this.props.fieldKey] = new VideoField(result.accessPaths.agnostic.client); + } else alert('video conversion failed'); + }; + this._audioRec.start(); + this._videoRec.start(); + runInAction(() => { + this._screenCapture = true; + this.dataDoc.mediaState = 'recording'; + }); + DocUtils.ActiveRecordings.push(this); + } else { + this._audioRec?.stop(); + this._videoRec?.stop(); + runInAction(() => { + this._screenCapture = false; + this.dataDoc.mediaState = 'paused'; + }); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); - CaptureManager.Instance.open(this.rootDoc); - } - } + CaptureManager.Instance.open(this.rootDoc); + } + }; - setupDictation = () => { - if (this.dataDoc[this.fieldKey + "-dictation"]) return; - const dictationText = CurrentUserUtils.GetNewTextDoc("dictation", - NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, - NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); - dictationText._autoHeight = false; - const dictationTextProto = Doc.GetProto(dictationText); - dictationTextProto.recordingSource = this.dataDoc; - dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); - dictationTextProto.mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState"); - this.dataDoc[this.fieldKey + "-dictation"] = dictationText; - } - contentFunc = () => [this.threed, this.content]; - videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], this.layoutDoc[WidthSym]()) * this.props.PanelWidth(); - formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); - render() { - TraceMobx(); - return
-
-
- - {this.contentFunc} -
-
- {!(this.dataDoc[this.fieldKey + "-dictation"] instanceof Doc) ? (null) : - - } -
-
- {!this.props.isSelected() ? (null) :
-
- + setupDictation = () => { + if (this.dataDoc[this.fieldKey + '-dictation']) return; + const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); + dictationText._autoHeight = false; + const dictationTextProto = Doc.GetProto(dictationText); + dictationTextProto.recordingSource = this.dataDoc; + dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); + dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); + this.dataDoc[this.fieldKey + '-dictation'] = dictationText; + }; + contentFunc = () => [this.threed, this.content]; + videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth(); + formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); + render() { + TraceMobx(); + return ( +
+
+
+ + {this.contentFunc} + +
+
+ {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( + + )} +
+
+ {!this.props.isSelected() ? null : ( +
+
+
-
} -
; - } -} \ No newline at end of file +
+ )} +
+ ); + } +} -- cgit v1.2.3-70-g09d2