aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.tsx7
-rw-r--r--src/client/views/nodes/FieldView.tsx5
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss57
-rw-r--r--src/client/views/nodes/VideoBox.tsx177
-rw-r--r--src/client/views/nodes/WebBox.tsx2
7 files changed, 230 insertions, 23 deletions
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index a824ae9cc..e4b2ecffd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -9,7 +9,6 @@ import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { LinkFollowBox } from "../linking/LinkFollowBox";
import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
@@ -100,7 +99,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null);
return <ObserverJsxParser
blacklistedAttrs={[]}
- components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0bf3b5c40..758222e52 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -21,7 +21,6 @@ import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
@@ -65,13 +64,13 @@ library.add(fa.faLock);
library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
export interface DocumentViewProps {
- ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+ ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
Document: Doc;
DataDoc?: Doc;
fitToBox?: boolean;
onClick?: ScriptField;
- addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+ addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
@@ -217,7 +216,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace");
maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab"));
if (maxLocation === "inPlace") {
- expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc, false));
+ expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc));
let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs);
} else {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c17730f48..074efaf6c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -8,7 +8,6 @@ import { List } from "../../../new_fields/List";
import { RichTextField } from "../../../new_fields/RichTextField";
import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField";
import { Transform } from "../../util/Transform";
-import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { AudioBox } from "./AudioBox";
import { FormattedTextBox } from "./FormattedTextBox";
@@ -28,7 +27,7 @@ export interface FieldViewProps {
fieldExt: string;
leaveNativeSize?: boolean;
fitToBox?: boolean;
- ContainingCollectionView: Opt<CollectionView | CollectionVideoView>;
+ ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
ruleProvider: Doc | undefined;
Document: Doc;
@@ -37,7 +36,7 @@ export interface FieldViewProps {
isSelected: () => boolean;
select: (isCtrlPressed: boolean) => void;
renderDepth: number;
- addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+ addDocument?: (document: Doc) => boolean;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
removeDocument?: (document: Doc) => boolean;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1f3887608..57803be1f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -174,7 +174,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
}
- _initialScale: number | undefined;
+ _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live....
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index d651a8621..b3cd439aa 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,6 +1,14 @@
+// .videoBox-container {
+// .collectionfreeformview-container {
+// mix-blend-mode: multiply;
+// }
+// }
+
.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
.videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
width: 100%;
+ z-index: 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;
}
.videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen {
@@ -11,7 +19,52 @@
height: 100%;
}
-.videoBox-content-interactive, .videoBox-content-fullScreen,
-.videoBox-content-YouTube-fullScreen {
+.videoBox-content-interactive, .videoBox-content-fullScreen, .videoBox-content-YouTube-fullScreen {
pointer-events: all;
+}
+
+.videoBox-time{
+ color : white;
+ top :25px;
+ left : 25px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+ pointer-events:all;
+}
+.videoBox-snapshot{
+ color : white;
+ top :25px;
+ right : 25px;
+ position: absolute;
+ background-color:rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+ pointer-events:all;
+}
+.videoBox-play {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ left : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: left bottom;
+ pointer-events:all;
+}
+.videoBox-full {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ right : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: right bottom;
+ pointer-events:all;
+
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e83aa8bea..feb067d8f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,11 +3,11 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
+import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import { RouteStore } from "../../../server/RouteStore";
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction, returnOne } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
@@ -22,6 +22,8 @@ import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { Doc } from "../../../new_fields/Doc";
import { ScriptField } from "../../../new_fields/ScriptField";
import { positionSchema } from "./CollectionFreeFormDocumentView";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
var path = require('path');
export const timeSchema = createSchema({
@@ -34,19 +36,24 @@ library.add(faVideo);
@observer
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+ static _youtubeIframeCounter: number = 0;
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
- static _youtubeIframeCounter: number = 0;
+ private _downX: number = 0;
+ private _downY: number = 0;
+ private _isResetClick = 0;
+ private _isChildActive = false;
+ private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
@observable _forceCreateYouTubeIFrame = false;
@observable static _showControls: boolean;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable public Playing: boolean = false;
- public static LayoutString() { return FieldView.LayoutString(VideoBox); }
+ public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); }
public get player(): HTMLVideoElement | null {
return this._videoRef;
@@ -123,8 +130,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
//convert to desired file format
var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
- VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
+ let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
+ VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
if (returnedFilename) {
let url = this.choosePath(Utils.prepend(returnedFilename));
let imageSummary = Docs.Create.ImageDocument(url, {
@@ -132,7 +139,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
});
imageSummary.isButton = true;
- this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
+ this.props.addDocument && this.props.addDocument(imageSummary);
DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
}
});
@@ -259,7 +266,73 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
});
}
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ let curTime = NumCast(this.props.Document.currentTimecode);
+ return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
+ <span>{"" + Math.round(curTime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
+ </div>,
+ <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}>
+ <FontAwesomeIcon icon="camera" size="lg" />
+ </div>,
+ VideoBox._showControls ? (null) : [
+ <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
+ <FontAwesomeIcon icon={this.Playing ? "pause" : "play"} size="lg" />
+ </div>,
+ <div className="collecvideoBoxtionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+ F
+ </div>
+ ]]);
+ }
+
+ @action
+ onPlayDown = () => {
+ if (this.Playing) {
+ this.Pause();
+ } else {
+ this.Play();
+ }
+ }
+ @action
+ onFullDown = (e: React.PointerEvent) => {
+ this.FullScreen();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ @action
+ onSnapshot = (e: React.PointerEvent) => {
+ this.Snapshot();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ @action
+ onResetDown = (e: React.PointerEvent) => {
+ this.Pause();
+ e.stopPropagation();
+ this._isResetClick = 0;
+ document.addEventListener("pointermove", this.onResetMove, true);
+ document.addEventListener("pointerup", this.onResetUp, true);
+ InkingControl.Instance.switchTool(InkTool.Eraser);
+ }
+
+ @action
+ onResetMove = (e: PointerEvent) => {
+ this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
+ this.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
+ e.stopImmediatePropagation();
+ }
+ @action
+ onResetUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.onResetMove, true);
+ document.removeEventListener("pointerup", this.onResetUp, true);
+ InkingControl.Instance.switchTool(InkTool.None);
+ this._isResetClick < 10 && (this.props.Document.currentTimecode = 0);
+ }
+ @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
@computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
@computed get youtubeContent() {
@@ -273,11 +346,95 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
></iframe>;
}
+
+ setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
+ this._setPreviewCursor = func;
+ }
+
+ @action.bound
+ removeDocument(doc: Doc): boolean {
+ Doc.GetProto(doc).annotationOn = undefined;
+ //TODO This won't create the field if it doesn't already exist
+ let targetDataDoc = this.fieldExtensionDoc;
+ let targetField = this.props.fieldExt;
+ let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
+ let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
+ index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
+ index !== -1 && value.splice(index, 1);
+ return true;
+ }
+ // this is called with the document that was dragged and the collection to move it into.
+ // if the target collection is the same as this collection, then the move will be allowed.
+ // otherwise, the document being moved must be able to be removed from its container before
+ // moving it into the target.
+ @action.bound
+ moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+ if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+ return true;
+ }
+ return this.removeDocument(doc) ? addDocument(doc) : false;
+ }
+
+ @action.bound
+ addDocument(doc: Doc): boolean {
+ Doc.GetProto(doc).annotationOn = this.props.Document;
+ var curTime = NumCast(this.props.Document.currentTimecode, -1);
+ curTime !== -1 && (doc.displayTimecode = curTime);
+ Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
+ return true;
+ }
+ whenActiveChanged = (isActive: boolean) => {
+ this._isChildActive = isActive;
+ this.props.whenActiveChanged(isActive);
+ }
+ active = () => {
+ return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+ }
+ onClick = (e: React.MouseEvent) => {
+ this._setPreviewCursor &&
+ e.button === 0 &&
+ Math.abs(e.clientX - this._downX) < 3 &&
+ Math.abs(e.clientY - this._downY) < 3 &&
+ this._setPreviewCursor(e.clientX, e.clientY, false);
+ }
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if ((e.button !== 0 || e.altKey) && this.active()) {
+ this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+ }
+ }
+ @computed get annotationLayer() {
+ return <CollectionFreeFormView {...this.props}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ select={emptyFunction}
+ active={this.active}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ chromeCollapsed={true}>
+ </CollectionFreeFormView>
+ }
+
render() {
Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
- return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
+ return (<div className={"videoBox-container"} onClick={this.onClick} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu}>
{this.youtubeVideoId ? this.youtubeContent : this.content}
- </div>;
+ {this.annotationLayer}
+ {this.uIButtons}
+ </div >);
}
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 29eef27a0..bb4c5adc9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -91,7 +91,7 @@ export class WebBox extends React.Component<FieldViewProps> {
});
SelectionManager.SelectedDocuments().map(dv => {
- dv.props.addDocument && dv.props.addDocument(newBox, false);
+ dv.props.addDocument && dv.props.addDocument(newBox);
dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
});