aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/DocumentDecorations.tsx4
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx30
-rw-r--r--src/client/views/nodes/AudioBox.scss121
-rw-r--r--src/client/views/nodes/AudioBox.tsx30
-rw-r--r--src/client/views/nodes/DocumentView.tsx5
-rw-r--r--src/client/views/nodes/LabelBox.tsx4
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
7 files changed, 159 insertions, 45 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e9a54d6a5..c4f6625fc 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -429,8 +429,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
const canDelete = SelectionManager.Views().some(docView => {
const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
- return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
- (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
+ //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
+ return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
});
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 89da6692a..970947b12 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -30,7 +30,7 @@ import { LinkManager } from "../../util/LinkManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { AudioWaveform } from "../AudioWaveform";
import { CollectionSubView } from "../collections/CollectionSubView";
import { LightboxView } from "../LightboxView";
@@ -354,6 +354,14 @@ export class CollectionStackedTimeline extends CollectionSubView<
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
+ const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const x = localPt[0] - docDragData.offset[0];
+ const timelineContentWidth = this.props.PanelWidth();
+ for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
+ const d = Doc.GetProto(docDragData.droppedDocuments[i]);
+ d._timecodeToHide = x / timelineContentWidth * this.props.trimDuration + NumCast(d._timecodeToHide) - NumCast(d._timecodeToShow);
+ d._timecodeToShow = x / timelineContentWidth * this.props.trimDuration;
+ }
return true;
}
@@ -573,11 +581,15 @@ export class CollectionStackedTimeline extends CollectionSubView<
);
const left = this.props.trimming ?
(start / this.duration) * timelineContentWidth
- : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
- const top = (d.level / maxLevel) * this.timelineContentHeight();
+ : Math.max((start - this.trimStart) / this.props.trimDuration * timelineContentWidth, 0);
+ const top = (d.level / maxLevel) * this.timelineContentHeight() + 15;
const timespan = end - start;
- const width = (timespan / this.props.trimDuration) * timelineContentWidth;
- const height = this.timelineContentHeight() / maxLevel;
+ let width = (timespan / this.props.trimDuration) * timelineContentWidth;
+ width = (!this.props.trimming && left == 0) ?
+ width - ((this.trimStart - start) / this.props.trimDuration * timelineContentWidth) : width;
+ width = (!this.props.trimming && this.trimEnd < end) ?
+ width - ((end - this.trimEnd) / this.props.trimDuration * timelineContentWidth) : width;
+ const height = (this.timelineContentHeight()) / maxLevel;
return this.props.Document.hideAnchors ? null : (
<div
className={"collectionStackedTimeline-marker-timeline"}
@@ -763,13 +775,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
}
return false;
};
+ var undo: UndoManager.Batch | undefined;
+
setupMoveUpEvents(
this,
e,
- (e) => changeAnchor(anchor, left, newTime(e)),
+ (e) => {
+ if (!undo) undo = UndoManager.StartBatch("drag anchor");
+ return changeAnchor(anchor, left, newTime(e))
+ },
(e) => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
+ undo?.end();
},
emptyFunction
);
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index a6494e540..6adda4730 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -3,13 +3,100 @@
.audiobox-container,
.audiobox-container-interactive {
+ width: 100%;
+ height: 100%;
+ position: inherit;
+ display: flex;
+ position: relative;
+ cursor: default;
+
+ .audiobox-buttons {
+ display: flex;
+ width: 100%;
+ align-items: center;
+
+ .audiobox-dictation {
+ position: relative;
+ width: 30px;
+ height: 100%;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ left: 0px;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ cursor: pointer;
+ }
+ }
+ }
+
+ .audiobox-control,
+ .audiobox-control-interactive {
+ top: 0;
+ max-height: 32px;
+ width: 100%;
+ display: inline-block;
+ pointer-events: none;
+ }
+
+ .audiobox-control-interactive {
+ pointer-events: all;
+ }
+
+ .audiobox-record-interactive,
+ .audiobox-record {
+ pointer-events: all;
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ color: white;
+ font-weight: bold;
+ }
+
+ .audiobox-record {
+ pointer-events: none;
+ }
+
+ .recording {
+ margin-top: auto;
+ margin-bottom: auto;
width: 100%;
height: 100%;
- position: inherit;
- display: flex;
position: relative;
- cursor: default;
+ padding-right: 5px;
+ display: flex;
+ background-color: $medium-blue;
+
+ .time {
+ position: relative;
+ width: 100%;
+ font-size: $large-header;
+ text-align: center;
+ }
+
+ .recording-buttons {
+ position: relative;
+ margin-top: auto;
+ margin-bottom: auto;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ }
+ }
+ .time, .recording-buttons {
+ display: flex;
+ align-items: center;
+ padding: 5px;
+ }
+ }
.audiobox-buttons {
display: flex;
width: 100%;
@@ -46,26 +133,6 @@
pointer-events: all;
}
- .audiobox-record-interactive,
- .audiobox-record {
- pointer-events: all;
- cursor: pointer;
- width: 100%;
- height: 100%;
- position: relative;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- gap: 10px;
- color: white;
- font-weight: bold;
- }
-
- .audiobox-record {
- pointer-events: none;
- }
-
.recording {
margin-top: auto;
margin-bottom: auto;
@@ -195,6 +262,12 @@
.audioBox-total-time {
right: 2px;
}
+
+ .audiobox-zoom {
+ bottom: 0;
+ left: 30px;
+ width: 70px;
+ }
}
}
}
@@ -219,4 +292,4 @@
.audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-buttons {
width: 70px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c79828470..06f1c4ae1 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -6,7 +6,7 @@ import {
IReactionDisposer,
observable,
reaction,
- runInAction,
+ runInAction
} from "mobx";
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
@@ -16,23 +16,24 @@ import { makeInterface } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DragManager } from "../../util/DragManager";
import { SnappingManager } from "../../util/SnappingManager";
import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import {
ViewBoxAnnotatableComponent,
- ViewBoxAnnotatableProps,
+ ViewBoxAnnotatableProps
} from "../DocComponent";
+import { Colors } from "../global/globalEnums";
import "./AudioBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
import { LinkDocPreview } from "./LinkDocPreview";
-import { faLessThan } from "@fortawesome/free-solid-svg-icons";
-import { Colors } from "../global/globalEnums";
+import e = require("connect-flash");
declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
@@ -166,11 +167,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
}
componentWillUnmount() {
+ this.dropDisposer?.();
Object.values(this._disposers).forEach((disposer) => disposer?.());
const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1);
}
+ private dropDisposer?: DragManager.DragDropDisposer;
@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.
@@ -545,7 +548,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
return (
<CollectionStackedTimeline
ref={this._stackedTimeline}
- {...this.props}
+ {...OmitKeys(this.props, ["CollectionFreeFormDocumentView"]).omit}
fieldKey={this.annotationKey}
dictationKey={this.fieldKey + "-dictation"}
mediaPath={this.path}
@@ -590,6 +593,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
: "";
return (
<div
+ ref={r => {
+ if (r && this._stackedTimeline.current) {
+ this.dropDisposer?.();
+ this.dropDisposer = DragManager.MakeDropTarget(r,
+ (e, de) => {
+ const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ de.complete.docDragData && this._stackedTimeline.current!.internalDocDrop(e, de, de.complete.docDragData, xp);
+ }
+ , this.layoutDoc, undefined);
+ }
+ }}
className="audiobox-container"
onContextMenu={this.specificContextMenu}
onClick={
@@ -606,9 +620,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
<div className="audiobox-buttons">
<div className="audiobox-dictation" onClick={this.onFile}>
<FontAwesomeIcon
- style={{
- width: "30px"
- }}
+ style={{ width: "30px" }}
icon="file-alt"
size={this.props.PanelHeight() < 36 ? "1x" : "2x"}
/>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5d0b91b91..b44b32832 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -736,9 +736,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
!zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
- onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
!Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
- onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+ this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
if (!this.Document.annotationOn) {
const options = cm.findByDescription("Options...");
@@ -788,6 +788,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
}
+ !more && moreItems.length && cm.addItem({ description: "More...", subitems: moreItems, icon: "compass" });
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
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
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e3ba638b3..3fc460102 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -61,9 +61,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable _playing = false;
+ @observable _trimming: boolean = false;
+ @observable _trimStart: number = NumCast(this.layoutDoc.clipStart) ? NumCast(this.layoutDoc.clipStart) : 0;
+ @observable _trimEnd: number = NumCast(this.layoutDoc.clipEnd) ? NumCast(this.layoutDoc.clipEnd)
+ : this.duration;
+
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
@computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @computed get trimDuration() {
+ return this._trimming && this._trimEnd ? this.duration : this._trimEnd - this._trimStart;
+ }
private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; }
public get player(): HTMLVideoElement | null { return this._videoRef; }
@@ -202,6 +210,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@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) {