aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json109
-rw-r--r--package.json3
-rw-r--r--src/client/util/CurrentUserUtils.ts5
-rw-r--r--src/client/views/nodes/ScreenshotBox.scss7
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx178
5 files changed, 270 insertions, 32 deletions
diff --git a/package-lock.json b/package-lock.json
index fa8f7259b..d286a4e02 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1342,6 +1342,11 @@
"integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==",
"dev": true
},
+ "@types/three": {
+ "version": "0.126.2",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.126.2.tgz",
+ "integrity": "sha512-6JqTgijtfXcTJik8NtiNxr2L90ex6ElM00qilOGeUcrEsJLOdzLJSIkXHUYS+KPAYQYtRJQKD6XaXds3HjS+gg=="
+ },
"@types/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
@@ -4828,6 +4833,11 @@
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
},
+ "debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
+ },
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
@@ -14726,6 +14736,11 @@
"resize-observer-polyfill": "^1.5.0"
}
},
+ "react-merge-refs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
+ "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ=="
+ },
"react-modal": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
@@ -14785,6 +14800,27 @@
}
}
},
+ "react-reconciler": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.1.tgz",
+ "integrity": "sha512-6E/CvH9zcDmHjhiNJlP0qJ8+3ufnY2b5RWs774Uy8XKWN0l6qfnlkz0XnDacxqj2rbJdq76w9dlFXjPPOQrmqA==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.1"
+ },
+ "dependencies": {
+ "scheduler": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ }
+ }
+ },
"react-redux": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
@@ -14881,6 +14917,41 @@
}
}
},
+ "react-three-fiber": {
+ "version": "5.3.22",
+ "resolved": "https://registry.npmjs.org/react-three-fiber/-/react-three-fiber-5.3.22.tgz",
+ "integrity": "sha512-TAOsFg7oerXDkUK/NUwlaMd+drteokXdDMACDmeBKzx64Z0IruJl/ORW7x3giWzqG3fs6BBv+rpcreFW9Ofuyg==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "react-merge-refs": "^1.1.0",
+ "react-reconciler": "0.26.1",
+ "react-use-measure": "^2.0.3",
+ "resize-observer-polyfill": "^1.5.1",
+ "scheduler": "0.20.1",
+ "tiny-emitter": "^2.1.0",
+ "use-asset": "^1.0.4",
+ "utility-types": "^3.10.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.13.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz",
+ "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "scheduler": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz",
+ "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ }
+ }
+ },
"react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
@@ -14892,6 +14963,14 @@
"react-lifecycles-compat": "^3.0.4"
}
},
+ "react-use-measure": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.0.4.tgz",
+ "integrity": "sha512-7K2HIGaPMl3Q9ZQiEVjen3tRXl4UDda8LiTPy/QxP8dP2rl5gPBhf7mMH6MVjjRNv3loU7sNzey/ycPNnHVTxQ==",
+ "requires": {
+ "debounce": "^1.2.0"
+ }
+ },
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@@ -16922,6 +17001,11 @@
"resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz",
"integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q=="
},
+ "three": {
+ "version": "0.127.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.127.0.tgz",
+ "integrity": "sha512-wtgrn+mhYUbobxT7QN3GPdu3SRpSBQvwY6uOzLChWS7QE//f7paDU/+wlzbg+ngeIvBBqjBHSRuywTh8A99Jng=="
+ },
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -16974,6 +17058,11 @@
"setimmediate": "^1.0.4"
}
},
+ "tiny-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+ },
"tiny-inflate": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
@@ -17887,6 +17976,21 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
+ "use-asset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/use-asset/-/use-asset-1.0.4.tgz",
+ "integrity": "sha512-7/hqDrWa0iMnCoET9W1T07EmD4Eg/Wmoj/X8TGBc++ECRK4m5yTsjP4O6s0yagbxfqIOuUkIxe2/sA+VR2GxZA==",
+ "requires": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "dependencies": {
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ }
+ }
+ },
"use-memo-one": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz",
@@ -17914,6 +18018,11 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
+ "utility-types": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
+ "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg=="
+ },
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
diff --git a/package.json b/package.json
index 7ea6fdc6f..8853714f1 100644
--- a/package.json
+++ b/package.json
@@ -128,6 +128,7 @@
"@types/cors": "^2.8.8",
"@types/google-maps": "^3.2.2",
"@types/reveal": "^3.3.33",
+ "@types/three": "^0.126.2",
"@types/webscopeio__react-textarea-autocomplete": "^4.6.1",
"@webscopeio/react-textarea-autocomplete": "^4.7.0",
"adm-zip": "^0.4.16",
@@ -240,6 +241,7 @@
"react-reveal": "^1.2.2",
"react-select": "^3.1.0",
"react-table": "^6.11.5",
+ "react-three-fiber": "^5.3.22",
"readline": "^1.3.0",
"request": "^2.88.0",
"request-promise": "^4.2.5",
@@ -254,6 +256,7 @@
"standard-http-error": "^2.0.1",
"styled-components": "^4.4.1",
"textarea-caret": "^3.1.0",
+ "three": "^0.127.0",
"translate-google-api": "^1.0.4",
"typescript-collections": "^1.3.3",
"typescript-language-server": "^0.4.0",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b1b7f8a09..14bb87e89 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -425,6 +425,10 @@ export class CurrentUserUtils {
if (doc.emptyScreenshot === undefined) {
doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, _width: 400, _height: 200, system: true, cloneFieldFilter: new List<string>(["system"]) });
}
+ if (doc.emptyWall === undefined) {
+ doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) });
+ (doc.emptyWall as Doc).videoWall = true;
+ }
if (doc.emptyAudio === undefined) {
doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "audio recording", system: true, cloneFieldFilter: new List<string>(["system"]) });
((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0;
@@ -454,6 +458,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc },
{ toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true },
{ toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc, noviceMode: true },
+ { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc },
{ toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
{ toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc },
{ toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
index ab54cf526..6fb5ea7b3 100644
--- a/src/client/views/nodes/ScreenshotBox.scss
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -10,6 +10,13 @@
// }
}
+#CANCAN {
+ canvas {
+ width:100% !important;
+ height: 100% !important;
+ }
+}
+
.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen {
width: 100%;
z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 44460dc41..3c281086a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,9 +1,9 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
-import { Doc, WidthSym } from "../../../fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
@@ -11,8 +11,7 @@ import { makeInterface } from "../../../fields/Schema";
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, Utils } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnFalse, returnOne, Utils, numberRange } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { Networking } from "../../Network";
@@ -26,6 +25,10 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from "./formattedText/FormattedTextBox";
import "./ScreenshotBox.scss";
import { VideoBox } from "./VideoBox";
+import { TraceMobx } from "../../../fields/util";
+import { Canvas } from 'react-three-fiber';
+import * as THREE from 'three';
+import { Vector3, Vector2, Camera } from "three"
declare class MediaRecorder {
constructor(e: any, options?: any); // whatever MediaRecorder has
}
@@ -33,18 +36,100 @@ declare class MediaRecorder {
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
const ScreenshotDocument = makeInterface(documentSchema);
+interface VideoTileProps {
+ raised: { coord: Vector2, off: Vector3 }[];
+ setRaised: (r: { coord: Vector2, off: Vector3 }[]) => void;
+ x: number;
+ y: number;
+ rootDoc: Doc;
+ color: string;
+}
+
+@observer
+export class VideoTile extends React.Component<VideoTileProps> {
+ @observable _videoRef: HTMLVideoElement | undefined;
+ _mesh: any = undefined;
+
+ render() {
+ const topLeft = [this.props.x, this.props.y];
+ const raised = this.props.raised;
+ const find = (raised: { coord: Vector2, off: Vector3 }[], what: Vector2) => raised.find(r => r.coord.x === what.x && r.coord.y === what.y);
+ const tl1 = find(raised, new Vector2(topLeft[0], topLeft[1] + 1));
+ const tl2 = find(raised, new Vector2(topLeft[0] + 1, topLeft[1] + 1));
+ const tl3 = find(raised, new Vector2(topLeft[0] + 1, topLeft[1]));
+ const tl4 = find(raised, new Vector2(topLeft[0], topLeft[1]));
+ const quad_indices = [0, 2, 1, 0, 3, 2];
+ const quad_uvs = [0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0];
+ const quad_normals = [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,];
+ const quad_vertices =
+ [
+ topLeft[0] - 0.0 + (tl1?.off.x || 0), topLeft[1] + 1.0 + (tl1?.off.y || 0), 0.0 + (tl1?.off.z || 0),
+ topLeft[0] + 1.0 + (tl2?.off.x || 0), topLeft[1] + 1.0 + (tl2?.off.y || 0), 0.0 + (tl2?.off.z || 0),
+ topLeft[0] + 1.0 + (tl3?.off.x || 0), topLeft[1] - 0.0 + (tl3?.off.y || 0), 0.0 + (tl3?.off.z || 0),
+ topLeft[0] - 0.0 + (tl4?.off.x || 0), topLeft[1] - 0.0 + (tl4?.off.y || 0), 0.0 + (tl4?.off.z || 0)
+ ];
+
+ const vertices = new Float32Array(quad_vertices);
+ const normals = new Float32Array(quad_normals);
+ const uvs = new Float32Array(quad_uvs); // Each vertex has one uv coordinate for texture mapping
+ const indices = new Uint32Array(quad_indices); // Use the four vertices to draw the two triangles that make up the square.
+ const popOut = () => NumCast(this.props.rootDoc.popOut);
+ const popOff = () => NumCast(this.props.rootDoc.popOff);
+ return (
+ <mesh key={`mesh${topLeft[0]}${topLeft[1]}`} onClick={action(async e => {
+ this.props.setRaised([
+ { coord: new Vector2(topLeft[0], topLeft[1]), off: new Vector3(-popOff(), -popOff(), popOut()) },
+ { coord: new Vector2(topLeft[0] + 1, topLeft[1]), off: new Vector3(popOff(), -popOff(), popOut()) },
+ { coord: new Vector2(topLeft[0], topLeft[1] + 1), off: new Vector3(-popOff(), popOff(), popOut()) },
+ { coord: new Vector2(topLeft[0] + 1, topLeft[1] + 1), off: new Vector3(popOff(), popOff(), popOut()) }
+ ]);
+ if (!this._videoRef) {
+ (navigator.mediaDevices as any).getDisplayMedia({ video: true }).then(action((stream: any) => {
+ //const videoSettings = stream.getVideoTracks()[0].getSettings();
+ this._videoRef = document.createElement("video");
+ Object.assign(this._videoRef, {
+ srcObject: stream,
+ //height: videoSettings.height,
+ //width: videoSettings.width,
+ autoplay: true
+ });
+ }));
+ }
+ })} ref={(r: any) => this._mesh = r}>
+ <bufferGeometry attach="geometry" ref={(r: any) => {
+ // itemSize = 3 because there are 3 values (components) per vertex
+ r?.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
+ r?.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
+ r?.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
+ r?.setIndex(new THREE.BufferAttribute(indices, 1));
+ }} />
+ {!this._videoRef ? <meshStandardMaterial color={this.props.color} /> :
+ <meshBasicMaterial >
+ <videoTexture attach="map" args={[this._videoRef]} />
+ </meshBasicMaterial>}
+ </mesh>
+ )
+ };
+}
+
@observer
export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
- private _videoRef: HTMLVideoElement | null = null;
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);
- this.setupDictation();
+ if (!this.rootDoc.videoWall) this.setupDictation();
+ else {
+ this.rootDoc.nativeWidth = undefined;
+ this.rootDoc.nativeHeight = undefined;
+ this.layoutDoc.popOff = 0;
+ this.layoutDoc.popOut = 1;
+ }
}
getAnchor = () => {
const startTime = Cast(this.layoutDoc._currentTimecode, "number", null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
@@ -67,6 +152,16 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
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);
@@ -89,8 +184,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
}
}, 1000);
}}
- autoPlay={true}
- style={{ width: "100%", height: "100%" }}
+ autoPlay={this._screenCapture}
+ style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
onCanPlay={this.videoLoad}
controls={true}
onClick={e => e.preventDefault()}>
@@ -99,6 +194,27 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
</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) return (null);
+ 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(
+ <VideoTile rootDoc={this.rootDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />)));
+ return <Canvas key="canvas" id="CANCAN" style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }} gl={{ antialias: false }} colorManagement={false} onCreated={props => {
+ 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);
+ }}>
+ {/* <ambientLight />*/}
+ <pointLight position={[10, 10, 10]} intensity={1} />
+ {screens}
+ </ Canvas>
+ };
toggleRecording = action(async () => {
this._screenCapture = !this._screenCapture;
if (this._screenCapture) {
@@ -155,14 +271,14 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
dictationTextProto.mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
this.dataDoc[this.fieldKey + "-dictation"] = dictationText;
}
- contentFunc = () => [this.content];
+ contentFunc = () => [this.threed, this.content];
videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth();
formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
render() {
TraceMobx();
return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} >
<div className="videoBox-viewer" >
- <div style={{ position: "relative", height: this.videoPanelHeight() }}>
+ <div style={{ position: "relative", height: "100%" }}>
<CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
PanelHeight={this.videoPanelHeight}
PanelWidth={this.props.PanelWidth}
@@ -183,28 +299,26 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
{this.contentFunc}
</CollectionFreeFormView></div>
<div style={{ position: "relative", height: this.formattedPanelHeight() }}>
- <FormattedTextBox {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- Document={this.dataDoc[this.fieldKey + "-dictation"]}
- fieldKey={"text"}
- PanelHeight={this.formattedPanelHeight}
- PanelWidth={this.props.PanelWidth}
- focus={this.props.focus}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- isContentActive={returnFalse}
- scaling={returnOne}
- xPadding={25}
- yPadding={10}
- whenChildContentsActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- </FormattedTextBox></div>
+ {!(this.dataDoc[this.fieldKey + "-dictation"] instanceof Doc) ? (null) :
+ <FormattedTextBox {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ Document={this.dataDoc[this.fieldKey + "-dictation"]}
+ fieldKey={"text"}
+ PanelHeight={this.formattedPanelHeight}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ isContentActive={returnFalse}
+ scaling={returnOne}
+ xPadding={25}
+ yPadding={10}
+ whenChildContentsActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+ </FormattedTextBox>}
+ </div>
</div>
{!this.props.isSelected() ? (null) : <div className="screenshotBox-uiButtons">
<div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >