From 77d66d159d75442ff5635c4bf4843b6155883cc2 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 10 Oct 2019 14:04:22 -0400 Subject: removed CollectionVideoView --- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 7 +- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.scss | 57 +++++++- src/client/views/nodes/VideoBox.tsx | 177 ++++++++++++++++++++++-- src/client/views/nodes/WebBox.tsx | 2 +- 7 files changed, 230 insertions(+), 23 deletions(-) (limited to 'src/client/views/nodes') 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; + ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; 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(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; + ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; 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(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(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(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(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(VideoD }); } + private get uIButtons() { + let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); + let curTime = NumCast(this.props.Document.currentTimecode); + return ([
+ {"" + Math.round(curTime)} + {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} +
, +
+ +
, + VideoBox._showControls ? (null) : [ +
+ +
, +
+ F +
+ ]]); + } + + @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(VideoD >; } + + 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 + + } + render() { Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); - return
+ return (
{this.youtubeVideoId ? this.youtubeContent : this.content} -
; + {this.annotationLayer} + {this.uIButtons} +
); } } 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 { }); 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); }); -- cgit v1.2.3-70-g09d2