diff options
| -rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
| -rw-r--r-- | src/client/views/DocComponent.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/StyleProvider.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 13 | ||||
| -rw-r--r-- | src/client/views/nodes/LabelBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.scss | 24 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 143 | ||||
| -rw-r--r-- | src/client/views/nodes/WebBox.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextMenu.tsx | 2 |
12 files changed, 119 insertions, 96 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df08345f9..286b7afa9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -152,6 +152,7 @@ export interface DocumentOptions { _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide) displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) + undisplayTimecode?: number; // the time that a document should be hidden lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears @@ -227,7 +228,7 @@ export interface DocumentOptions { isLabel?: boolean; // whether the document is a label or not (video / audio) useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox audioStart?: number; // the time frame where the audio should begin playing - audioEnd?: number; // the time frame where the audio should stop playing + audioEnd?: number; // the time frame where the audio should stop playing border?: string; //for searchbox hovercolor?: string; } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 2c7d15ae0..db0b626a1 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -124,7 +124,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; } @action.bound - removeDocument(doc: Doc | Doc[]): boolean { + removeDocument(doc: Doc | Doc[], annotationKey?: string): boolean { const effectiveAcl = GetEffectiveAcl(this.dataDoc); const indocs = doc instanceof Doc ? [doc] : doc; const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin); @@ -132,13 +132,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T const docs = doc instanceof Doc ? [doc] : doc; docs.map(doc => doc.isPushpin = doc.annotationOn = undefined); const targetDataDoc = this.dataDoc; - const value = DocListCast(targetDataDoc[this.annotationKey]); + const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc; toRemove.forEach(doc => { - Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey + "-annotations", doc); + Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); }); return true; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 0ab2d1ecf..8ef69802b 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -163,13 +163,13 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100); FormattedTextBox.SelectOnLoad = target[Id]; return target; - } + }; const anchorCreator = () => { const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing? this.props.addDocument(annoDoc); return annoDoc; - } + }; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 1d822439a..95a28b33e 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -108,9 +108,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break; case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break; case DocumentType.SLIDER: break; - case DocumentType.LABEL: docColor = docColor || (doc?.audioStart !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; + case DocumentType.LABEL: docColor = docColor || (doc?.audioStart !== undefined || doc?.displayTimecode !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break; case DocumentType.LINK: return "transparent"; + case DocumentType.VID: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break; case DocumentType.COL: if (StrCast(Doc.LayoutField(doc)).includes("SliderBox")) break; docColor = docColor ? docColor : diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2bc716928..bc86ecd19 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -196,7 +196,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); } - public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document._currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } + public isCurrent(doc: Doc) { + const dispTime = NumCast(doc.displayTimecode, -1); + const endTime = NumCast(doc.undisplayTimecode, dispTime + 1.5); + const curTime = NumCast(this.Document._currentTimecode, -1); + return dispTime === -1 || (curTime > dispTime && curTime < endTime); + } public getActiveDocuments = () => { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 77777ff76..4ddb0502b 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -29,6 +29,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; import { LinkDocPreview } from "./LinkDocPreview"; import "./AudioBox.scss"; +import { Id } from "../../../fields/FieldSymbols"; declare class MediaRecorder { // whatever MediaRecorder has @@ -390,9 +391,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD if (audioStart === undefined) return this.rootDoc; const marker = Docs.Create.LabelDocument({ title: ComputedField.MakeFunction(`"#" + formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any, - useLinkSmallAnchor: true, hideLinkButton: true, audioStart, audioEnd, _showSidebar: false, - isLabel: audioEnd === undefined, - _autoHeight: true, annotationOn: this.props.Document + useLinkSmallAnchor: true, + hideLinkButton: true, + audioStart, + audioEnd, + annotationOn: this.props.Document }); if (this.dataDoc[this.annotationKey]) { this.dataDoc[this.annotationKey].push(marker); @@ -571,13 +574,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD <div className="waveform"> {this.waveform} </div> - {drawMarkers.map((d, i) => { + {drawMarkers.map(d => { const m = d.marker; const left = NumCast(m.audioStart) / this.audioDuration * timelineContentWidth; const top = d.level / maxLevel * timelineContentHeight; const timespan = m.audioEnd === undefined ? 10 / timelineContentWidth * this.audioDuration : NumCast(m.audioEnd) - NumCast(m.audioStart); return this.layoutDoc.hideMarkers ? (null) : - <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={i} + <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]} style={{ left, top, width: `${timespan / this.audioDuration * 100}%`, height: `${1 / maxLevel * 100}%` }} onClick={e => { this.playFrom(NumCast(m.audioStart), Cast(m.audioEnd, "number", null)); e.stopPropagation(); }} > {this.renderMarker(m, this.rangeClickScript, this.rangePlayScript, diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 3448a4abd..87d5b07a2 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -67,7 +67,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument 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 = StrCast(this.rootDoc[this.fieldKey], StrCast(this.rootDoc.title)); + const label = typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); return ( <div className="labelBox-outerDiv" onClick={action(() => this.clicked = !this.clicked)} diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 76edda847..8bba5d1ff 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -13,15 +13,13 @@ .audiobox-timeline { position: absolute; - height: 20%; width: 100%; - bottom: 0px; - background: white; + background: beige; border: gray solid 1px; border-radius: 3px; z-index: 1000; overflow: hidden; - left: 0px; + bottom: 0; .audiobox-current { width: 1px; @@ -46,18 +44,23 @@ border-width: 1px; } - .audiobox-marker-container, + .audiobox-marker-timeline, .audiobox-marker-minicontainer { position: absolute; width: 10px; height: 10px; top: 2.5%; - background: gray; border-radius: 50%; box-shadow: black 2px 2px 1px; overflow: visible; cursor: pointer; + .left-resizer { + background: dimgrey; + } + .resizer { + background: dimgrey; + } .audiobox-marker { position: relative; height: 100%; @@ -77,10 +80,8 @@ width: 10px; height: 90%; top: 2.5%; - background: gray; border-radius: 5px; box-shadow: black 2px 2px 1px; - opacity: 0.3; .audiobox-marker { position: relative; @@ -94,10 +95,12 @@ .resizer { position: absolute; + top: 0; right: 0; + pointer-events: all; cursor: ew-resize; height: 100%; - width: 2px; + width: 10px; z-index: 100; } @@ -111,9 +114,10 @@ .left-resizer { position: absolute; left: 0; + top: 0; cursor: ew-resize; height: 100%; - width: 2px; + width: 10px; z-index: 100; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 2ac105545..608d7daa3 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -26,10 +26,13 @@ import { List } from "../../../fields/List"; import { DocumentView } from "./DocumentView"; import { LinkDocPreview } from "./LinkDocPreview"; import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; -import { Transform } from "../../util/Transform"; import { StyleProp } from "../StyleProvider"; import { computedFn } from "mobx-utils"; import { DocumentManager } from "../../util/DocumentManager"; +import { Dictionary } from "typescript-collections"; +import { MarqueeAnnotator } from "../MarqueeAnnotator"; +import { Id } from "../../../fields/FieldSymbols"; +import { LabelBox } from "./LabelBox"; const path = require('path'); export const timeSchema = createSchema({ @@ -47,16 +50,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD static RangePlayScript: ScriptField; static LabelPlayScript: ScriptField; static heightPercent = 20; // height of timeline in percent of height of videoBox. - private _reactionDisposer?: IReactionDisposer; - private _youtubeReactionDisposer?: IReactionDisposer; - // private _reactionDisposer?: IReactionDisposer; - // private _youtubeReactionDisposer?: IReactionDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; private _isResetClick = 0; + private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); + private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); + @observable _marqueeing: number[] | undefined; + @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); _play: any = null; _timeline: Opt<HTMLDivElement>; _audioRef = React.createRef<HTMLDivElement>(); @@ -75,7 +78,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @observable _playing = false; @observable static _showControls: boolean; @computed get videoDuration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); } - @computed get markerDocs() { return DocListCast(this.dataDoc[this.annotationKey]); } + @computed get markerDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); } public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } public get player(): HTMLVideoElement | null { @@ -87,10 +90,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD VideoBox.Instance = this; // onClick play scripts - VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!; - VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!; - VideoBox.RangePlayScript = VideoBox.RangePlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!; - VideoBox.LabelPlayScript = VideoBox.LabelPlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!; + VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.displayTimecode, this.undisplayTimecode)`, { self: Doc.name, scriptContext: "any" })!; + VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.displayTimecode)`, { self: Doc.name, scriptContext: "any" })!; + VideoBox.RangePlayScript = VideoBox.RangePlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.displayTimecode, this.undisplayTimecode)`, { self: Doc.name, scriptContext: "any" })!; + VideoBox.LabelPlayScript = VideoBox.LabelPlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.displayTimecode)`, { self: Doc.name, scriptContext: "any" })!; } videoLoad = () => { @@ -240,7 +243,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { const delay = this.player ? 0 : 250; // wait for mainCont and try again to play setTimeout(() => this.player && this.Play(), delay); - setTimeout(() => { this.Document._videoStart = undefined; }, 10 + delay); + setTimeout(() => this.Document._videoStart = undefined, 10 + delay); } } }, @@ -349,9 +352,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD <source src={field.url.href} type="video/mp4" /> Not supported. </video> - {this.uIButtons} </div> - {this.renderTimeline} </div>; } @@ -412,9 +413,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD </div>, <div className="timeline-button" key="timeline-button" onPointerDown={this.toggleTimeline} style={{ position: "absolute", - bottom: "41px", - right: this.layoutDoc._showTimeline ? "235px" : "155px", - color: "lightgrey", + bottom: 0, + right: 0, + zIndex: 1001, + color: "white", + background: "dimgrey", width: "20px" }}> <FontAwesomeIcon icon={this.layoutDoc._showTimeline ? "eye-slash" : "eye"} style={{ width: "100%" }} /> @@ -557,17 +560,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } @action - createMarker(audioStart: number, audioEnd?: number) { + createMarker(displayTimecode: number, undisplayTimecode?: number) { const marker = Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction(`formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any, isLabel: audioEnd === undefined, - useLinkSmallAnchor: true, hideLinkButton: true, audioStart, audioEnd, _showSidebar: false, - _autoHeight: true, annotationOn: this.props.Document + title: ComputedField.MakeFunction(`"#" + formatToTime(self.displayTimecode) + "-" + formatToTime(self.undisplayTimecode)`) as any, + useLinkSmallAnchor: true, // bcz: note this also flags that the annotation is not on the video itself, just the timeline + hideLinkButton: true, + displayTimecode, + undisplayTimecode, + annotationOn: this.props.Document }); - marker.data = ""; // clears out the label's text so that only its border will display - if (this.dataDoc[this.annotationKey]) { - this.dataDoc[this.annotationKey].push(marker); + if (this.dataDoc[this.annotationKey + "-timeline"]) { + this.dataDoc[this.annotationKey + "-timeline"].push(marker); } else { - this.dataDoc[this.annotationKey] = new List<Doc>([marker]); + this.dataDoc[this.annotationKey + "-timeline"] = new List<Doc>([marker]); } } @@ -609,14 +614,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } // makes sure no markers overlaps each other by setting the correct position and width - getLevel = (m: any, placed: { audioStart: number, audioEnd: number, level: number }[]) => { + getLevel = (m: any, placed: { videoStart: number, videoEnd: number, level: number }[]) => { const timelineContentWidth = this.props.PanelWidth(); - const x1 = m.audioStart; - const x2 = m.audioEnd === undefined ? m.audioStart + 10 / timelineContentWidth * this.videoDuration : m.audioEnd; + const x1 = m.displayTimecode; + const x2 = m.undisplayTimecode === undefined ? m.displayTimecode + 10 / timelineContentWidth * this.videoDuration : m.undisplayTimecode; let max = 0; const overlappedLevels = new Set(placed.map(p => { - const y1 = p.audioStart; - const y2 = p.audioEnd; + const y1 = p.videoStart; + const y2 = p.videoEnd; if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) || (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) { max = Math.max(max, p.level); @@ -626,23 +631,25 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD let level = max + 1; for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j); - placed.push({ audioStart: x1, audioEnd: x2, level }); + placed.push({ videoStart: x1, videoEnd: x2, level }); return level; } // renders the markers as a document - renderInner = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) { + renderInner = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) { const marker = observable({ view: undefined as any }); return { - marker, view: <DocumentView key="view" {...this.props} ref={action((r: DocumentView | null) => marker.view = r)} + marker, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} ref={action((r: DocumentView | null) => marker.view = r)} Document={mark} + DataDoc={undefined} PanelWidth={() => width} PanelHeight={() => height} rootSelected={returnFalse} LayoutTemplate={undefined} + LayoutTemplateString={LabelBox.LayoutString("data")} ContainingCollectionDoc={this.props.Document} - removeDocument={this.removeDocument} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x - 4, -y - 3)} + removeDocument={(doc: Doc | Doc[]) => this.removeDocument(doc, annotationKey)} + ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).translate(-x - 4, -y - 3)} parentActive={(out) => this.props.isSelected(out) || this._isChildActive} whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))} onClick={script} @@ -653,8 +660,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD }; }); - renderMarker = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) { - const inner = this.renderInner(mark, script, doublescript, x, y, width, height); + renderMarker = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) { + const inner = this.renderInner(mark, script, doublescript, x, y, width, height, annotationKey); return <> {inner.view} {!inner.marker.view || !SelectionManager.IsSelected(inner.marker.view) ? (null) : @@ -667,32 +674,34 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD // returns the timeline @computed get renderTimeline() { - const rect = this._timeline?.getBoundingClientRect(); const timelineContentWidth = this.props.PanelWidth(); - //const timelineContentWidth = this.layoutDoc._showTimeline ? this.props.PanelWidth() * 1.25 : this.props.PanelWidth(); - //const timelineContentWidth = rect ? rect.width : this.props.PanelWidth(); - const timelineContentHeight = (this.props.PanelHeight() * VideoBox.heightPercent / 100); // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) - const overlaps: { audioStart: number, audioEnd: number, level: number }[] = []; - const drawMarkers = this.markerDocs.map((m, i) => ({ level: this.getLevel(m, overlaps), marker: m })); + const timelineContentHeight = this.props.PanelHeight() * VideoBox.heightPercent / 100; + const overlaps: { videoStart: number, videoEnd: number, level: number }[] = []; + const drawMarkers: { level: number, marker: Doc }[] = this.markerDocs.map((m, i) => ({ level: this.getLevel(m, overlaps), marker: m })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; return !this.layoutDoc._showTimeline ? (null) : - <div className="audiobox-timeline" ref={this.timelineRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ height: `${VideoBox.heightPercent}%` }} - onPointerDown={e => e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e)}> - {drawMarkers.map((d, i) => { + <div className="audiobox-timeline" ref={this.timelineRef} style={{ height: `${VideoBox.heightPercent}%` }} + onClick={e => { if (this._isChildActive || this.props.isSelected()) { e.stopPropagation(); e.preventDefault(); } }} + onPointerDown={e => { + if (this._isChildActive || this.props.isSelected()) { + e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e); + } + }}> + {drawMarkers.map(d => { const m = d.marker; - const left = NumCast(m.audioStart) / this.videoDuration; - const l = `${NumCast(m.audioStart) / this.videoDuration * 100}%`; + const start = NumCast(m.displayTimecode, NumCast(m.displayTimecode, null)); + const left = start / this.videoDuration * timelineContentWidth; const top = d.level / maxLevel * timelineContentHeight; - const timespan = m.audioEnd === undefined ? 10 / timelineContentWidth * this.videoDuration : NumCast(m.audioEnd) - NumCast(m.audioStart); + const timespan = m.undisplayTimecode === undefined ? 10 / timelineContentWidth * this.videoDuration : NumCast(m.undisplayTimecode) - NumCast(m.displayTimecode); return this.layoutDoc.hideMarkers ? (null) : - <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={i} - style={{ left: l, top, width: `${timespan / this.videoDuration * 100}%`, height: `${1 / maxLevel * 100}%` }} - onClick={e => { this.playFrom(NumCast(m.audioStart), Cast(m.audioEnd, "number", null)); e.stopPropagation(); }} > + <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]} + style={{ left, top, width: `${timespan / this.videoDuration * 100}%`, height: `${1 / maxLevel * 100}%` }} + onClick={e => { this.playFrom(start, Cast(m.undisplayTimecode, "number", null)); e.stopPropagation(); }} > {this.renderMarker(m, this.rangeClickScript, this.rangePlayScript, left, - top, + top + (this.props.PanelHeight() - timelineContentHeight), timelineContentWidth * timespan / this.videoDuration, - timelineContentHeight / maxLevel)} + timelineContentHeight / maxLevel, this.annotationKey + (m.useLinkSmallAnchor ? "-timeline" : ""))} </div>; })} {this.selectionContainer} @@ -703,13 +712,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD // updates the marker with the new time @action changeMarker = (m: any, time: any) => { - DocListCast(this.dataDoc[this.annotationKey]).filter(marker => this.isSame(marker, m)).forEach(marker => - this._left ? marker.audioStart = time : marker.audioEnd = time); + this.markerDocs.filter(marker => this.isSame(marker, m)).forEach(marker => + this._left ? marker.displayTimecode = time : marker.undisplayTimecode = time); } // checks if the two markers are the same with start and end time isSame = (m1: any, m2: any) => { - return m1.audioStart === m2.audioStart && m1.audioEnd === m2.audioEnd; + return m1.displayTimecode === m2.displayTimecode && m1.undisplayTimecode === m2.undisplayTimecode; } // returns the blue container when dragging @@ -753,17 +762,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; @computed get annotationLayer() { - return <div className="imageBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer} />; + return <div className="imageBox-annotationLayer" style={{ height: "100%" }} ref={this._annotationLayer} />; } marqueeDown = action((e: React.PointerEvent) => { if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; - }) + }); finishMarquee = action(() => { this._marqueeing = undefined; this.props.select(true); - }) + }); render() { const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); @@ -781,7 +790,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD select={emptyFunction} active={this.annotationsActive} scaling={returnOne} - ScreenToLocalTransform={this.screenToLocalTransform} + PanelWidth={() => this.props.PanelWidth() * (this.layoutDoc._showTimeline ? .8 : 1)} + PanelHeight={() => this.props.PanelHeight() * (this.layoutDoc._showTimeline ? .8 : 1)} + ScreenToLocalTransform={() => this.screenToLocalTransform().scale(this.layoutDoc._showTimeline ? 1 / .8 : 1)} whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} @@ -790,14 +801,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD renderDepth={this.props.renderDepth + 1}> {this.contentFunc} </CollectionFreeFormView> + {this.uIButtons} + {this.annotationLayer} + {this.renderTimeline} + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : + <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocumentWithTimestamp} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />} </div> -<<<<<<< HEAD -======= - {this.uIButtons} - {this.annotationLayer} - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocumentWithTimestamp} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />} ->>>>>>> 10b759d2bd09af3a8e8a4effbc8fd2312dd873d2 </div >); } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 69f797880..37f268823 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -194,7 +194,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum this._iframe?.removeEventListener('wheel', this.iframeWheel); } - onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); } + onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); }; @undoBatch @action @@ -280,8 +280,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } _ignore = 0; - onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; } - onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; } + onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; }; + onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; }; onPostPointer = (e: React.PointerEvent) => { if (this._ignore !== e.timeStamp) e.stopPropagation(); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ac5ea66ff..7348ebdd2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -230,7 +230,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100); FormattedTextBox.SelectOnLoad = target[Id]; return target; - } + }; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, { dragComplete: e => { diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 992194e2b..dc630af74 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -604,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { e.stopPropagation(); self.TextView.endUndoTypingBatch(); UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush"); - } + }; let label = "Stored marks: "; if (this.brushMarks && this.brushMarks.size > 0) { |
