From 657c24efd3083bf64e841fa1bb9ccd59ed4a0281 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 13:57:06 -0400 Subject: fixed up context menu for audio box labels . --- src/client/views/nodes/LabelBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/LabelBox.tsx') diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index db1ae0537..468a2e585 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -50,14 +50,14 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ + !Doc.UserDoc().noviceMode && funcs.push({ description: "Clear Script Params", event: () => { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); params?.map(p => this.paramsDoc[p] = undefined); }, icon: "trash" }); - ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" }); + funcs.length && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" }); } @undoBatch -- cgit v1.2.3-70-g09d2 From 6128f124312d968bb20d2f737860fa5a0064d015 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 24 Sep 2021 09:51:24 -0400 Subject: updated titling of LabelBox to show custom titles when they are set. --- src/client/views/nodes/LabelBox.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/LabelBox.tsx') diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 468a2e585..81c42ff0c 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -37,7 +37,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro } getTitle() { - return this.props.label || ""; + return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : + this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -81,7 +82,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... - const label = this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); + const label = this.getTitle(); return (
this._mouseOver = false)} @@ -90,7 +91,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
Date: Fri, 24 Sep 2021 11:52:33 -0400 Subject: changed label boxes to fit their entire text by scaling their font --- src/client/views/nodes/FunctionPlotBox.tsx | 14 +- src/client/views/nodes/LabelBigText.js | 241 +++++++++++++++++++++++++++++ src/client/views/nodes/LabelBox.scss | 6 +- src/client/views/nodes/LabelBox.tsx | 30 ++-- 4 files changed, 271 insertions(+), 20 deletions(-) create mode 100644 src/client/views/nodes/LabelBigText.js (limited to 'src/client/views/nodes/LabelBox.tsx') diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index b00f97236..5050fc2d2 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -1,18 +1,16 @@ -import EquationEditor from 'equation-editor-react'; import functionPlot from "function-plot"; +import { action, computed, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; -import { createSchema, makeInterface, listSpec } from '../../../fields/Schema'; -import { StrCast, Cast } from '../../../fields/Types'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { Cast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; +import { Docs } from '../../documents/Documents'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; -import './LabelBox.scss'; -import { DocListCast, Doc } from '../../../fields/Doc'; -import { computed, action, reaction } from 'mobx'; -import { Docs } from '../../documents/Documents'; -import { List } from '../../../fields/List'; const EquationSchema = createSchema({}); diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js new file mode 100644 index 000000000..db43551d8 --- /dev/null +++ b/src/client/views/nodes/LabelBigText.js @@ -0,0 +1,241 @@ +/* +Brorlandi/big-text.js v1.0.0, 2017 +Adapted from DanielHoffmann/jquery-bigtext, v1.3.0, May 2014 +And from Jetroid/bigtext.js v1.0.0, September 2016 + +Usage: +BigText("#myElement",{ + rotateText: {Number}, (null) + fontSizeFactor: {Number}, (0.8) + maximumFontSize: {Number}, (null) + limitingDimension: {String}, ("both") + horizontalAlign: {String}, ("center") + verticalAlign: {String}, ("center") + textAlign: {String}, ("center") + whiteSpace: {String}, ("nowrap") +}); + + +Original Projects: +https://github.com/DanielHoffmann/jquery-bigtext +https://github.com/Jetroid/bigtext.js + +Options: + +rotateText: Rotates the text inside the element by X degrees. + +fontSizeFactor: This option is used to give some vertical spacing for letters that overflow the line-height (like 'g', 'Á' and most other accentuated uppercase letters). This does not affect the font-size if the limiting factor is the width of the parent div. The default is 0.8 + +maximumFontSize: maximum font size to use. + +limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height. + +horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center". + +verticalAlign: Where to align the text vertically. Possible values: "top", "center", "bottom". Defaults to "center". + +textAlign: Sets the text align of the element. Possible values: "left", "center", "right". Defaults to "center". This option is only useful if there are linebreaks (
tags) inside the text. + +whiteSpace: Sets whitespace handling. Possible values: "nowrap", "pre". Defaults to "nowrap". (Can also be set to enable wrapping but this doesn't work well.) + +Bruno Orlandi - 2017 + +Copyright (C) 2013 Daniel Hoffmann Bernardes, Ícaro Technologies +Copyright (C) 2016 Jet Holt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +function _calculateInnerDimensions(computedStyle) { + //Calculate the inner width and height + var innerWidth; + var innerHeight; + + var width = parseInt(computedStyle.getPropertyValue("width")); + var height = parseInt(computedStyle.getPropertyValue("height")); + var paddingLeft = parseInt(computedStyle.getPropertyValue("padding-left")); + var paddingRight = parseInt(computedStyle.getPropertyValue("padding-right")); + var paddingTop = parseInt(computedStyle.getPropertyValue("padding-top")); + var paddingBottom = parseInt(computedStyle.getPropertyValue("padding-bottom")); + var borderLeft = parseInt(computedStyle.getPropertyValue("border-left-width")); + var borderRight = parseInt(computedStyle.getPropertyValue("border-right-width")); + var borderTop = parseInt(computedStyle.getPropertyValue("border-top-width")); + var borderBottom = parseInt(computedStyle.getPropertyValue("border-bottom-width")); + + //If box-sizing is border-box, we need to subtract padding and border. + var parentBoxSizing = computedStyle.getPropertyValue("box-sizing"); + if (parentBoxSizing == "border-box") { + innerWidth = width - (paddingLeft + paddingRight + borderLeft + borderRight); + innerHeight = height - (paddingTop + paddingBottom + borderTop + borderBottom); + } else { + innerWidth = width; + innerHeight = height; + } + var obj = {}; + obj["width"] = innerWidth; + obj["height"] = innerHeight; + return obj; +} + +export default function BigText(element, options) { + + if (typeof element === 'string') { + element = document.querySelector(element); + } else if (element.length) { + // Support for array based queries (such as jQuery) + element = element[0]; + } + + var defaultOptions = { + rotateText: null, + fontSizeFactor: 0.8, + maximumFontSize: null, + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }; + + //Merge provided options and default options + options = options || {}; + for (var opt in defaultOptions) + if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt)) + options[opt] = defaultOptions[opt]; + + //Get variables which we will reference frequently + var style = element.style; + var computedStyle = document.defaultView.getComputedStyle(element); + var parent = element.parentNode; + var parentStyle = parent.style; + var parentComputedStyle = document.defaultView.getComputedStyle(parent); + + //hides the element to prevent "flashing" + style.visibility = "hidden"; + + //Set some properties + style.display = "inline-block"; + style.clear = "both"; + style.float = "left"; + style.fontSize = (1000 * options.fontSizeFactor) + "px"; + style.lineHeight = "1000px"; + style.whiteSpace = options.whiteSpace; + style.textAlign = options.textAlign; + style.position = "relative"; + style.padding = 0; + style.margin = 0; + style.left = "50%"; + style.top = "50%"; + + //Get properties of parent to allow easier referencing later. + var parentPadding = { + top: parseInt(parentComputedStyle.getPropertyValue("padding-top")), + right: parseInt(parentComputedStyle.getPropertyValue("padding-right")), + bottom: parseInt(parentComputedStyle.getPropertyValue("padding-bottom")), + left: parseInt(parentComputedStyle.getPropertyValue("padding-left")), + }; + var parentBorder = { + top: parseInt(parentComputedStyle.getPropertyValue("border-top")), + right: parseInt(parentComputedStyle.getPropertyValue("border-right")), + bottom: parseInt(parentComputedStyle.getPropertyValue("border-bottom")), + left: parseInt(parentComputedStyle.getPropertyValue("border-left")), + }; + + //Calculate the parent inner width and height + var parentInnerDimensions = _calculateInnerDimensions(parentComputedStyle); + var parentInnerWidth = parentInnerDimensions["width"]; + var parentInnerHeight = parentInnerDimensions["height"]; + + var box = { + width: element.offsetWidth, //Note: This is slightly larger than the jQuery version + height: element.offsetHeight, + }; + + + if (options.rotateText !== null) { + if (typeof options.rotateText !== "number") + throw "bigText error: rotateText value must be a number"; + var rotate = "rotate(" + options.rotateText + "deg)"; + style.webkitTransform = rotate; + style.msTransform = rotate; + style.MozTransform = rotate; + style.OTransform = rotate; + style.transform = rotate; + //calculating bounding box of the rotated element + var sine = Math.abs(Math.sin(options.rotateText * Math.PI / 180)); + var cosine = Math.abs(Math.cos(options.rotateText * Math.PI / 180)); + box.width = element.offsetWidth * cosine + element.offsetHeight * sine; + box.height = element.offsetWidth * sine + element.offsetHeight * cosine; + } + + var widthFactor = (parentInnerWidth - parentPadding.left - parentPadding.right) / box.width; + var heightFactor = (parentInnerHeight - parentPadding.top - parentPadding.bottom) / box.height; + var lineHeight; + + if (options.limitingDimension.toLowerCase() === "width") { + lineHeight = Math.floor(widthFactor * 1000); + parentStyle.height = lineHeight + "px"; + } else if (options.limitingDimension.toLowerCase() === "height") { + lineHeight = Math.floor(heightFactor * 1000); + } else if (widthFactor < heightFactor) + lineHeight = Math.floor(widthFactor * 1000); + else if (widthFactor >= heightFactor) + lineHeight = Math.floor(heightFactor * 1000); + + var fontSize = lineHeight * options.fontSizeFactor; + if (options.maximumFontSize !== null && fontSize > options.maximumFontSize) { + fontSize = options.maximumFontSize; + lineHeight = fontSize / options.fontSizeFactor; + } + + style.fontSize = Math.floor(fontSize) + "px"; + style.lineHeight = Math.ceil(lineHeight) + "px"; + style.marginBottom = "0px"; + style.marginRight = "0px"; + + if (options.limitingDimension.toLowerCase() === "height") { + //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size + //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code + parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px"; + } + + //Calculate the inner width and height + var innerDimensions = _calculateInnerDimensions(computedStyle); + var innerWidth = innerDimensions["width"]; + var innerHeight = innerDimensions["height"]; + + switch (options.verticalAlign.toLowerCase()) { + case "top": + style.top = "0%"; + break; + case "bottom": + style.top = "100%"; + style.marginTop = Math.floor(-innerHeight) + "px"; + break; + default: + style.marginTop = Math.ceil((-innerHeight / 2)) + "px"; + break; + } + + switch (options.horizontalAlign.toLowerCase()) { + case "left": + style.left = "0%"; + break; + case "right": + style.left = "100%"; + style.marginLeft = Math.floor(-innerWidth) + "px"; + break; + default: + style.marginLeft = Math.ceil((-innerWidth / 2)) + "px"; + break; + } + + //shows the element after the work is done + style.visibility = "visible"; + + return element; +} diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index 1290193d0..6a0d651d2 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -9,10 +9,10 @@ .labelBox-mainButton { max-width: 100%; - width: fit-content; - height: max-content; + width: 100%; + height: 100%; border-radius: inherit; - letter-spacing: 2px; + //letter-spacing: 2px; // bcz: doesn't work with LabelBigText text-transform: uppercase; overflow: hidden; display: inline-block; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 81c42ff0c..935c878ee 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,19 +1,20 @@ -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; import { List } from '../../../fields/List'; import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; +import BigText from './LabelBigText'; import './LabelBox.scss'; -import { StyleProp } from '../StyleProvider'; const LabelSchema = createSchema({}); @@ -91,17 +92,28 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
- {label.startsWith("#") ? (null) : label} + { + if (r) { + BigText(r, { + rotateText: null, + fontSizeFactor: 1, + maximumFontSize: null, + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }) + } + }}>{label.startsWith("#") ? (null) : label}
{!missingParams?.length ? (null) : missingParams.map(m =>
{m}
)} -- cgit v1.2.3-70-g09d2 From a793b7e981e46b7c98bee6cce3faaf3a5b05f6ae Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 26 Sep 2021 02:55:23 -0400 Subject: fixed warnings, fixed bug following link w/auto play. plus refactorings. --- src/client/views/AudioWaveform.tsx | 11 +- .../collections/CollectionStackedTimeline.tsx | 78 ++-- src/client/views/nodes/AudioBox.tsx | 291 +++++++-------- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 405 ++++++++++----------- 5 files changed, 373 insertions(+), 414 deletions(-) (limited to 'src/client/views/nodes/LabelBox.tsx') diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 0e9c00656..270b3869c 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -25,14 +25,13 @@ export interface AudioWaveformProps { export class AudioWaveform extends React.Component { public static NUMBER_OF_BUCKETS = 100; _disposer: IReactionDisposer | undefined; - @computed get _waveHeight() { - return Math.max(50, this.props.PanelHeight()); - } - + @computed get waveHeight() { return Math.max(50, this.props.PanelHeight()); } @computed get clipStart() { return this.props.clipStart; } @computed get clipEnd() { return this.props.clipEnd; } - audioBucketField = (start: number, end: number) => { return "audioBuckets/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_"); } @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)], listSpec("number"), []); } + + audioBucketField = (start: number, end: number) => "audioBuckets/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_"); + componentWillUnmount() { this._disposer?.(); } @@ -87,7 +86,7 @@ export class AudioWaveform extends React.Component {
- ); - } - constructor(props: any) { super(props); // onClick play scripts @@ -157,8 +129,23 @@ export class CollectionStackedTimeline extends CollectionSubView< } } - anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])) - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val)); + public get IsTrimming() { return this._trimming; } + + @action + public StartTrimming(scope: TrimScope) { + this._trimStart = this.clipStart; + this._trimEnd = this.clipEnd; + this._trimming = scope; + } + @action + public StopTrimming() { + this.layoutDoc.clipStart = this.trimStart; + this.layoutDoc.clipEnd = this.trimEnd; + this._trimming = TrimScope.None; + } + + anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null); toTimeline = (screen_delta: number, width: number) => { return Math.max( this.clipStart, @@ -345,13 +332,13 @@ export class CollectionStackedTimeline extends CollectionSubView< const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); const x = localPt[0] - docDragData.offset[0]; const timelinePt = this.toTimeline(x, this.props.PanelWidth()); - for (let i = 0; i < docDragData.droppedDocuments.length; i++) { - const d = Doc.GetProto(docDragData.droppedDocuments[i]); + docDragData.droppedDocuments.forEach(drop => { + const d = Doc.GetProto(drop); if (this.anchorEnd(d) !== undefined) { d[d._timecodeToHide === undefined ? this.props.endTag : "_timecodeToHide"] = timelinePt + this.anchorEnd(d) - this.anchorStart(d); } d[d._timecodToShow === undefined ? this.props.startTag : "_timecodToShow"] = timelinePt; - } + }); return true; } @@ -483,9 +470,12 @@ export class CollectionStackedTimeline extends CollectionSubView< } dictationHeightPercent = 50; - dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100 - timelineContentHeight = () => (this.props.PanelHeight() * this.dictationHeightPercent) / 100 - dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()) + dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; + timelineContentHeight = () => (this.props.PanelHeight() * this.dictationHeightPercent) / 100; + dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()); + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + currentTimecode = () => this.currentTime; + @computed get renderDictation() { const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); return !dictation ? null : ( @@ -537,9 +527,19 @@ export class CollectionStackedTimeline extends CollectionSubView<
); } + @computed get selectionContainer() { + const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd; + return markerEnd === undefined ? null : ( +
+ ); + } - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - currentTimecode = () => this.currentTime; render() { const timelineContentWidth = this.props.PanelWidth(); const overlaps: { @@ -760,7 +760,7 @@ class StackedTimelineAnchor extends React.Component e, (e) => { if (!undo) undo = UndoManager.StartBatch("drag anchor"); - return changeAnchor(anchor, left, newTime(e)) + return changeAnchor(anchor, left, newTime(e)); }, (e) => { this.props.setTime(newTime(e)); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6e6558030..fa78d2301 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,6 +1,6 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { DateField } from "../../../fields/DateField"; import { Doc, DocListCast } from "../../../fields/Doc"; @@ -19,10 +19,8 @@ import { CollectionStackedTimeline, TrimScope } from "../collections/CollectionS import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; -import { Colors } from "../global/globalEnums"; import "./AudioBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; -import { LinkDocPreview } from "./LinkDocPreview"; declare class MediaRecorder { constructor(e: any); // whatever MediaRecorder has @@ -36,10 +34,14 @@ enum media_state { Recording = "recording", Paused = "paused", Playing = "playing" -}; +} @observer export class AudioBox extends ViewBoxAnnotatableComponent(AudioDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } + public static SetScrubTime = action((timeInMillisFrom1970: number) => { + AudioBox._scrubTime = 0; + AudioBox._scrubTime = timeInMillisFrom1970; + }); public static Enabled = false; static playheadWidth = 40; // width of playhead static heightPercent = 75; // height of timeline in percent of height of audioBox. @@ -63,13 +65,30 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - AudioBox._scrubTime = 0; - AudioBox._scrubTime = timeInMillisFrom1970; - }); + get timeline() { return this._stackedTimeline.current; } // can't be computed since it's not observable + + componentWillUnmount() { + this._dropDisposer?.(); + Object.values(this._disposers).forEach((disposer) => disposer?.()); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + } + + @action + componentDidMount() { + this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + + this.mediaState = this.path ? media_state.Paused : undefined as any as media_state; + + this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart)); + this.path && this.timecodeChanged(); + } getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; @@ -100,34 +119,15 @@ export class AudioBox extends ViewBoxAnnotatableComponent disposer?.()); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); - } - - @action - componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - - this.mediaState = this.path ? media_state.Paused : undefined as any as media_state; - - this.path && this.setAnchorTime(NumCast(this.layoutDoc.clipStart)); - this.path && this.timecodeChanged(); - } - // for updating the timecode @action timecodeChanged = () => { if (this.mediaState !== media_state.Recording && this._ele) { this.links - .map((l) => this.getLinkData(l)) + .map(l => this.getLinkData(l)) .forEach(({ la1, la2, linkTime }) => { - if ( - linkTime > NumCast(this.layoutDoc._currentTimecode) && - linkTime < this._ele!.currentTime - ) { + if (linkTime > NumCast(this.layoutDoc._currentTimecode) && + linkTime < this._ele!.currentTime) { Doc.linkFollowHighlight(la1); } }); @@ -135,23 +135,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - this._ele!.pause(); - this.mediaState = media_state.Paused; - }); - - // play audio for documents created during recording - playFromTime = (absoluteTime: number) => { - this.recordingStart && - this.playFrom((absoluteTime - this.recordingStart) / 1000); - } - // play back the audio from time @action - playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false): any => { - clearTimeout(this._play); - if (Number.isNaN(this._ele?.duration)) { + playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => { + clearTimeout(this._play); // abort any previous clip ending + if (Number.isNaN(this._ele?.duration)) { // audio element isn't loaded yet... wait 1/2 second and try again setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); } else if (this.timeline && this._ele && AudioBox.Enabled) { @@ -160,18 +148,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent= 0 && this.timeline.trimStart <= end && seekTimeInSeconds <= this.timeline.trimEnd) { this._ele.currentTime = start; this._ele.play(); - runInAction(() => this.mediaState = media_state.Playing); - if (end !== this.timeline.clipDuration) { - return this._play = setTimeout( - () => { - if (fullPlay) this.setAnchorTime(this.timeline!.trimStart); - this.Pause(); - }, - (end - start) * 1000 - ); // use setTimeout to play a specific duration - } - } else if (seekTimeInSeconds < 0 && seekTimeInSeconds > -1) { - setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); + this.mediaState = media_state.Playing; + this._play = setTimeout( + () => { + if (fullPlay) this.setAnchorTime(this.timeline!.trimStart); + this.Pause(); + }, + (end - start) * 1000); } else { this.Pause(); } @@ -185,8 +168,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.mediaState = media_state.Recording); - setTimeout(this.updateRecordTime, 0); + setTimeout(this.updateRecordTime); this._recorder.start(); - setTimeout(() => this.stopRecording(), 60 * 60 * 1000); // stop after an hour + setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour + } + + @action + stopRecording = () => { + if (this._recorder) { + this._recorder.stop(); + this._recorder = undefined; + this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + this.mediaState = media_state.Paused; + this._stream?.getAudioTracks()[0].stop(); + const ind = DocUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + } } // context menu @@ -243,22 +238,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - if (this._recorder) { - this._recorder.stop(); - this._recorder = undefined; - this.dataDoc[this.fieldKey + "-duration"] = - (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; - this.mediaState = media_state.Paused; - this._stream?.getAudioTracks()[0].stop(); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); - } - }); - // button for starting and stopping the recording - recordClick = (e: React.MouseEvent) => { + Record = (e: React.MouseEvent) => { if (e.button === 0 && !e.ctrlKey) { this._recorder ? this.stopRecording() : this.recordAudioAnnotation(); e.stopPropagation(); @@ -267,12 +248,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - const eleTime = this._ele!.currentTime; - const start = eleTime === this.timeline?.trimDuration ? this.timeline.trimStart : eleTime; + const eleTime = this._ele?.currentTime || 0; + const start = eleTime === this.timeline?.trimEnd ? this.timeline.trimStart : eleTime; this.playFrom(start, undefined, true); e?.stopPropagation?.(); } + // pause play back + @action + Pause = () => { + this._ele?.pause(); + this.mediaState = media_state.Paused; + } + // creates a text document for dictation onFile = (e: any) => { const newDoc = CurrentUserUtils.GetNewTextDoc( @@ -302,27 +290,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - const duration = this._ele?.duration; - if (duration && duration !== Infinity) { - this.dataDoc[this.fieldKey + "-duration"] = duration; - } - })} - className={`audiobox-control${this.props.isContentActive() ? "-interactive" : ""}`}> - - Not supported. - ; - } - // pause the time during recording phase @action recordPause = (e: React.MouseEvent) => { @@ -341,14 +308,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.mediaState === media_state.Playing; playLink = (link: Doc) => { if (link.annotationOn === this.rootDoc) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) { this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link)); } else { - this._ele!.currentTime = this.layoutDoc._currentTimecode = - this.timeline?.anchorStart(link) || 0; + this._ele!.currentTime = this.layoutDoc._currentTimecode = this.timeline?.anchorStart(link) || 0; } } else { this.links @@ -368,6 +333,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.mediaState === media_state.Playing; isActiveChild = () => this._isAnyChildContentActive; timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged( @@ -380,54 +346,21 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - (this._ele!.currentTime = this.layoutDoc._currentTimecode = time); - } + setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; + timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; timelineHeight = () => (((this.props.PanelHeight() * AudioBox.heightPercent) / 100) * AudioBox.heightPercent) / 100 // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) - timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; - @computed get renderTimeline() { - return ( - - ); - } - // hides trim controls and displays new clip + + @undoBatch - finishTrim = action(() => { + finishTrim = () => { // hides trim controls and displays new clip this.Pause(); this.setAnchorTime(Math.max(Math.min(this.timeline?.trimEnd || 0, this._ele!.currentTime), this.timeline?.trimStart || 0)); this.timeline?.StopTrimming(); - }); + } + startTrim = (scope: TrimScope) => { this.Pause(); this.timeline?.StartTrimming(scope); @@ -444,6 +377,18 @@ export class AudioBox extends ViewBoxAnnotatableComponent { + if (r && this.timeline) { + this._dropDisposer?.(); + this._dropDisposer = DragManager.MakeDropTarget(r, + (e, de) => { + const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + de.complete.docDragData && this.timeline!.internalDocDrop(e, de, de.complete.docDragData, xp); + }, + this.layoutDoc, undefined); + } + } + @computed get recordingControls() { return
@@ -453,8 +398,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent
{[media_state.Recording, media_state.Playing].includes(this.mediaState) ? -
e.stopPropagation()}> -
+
e.stopPropagation()}> +
@@ -522,16 +467,52 @@ export class AudioBox extends ViewBoxAnnotatableComponent; } - setupTimelineDrop = (r: HTMLDivElement | null) => { - if (r && this.timeline) { - this._dropDisposer?.(); - this._dropDisposer = DragManager.MakeDropTarget(r, - (e, de) => { - const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); - de.complete.docDragData && this.timeline!.internalDocDrop(e, de, de.complete.docDragData, xp); - }, - this.layoutDoc, undefined); - } + @computed get renderTimeline() { + return ( + + ); + } + // returns the html audio element + @computed get audio() { + return ; } render() { diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 935c878ee..97b1aac86 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -111,7 +111,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro verticalAlign: "center", textAlign: "center", whiteSpace: "nowrap" - }) + }); } }}>{label.startsWith("#") ? (null) : label}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 3435c2a24..2befb4128 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,5 +1,6 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; @@ -8,13 +9,16 @@ import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { AudioField, nullAudio, VideoField } from "../../../fields/URLField"; -import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils, returnFalse, returnZero } from "../../../Utils"; +import { AudioField, VideoField } from "../../../fields/URLField"; +import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DocumentManager } from "../../util/DocumentManager"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; +import { undoBatch } from "../../util/UndoManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionStackedTimeline, TrimScope } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; @@ -22,16 +26,10 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; +import { AnchorMenu } from "../pdf/AnchorMenu"; import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; -import { LinkDocPreview } from "./LinkDocPreview"; import "./VideoBox.scss"; -import { DragManager } from "../../util/DragManager"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Tooltip } from "@material-ui/core"; -import { AnchorMenu } from "../pdf/AnchorMenu"; -import { undoBatch } from "../../util/UndoManager"; const path = require('path'); type VideoDocument = makeInterface<[typeof documentSchema]>; @@ -40,14 +38,30 @@ const VideoDocument = makeInterface(documentSchema); @observer export class VideoBox extends ViewBoxAnnotatableComponent(VideoDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } + 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("VideoBox :" + e); + } + } static _youtubeIframeCounter: number = 0; - static Instance: VideoBox; static heightPercent = 60; // height of timeline in percent of height of videoBox. private _disposers: { [name: string]: IReactionDisposer } = {}; private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; + private _audioPlayer: HTMLAudioElement | null = null; private _stackedTimeline = React.createRef(); private _mainCont: React.RefObject = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); @@ -62,47 +76,52 @@ export class VideoBox extends ViewBoxAnnotatableComponent arr[arr.length - 1])(field.url.href.split("/")) : ""; + } + // returns the path of the audio file + @computed get audiopath() { + const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null); + const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null); + return field?.url.href ?? vfield?.url.href ?? ""; } + private get timeline() { return this._stackedTimeline.current; } private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; } public get player(): HTMLVideoElement | null { return this._videoRef; } - constructor(props: Readonly) { - super(props); - VideoBox.Instance = this; - } - - getAnchor = () => { - const timecode = Cast(this.layoutDoc._currentTimecode, "number", null); - const marquee = AnchorMenu.Instance.GetAnchor?.(); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc; + componentDidMount() { + this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + if (this.youtubeVideoId) { + const youtubeaspect = 400 / 315; + const nativeWidth = Doc.NativeWidth(this.layoutDoc); + const nativeHeight = Doc.NativeHeight(this.layoutDoc); + if (!nativeWidth || !nativeHeight) { + if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600); + Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect); + this.layoutDoc._height = (this.layoutDoc._width || 0) / youtubeaspect; + } + } } - videoLoad = () => { - const aspect = this.player!.videoWidth / this.player!.videoHeight; - Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); - Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); - this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect; - if (Number.isFinite(this.player!.duration)) { - this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration; - } + componentWillUnmount() { + this.Pause(); + Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); } @action public Play = (update: boolean = true) => { this._playing = true; + const eleTime = this.player?.currentTime || 0; + const start = eleTime >= (this.timeline?.trimEnd || 0) ? this.timeline?.trimStart || 0 : eleTime; try { this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime); - update && this.player && this.playFrom(this.player.currentTime); + update && this.player && this.playFrom(start, undefined, true); update && this._audioPlayer?.play(); update && this._youtubePlayer?.playVideo(); this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); @@ -190,7 +209,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => { const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath; const width = this.layoutDoc._width || 1; const height = this.layoutDoc._height || 0; @@ -208,11 +227,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + const timecode = Cast(this.layoutDoc._currentTimecode, "number", null); + const marquee = AnchorMenu.Instance.GetAnchor?.(); + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc; + } + + videoLoad = () => { + const aspect = this.player!.videoWidth / this.player!.videoHeight; + Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); + Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); + this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect; + if (Number.isFinite(this.player!.duration)) { + this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration; + } + } + @action updateTimecode = () => { this.player && (this.layoutDoc._currentTimecode = this.player.currentTime); - this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? Math.min(this.duration, NumCast(this.layoutDoc.clipEnd)) : this.duration; - this._trimEnd = this._trimEnd ? Math.min(this.duration, this._trimEnd) : this.duration; try { this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime?.()); } catch (e) { @@ -220,25 +253,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._disposers[d]?.()); - } - @action setVideoRef = (vref: HTMLVideoElement | null) => { this._videoRef = vref; @@ -252,23 +266,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); if (field) { @@ -294,48 +291,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._audioPlayer = e; - @computed get content() { - const field = Cast(this.dataDoc[this.fieldKey], VideoField); - const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; - const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; - return !field ?
Loading
: -
-
- - {!this.audiopath || this.audiopath === field.url.href ? (null) : - } -
-
; - } - - @computed get youtubeVideoId() { - const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); - return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; - } - - @action youtubeIframeLoaded = (e: any) => { + @action + youtubeIframeLoaded = (e: any) => { if (!this._youtubeContentCreated) { this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame; return; @@ -344,7 +301,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + loadYouTube = (iframe: any) => { let started = true; const onYoutubePlayerStateChange = (event: any) => runInAction(() => { if (started && event.data === YT.PlayerState.PLAYING) { @@ -376,39 +333,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent{"playback"}
} key="play" placement="bottom"> -
- -
- , - {"timecode"}
} key="time" placement="bottom"> -
- {formatTime(curTime)} - {" " + Math.floor((curTime - Math.trunc(curTime)) * 100).toString().padStart(2, "0")} -
- , - {"view full screen"}
} key="full" placement="bottom"> -
- -
- ]; - return
- {[...(VideoBox._nativeControls ? [] : nonNativeControls), - {"snapshot current frame"}
} key="snap" placement="bottom"> -
- -
- , - {"show annotation timeline"}
} key="timeline" placement="bottom"> -
- -
- ,]} -
; - } onPlayDown = () => this._playing ? this.Pause() : this.Play(); @@ -425,10 +349,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.Snapshot()); } - onTimelineHdlDown = action((e: React.PointerEvent) => { + @action + onTimelineHdlDown = (e: React.PointerEvent) => { this._clicking = true; setupMoveUpEvents(this, e, - action((e: PointerEvent) => { + action(encodeURIComponent => { this._clicking = false; if (this.props.isContentActive()) { const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY); @@ -440,28 +365,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._clicking = false), 500); }, this.props.isContentActive(), this.props.isContentActive()); - }); + } onResetDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, - (e: PointerEvent) => { + e => { this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); e.stopImmediatePropagation(); return false; }, emptyFunction, - (e: PointerEvent) => this.layoutDoc._currentTimecode = 0); - } - - @computed get youtubeContent() { - this._youtubeIframeId = VideoBox._youtubeIframeCounter++; - this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; - const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0))); - return