aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/documents/DocumentTypes.ts3
-rw-r--r--src/client/documents/Documents.ts9
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/ImageBox.tsx27
-rw-r--r--src/client/views/nodes/ScreenshotBox.scss46
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx193
-rw-r--r--src/client/views/nodes/VideoBox.tsx17
-rw-r--r--src/client/views/nodes/WebBox.scss7
-rw-r--r--src/client/views/nodes/WebBox.tsx9
-rw-r--r--src/server/authentication/models/current_user_utils.ts3
11 files changed, 296 insertions, 23 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index e3ce8e798..5ec1cfdb4 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -27,5 +27,6 @@ export enum DocumentType {
DOCULINK = "doculink",
PDFANNO = "pdfanno",
INK = "ink",
- DOCUMENT = "document"
+ DOCUMENT = "document",
+ SCREENSHOT = "screenshot",
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 131a48a2b..b5cffaddd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -58,6 +58,7 @@ import { MessageStore } from "../../server/Message";
import { ContextMenuProps } from "../views/ContextMenuItem";
import { ContextMenu } from "../views/ContextMenu";
import { LinkBox } from "../views/nodes/LinkBox";
+import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
const requestImageSize = require('../util/request-image-size');
const path = require('path');
@@ -277,6 +278,10 @@ export namespace Docs {
[DocumentType.INK, {
layout: { view: InkingStroke, dataField: data },
options: { backgroundColor: "transparent" }
+ }],
+ [DocumentType.SCREENSHOT, {
+ layout: { view: ScreenshotBox, dataField: data },
+ options: {}
}]
]);
@@ -522,6 +527,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options);
}
+ export function ScreenshotDocument(url: string, options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options);
+ }
+
export function AudioDocument(url: string, options: DocumentOptions = {}) {
const instance = InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options);
Doc.GetProto(instance).backgroundColor = ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'");
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2d6777a4e..97177855f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -316,7 +316,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.includes("uri")) {
const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
- const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+ const type = "html";// (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
if (type) {
const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
doc && generatedDocuments.push(doc);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f121ba3e0..239f414fd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -29,6 +29,7 @@ import { ColorBox } from "./ColorBox";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { DocuLinkBox } from "./DocuLinkBox";
import { PresElementBox } from "../presentationview/PresElementBox";
+import { ScreenshotBox } from "./ScreenshotBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
@@ -99,7 +100,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox,
- RecommendationsBox,
+ RecommendationsBox, ScreenshotBox
}}
bindings={this.CreateBindings()}
jsx={this.layout}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f870a5df4..b1c172e22 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,37 +1,34 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faFileAudio, faImage, faPaintBrush, faBrain } from '@fortawesome/free-solid-svg-icons';
+import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, trace } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
+import { ObjectField } from '../../../new_fields/ObjectField';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
import { ComputedField } from '../../../new_fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { AudioField, ImageField } from '../../../new_fields/URLField';
-import { Utils, returnOne, emptyFunction } from '../../../Utils';
+import { TraceMobx } from '../../../new_fields/util';
+import { emptyFunction, returnOne, Utils } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Docs } from '../../documents/Documents';
+import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
+import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
import { DocAnnotatableComponent } from '../DocComponent';
import FaceRectangles from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
-import { SearchUtil } from '../../util/SearchUtil';
-import { ClientRecommender } from '../../ClientRecommender';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { TraceMobx } from '../../../new_fields/util';
-import { SelectionManager } from '../../util/SelectionManager';
-import { cache } from 'sharp';
-import { ObjectField } from '../../../new_fields/ObjectField';
-import { Networking } from '../../Network';
const requestImageSize = require('../../util/request-image-size');
const path = require('path');
const { Howl } = require('howler');
@@ -163,7 +160,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
funcs.push({
- description: "Reset Native Dimensions", event: action(() => {
+ description: "Reset Native Dimensions", event: action(async () => {
const curNW = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]);
const curNH = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]);
if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
new file mode 100644
index 000000000..4aaccb472
--- /dev/null
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -0,0 +1,46 @@
+.screenshotBox {
+ pointer-events: all;
+ transform-origin: top left;
+ background: white;
+ color: black;
+ // .screenshotBox-viewer {
+ // opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
+ // }
+ // .inkingCanvas-paths-markers {
+ // opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
+ // }
+}
+
+.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
+ position: absolute;
+}
+
+.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen {
+ height: Auto;
+}
+
+.screenshotBox-content-interactive, .screenshotBox-content-fullScreen {
+ pointer-events: all;
+}
+
+.screenshotBox-snapshot{
+ color : white;
+ top :25px;
+ right : 25px;
+ position: absolute;
+ background-color:rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+ pointer-events:all;
+}
+
+.screenshotBox-recorder{
+ color : white;
+ top :25px;
+ right : 50px;
+ position: absolute;
+ background-color:rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+ pointer-events:all;
+}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
new file mode 100644
index 000000000..de9e521f6
--- /dev/null
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -0,0 +1,193 @@
+import React = require("react");
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faVideo } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as rp from 'request-promise';
+import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
+import { makeInterface } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { DocAnnotatableComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ScreenshotBox.scss";
+const path = require('path');
+
+type ScreenshotDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>;
+const ScreenshotDocument = makeInterface(documentSchema, positionSchema);
+
+library.add(faVideo);
+
+@observer
+export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
+ private _reactionDisposer?: IReactionDisposer;
+ private _videoRef: HTMLVideoElement | null = null;
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
+
+ public get player(): HTMLVideoElement | null {
+ return this._videoRef;
+ }
+
+ videoLoad = () => {
+ const aspect = this.player!.videoWidth / this.player!.videoHeight;
+ const nativeWidth = (this.Document._nativeWidth || 0);
+ const nativeHeight = (this.Document._nativeHeight || 0);
+ if (!nativeWidth || !nativeHeight) {
+ if (!this.Document._nativeWidth) this.Document._nativeWidth = this.player!.videoWidth;
+ this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect;
+ this.Document._height = (this.Document._width || 0) / aspect;
+ }
+ if (!this.Document.duration) this.Document.duration = this.player!.duration;
+ }
+
+ @action public Snapshot() {
+ const width = this.Document._width || 0;
+ const height = this.Document._height || 0;
+ const canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1);
+ const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ if (ctx) {
+ ctx.rect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = "blue";
+ ctx.fill();
+ this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
+ }
+
+ if (this._videoRef) {
+ //convert to desired file format
+ const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // if you want to preview the captured image,
+ const filename = path.basename(encodeURIComponent("screenshot" + Utils.GenerateGuid().replace(/\..*$/, "").replace(" ", "_")));
+ ScreenshotBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ setTimeout(() => {
+ if (returnedFilename) {
+ const imageSummary = Docs.Create.ImageDocument(Utils.prepend(returnedFilename), {
+ x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+ _width: 150, _height: height / width * 150, title: "--screenshot--"
+ });
+ this.props.addDocument?.(imageSummary);
+ }
+ }, 500);
+ });
+ }
+ }
+
+ componentDidMount() {
+ }
+
+ componentWillUnmount() {
+ this._reactionDisposer && this._reactionDisposer();
+ }
+
+ @action
+ setVideoRef = (vref: HTMLVideoElement | null) => {
+ this._videoRef = vref;
+ }
+
+ public static async convertDataUri(imageUri: string, returnedFilename: string) {
+ try {
+ const posting = Utils.prepend("/uploadURI");
+ const returnedUri = await rp.post(posting, {
+ body: {
+ uri: imageUri,
+ name: returnedFilename
+ },
+ json: true,
+ });
+ return returnedUri;
+
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ @observable _screenCapture = false;
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ if (field) {
+ const url = field.url.href;
+ const subitems: ContextMenuProps[] = [];
+ subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
+ subitems.push({
+ description: "Screen Capture", event: (async () => {
+ runInAction(() => this._screenCapture = !this._screenCapture);
+ this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+ }), icon: "expand-arrows-alt"
+ });
+ ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" });
+ }
+ }
+
+ @computed get content() {
+ const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+ const style = "videoBox-content" + interactive;
+ return <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
+ onCanPlay={this.videoLoad}
+ controls={true}
+ onClick={e => e.preventDefault()}>
+ <source type="video/mp4" />
+ Not supported.
+ </video>;
+ }
+
+ toggleRecording = action(async () => {
+ this._screenCapture = !this._screenCapture;
+ this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+ })
+
+ private get uIButtons() {
+ return ([
+ <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
+ <FontAwesomeIcon icon="file" size="lg" />
+ </div>,
+ <div className="screenshotBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
+ <FontAwesomeIcon icon="camera" size="lg" />
+ </div>]);
+ }
+
+ onSnapshot = (e: React.PointerEvent) => {
+ this.Snapshot();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ contentFunc = () => [this.content];
+ render() {
+ return (<div className="videoBox" onContextMenu={this.specificContextMenu}
+ style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+ <div className="videoBox-viewer" >
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ annotationsKey={this.annotationKey}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ </div>
+ {this.uIButtons}
+ </div >);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 439f2d85f..24b66d8f7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -195,6 +195,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
console.log(e);
}
}
+ @observable _screenCapture = false;
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
@@ -203,6 +204,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
+ subitems.push({
+ description: "Screen Capture", event: (async () => {
+ runInAction(() => this._screenCapture = !this._screenCapture);
+ this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
+ }), icon: "expand-arrows-alt"
+ });
ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" });
}
}
@@ -212,8 +219,14 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
- <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
- onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
+ <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
+ onCanPlay={this.videoLoad}
+ controls={VideoBox._showControls}
+ onPlay={() => this.Play()}
+ onSeeked={this.updateTimecode}
+ onPause={() => this.Pause()}
+ onClick={e => e.preventDefault()}>
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 226103a53..b41687c11 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,13 +1,18 @@
@import "../globalCssVariables.scss";
+
+.webBox-container, .webBox-container-dragging {
+ transform-origin: top left;
+}
.webBox-cont,
-.webBox-cont-interactive {
+.webBox-cont-dragging {
padding: 0vw;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
+ transform-origin: top left;
overflow: auto;
pointer-events: none;
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 557adfc03..591864f2c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -323,7 +323,14 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
</>);
}
render() {
- return (<div className={"webBox-container"} >
+ const dragging = "";//</div>!SelectionManager.GetIsDragging() ? "" : "-dragging";
+ return (<div className={`webBox-container${dragging}`}
+ style={{
+ transform: `scale(${this.props.ContentScaling()})`,
+ width: `${100 / this.props.ContentScaling()}%`,
+ height: `${100 / this.props.ContentScaling()}%`,
+ pointerEvents: this.props.Document.isBackground ? "none" : undefined
+ }} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index d1f68ba49..ea7b91b23 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -67,7 +67,8 @@ export class CurrentUserUtils {
{ title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
{ title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
{ title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" },
- { title: "webcam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { width: 400, height: 400, title: "a test cam" })' },
+ { title: "screenshot", icon: "frame", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
+ { title: "webcam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
{ title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
{ title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
{ title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },