aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLionel Han <47760119+IGoByJoe@users.noreply.github.com>2021-01-21 17:30:02 -0800
committerLionel Han <47760119+IGoByJoe@users.noreply.github.com>2021-01-21 17:30:02 -0800
commitf0a5763946d6b717fb745f2216dc1e681e3e0c68 (patch)
treeb544a362993b76cda6c40333c82e6b01b5341a17 /src
parentfd4a1c602396362e9f0a1effa87fc751614e541b (diff)
parentba3bcbbf3f0ce8769acb6e84ac3aa8ac3a5c9694 (diff)
final fixes
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts16
-rw-r--r--src/client/util/CurrentUserUtils.ts7
-rw-r--r--src/client/util/DragManager.ts8
-rw-r--r--src/client/views/DocumentButtonBar.tsx11
-rw-r--r--src/client/views/DocumentDecorations.tsx63
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/InkStrokeProperties.ts58
-rw-r--r--src/client/views/InkingStroke.tsx2
-rw-r--r--src/client/views/MainView.scss1
-rw-r--r--src/client/views/MainView.tsx41
-rw-r--r--src/client/views/StyleProvider.tsx12
-rw-r--r--src/client/views/collections/CollectionMenu.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/TabDocView.tsx18
-rw-r--r--src/client/views/collections/TreeView.scss27
-rw-r--r--src/client/views/collections/TreeView.tsx24
-rw-r--r--src/client/views/nodes/AudioBox.scss26
-rw-r--r--src/client/views/nodes/AudioBox.tsx451
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx39
-rw-r--r--src/client/views/nodes/FilterBox.tsx14
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/LabelBox.tsx3
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx1
-rw-r--r--src/client/views/nodes/PresBox.tsx35
-rw-r--r--src/client/views/nodes/SliderBox-components.tsx3
-rw-r--r--src/client/views/nodes/SliderBox.scss14
-rw-r--r--src/client/views/nodes/SliderBox.tsx17
-rw-r--r--src/client/views/nodes/VideoBox.scss24
-rw-r--r--src/client/views/nodes/VideoBox.tsx331
-rw-r--r--src/client/views/nodes/WebBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx3
-rw-r--r--src/client/views/pdf/PDFViewer.tsx2
-rw-r--r--src/client/views/search/SearchBox.scss29
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/mobile/MobileInterface.tsx2
37 files changed, 701 insertions, 601 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8fd906dc7..b32cbd3d0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,4 +1,4 @@
-import { runInAction, action } from "mobx";
+import { action, runInAction } from "mobx";
import { basename, extname } from "path";
import { DateField } from "../../fields/DateField";
import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
@@ -42,19 +42,19 @@ import { ImageBox } from "../views/nodes/ImageBox";
import { KeyValueBox } from "../views/nodes/KeyValueBox";
import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
+import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
import { PDFBox } from "../views/nodes/PDFBox";
import { PresBox } from "../views/nodes/PresBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { SliderBox } from "../views/nodes/SliderBox";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { PresElementBox } from "../views/presentationview/PresElementBox";
import { SearchBox } from "../views/search/SearchBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
const path = require('path');
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -113,6 +113,7 @@ export interface DocumentOptions {
page?: number;
description?: string; // added for links
_viewScale?: number;
+ _overflow?: string;
forceActive?: boolean;
layout?: string | Doc; // default layout string for a document
contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
@@ -609,7 +610,7 @@ export namespace Docs {
proto.links = ComputedField.MakeFunction("links(self)");
viewDoc.author = Doc.CurrentUserEmail;
- viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
+ viewDoc.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc);
viewDoc["acl-Public"] = dataDoc["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
viewDoc["acl-Override"] = dataDoc["acl-Override"] = "None";
@@ -1007,10 +1008,10 @@ export namespace DocUtils {
});
}
- export let ActiveRecordings: Doc[] = [];
+ export let ActiveRecordings: AudioBox[] = [];
export function MakeLinkToActiveAudio(doc: Doc) {
- DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+ DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d.getAnchor() || d.props.Document }, "audio link", "audio timeline"));
}
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
@@ -1250,14 +1251,13 @@ export namespace DocUtils {
});
});
if (x !== undefined && y !== undefined) {
- const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100 });
+ const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _overflow: "visible" });
newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
newCollection._width = newCollection._height = 110;
//newCollection.borderRounding = "40px";
newCollection._jitterRotation = 10;
newCollection._backgroundColor = "gray";
- newCollection._overflow = "visible";
return newCollection;
}
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 00ee0f4b9..382b225a4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -795,10 +795,11 @@ export class CurrentUserUtils {
treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true,
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
}));
- const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
- (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
- (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
}
+ const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([]); scriptContext._docFilters = scriptContext._docRangeFilters = undefined;`, { scriptContext: Doc.name });
+ (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
+ (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
+
}
static setupUserDoc(doc: Doc) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d24348746..52ccfda74 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -529,15 +529,16 @@ export namespace DragManager {
endDrag();
};
const upHandler = (e: PointerEvent) => {
- dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
- options?.dragComplete?.(new DragCompleteEvent(false, dragData));
+ const complete = new DragCompleteEvent(false, dragData);
+ dispatchDrag(eles, e, complete, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
+ options?.dragComplete?.(complete);
endDrag();
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any },
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, complete: DragCompleteEvent,
xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
const removed = dragEles.map(dragEle => {
const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow };
@@ -558,7 +559,6 @@ export namespace DragManager {
});
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
if (target) {
- const complete = new DragCompleteEvent(false, dragData);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashPreDrop", {
bubbles: true,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 96f5d78fd..a2bd0aad9 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -186,20 +186,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
@computed
get pinButton() {
const targetDoc = this.view0?.props.Document;
- const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}</div></>}>
+ return !targetDoc ? (null) : <Tooltip title={
+ <div className="dash-tooltip">{SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}</div>}>
<div className="documentButtonBar-linker"
style={{ color: "white" }}
- onClick={undoBatch(e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, false)))}>
+ onClick={undoBatch(e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, { setPosition: e.shiftKey ? true : undefined })))}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
- </div></Tooltip>;
+ </div>
+ </Tooltip>;
}
@undoBatch
@action
pinWithView = (targetDoc: Doc) => {
if (targetDoc) {
- TabDocView.PinDoc(targetDoc, false);
+ TabDocView.PinDoc(targetDoc);
const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
const scrollable: boolean = (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking);
const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 372dc5456..e1b9fe8aa 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -62,7 +62,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
- return SelectionManager.Views().map(dv => dv.getBounds()).reduce((bounds, rect) =>
+ const boudns = SelectionManager.Views().map(dv => dv.getBounds()).reduce((bounds, rect) =>
!rect ? bounds :
{
x: Math.min(rect.left, bounds.x),
@@ -71,6 +71,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
b: Math.max(rect.bottom, bounds.b)
},
{ x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+ return boudns;
}
titleBlur = action((commit: boolean) => {
@@ -275,7 +276,6 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
// doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale;
doc._height = (bottom - top);
doc._width = (right - left);
-
}
index++;
}
@@ -423,7 +423,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
const width = (doc._width || 0);
let height = (doc._height || (nheight / nwidth * width));
height = !height || isNaN(height) ? 20 : height;
- const scale = docView.props.ScreenToLocalTransform().Scale * docView.ContentScale();
+ const scale = docView.props.ScreenToLocalTransform().Scale;
if (nwidth && nheight) {
if (nwidth / nheight !== width / height && !dragBottom) {
height = nheight / nwidth * width;
@@ -559,7 +559,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
const canDelete = SelectionManager.Views().some(docView => {
const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
const docAcl = GetEffectiveAcl(docView.props.Document);
- return !docView.props.Document._stayInCollection && (collectionAcl === AclAdmin || collectionAcl === AclEdit || docAcl === AclAdmin);
+ return (!docView.props.Document._stayInCollection || docView.props.Document.isInkMask) &&
+ (collectionAcl === AclAdmin || collectionAcl === AclEdit || docAcl === AclAdmin);
});
const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection);
const closeIcon = !canDelete ? (null) : (
@@ -568,7 +569,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
<FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
</div></Tooltip>);
- const openIcon = !canOpen ? (null) : <Tooltip title={<div className="dash-tooltip">Open in Tab (ctrl: as alias, shift: in new collection)</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
+ const openIcon = !canOpen ? (null) : <Tooltip key="open" title={<div className="dash-tooltip">Open in Tab (ctrl: as alias, shift: in new collection)</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
{SelectionManager.Views().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
</div>
</Tooltip>;
@@ -612,31 +613,35 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
}}>
{closeIcon}
{bounds.r - bounds.x < 100 ? null : titleArea}
- {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <Tooltip title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
- <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
- <FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" />
- </div>
- </Tooltip>}
- {openIcon}
- <div className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-centerCont"></div>
- <div className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- <div className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
- {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- <Tooltip title={<div className="dash-tooltip">tap to select containing document</div>} placement="top">
- <div className="documentDecorations-levelSelector"
- onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={"arrow-alt-circle-up"} size="lg" />
- </div></Tooltip>}
- <div className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
+ {(seldoc.rootDoc.annotationOn as Doc)?.type === DocumentType.AUDIO ? (null) :
+ <>
+ {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
+ <Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
+ <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
+ <FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" />
+ </div>
+ </Tooltip>}
+ {openIcon}
+ <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="l" className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="c" className="documentDecorations-centerCont"></div>
+ <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+ <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+
+ {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+ <Tooltip key="level" title={<div className="dash-tooltip">tap to select containing document</div>} placement="top">
+ <div className="documentDecorations-levelSelector"
+ onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={"arrow-alt-circle-up"} size="lg" />
+ </div></Tooltip>}
+ </>
+ }
+ <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`}
onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div>
-
</div >
{seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
<DocumentButtonBar views={SelectionManager.Views} />
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c38842c4f..8a07c5321 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -27,6 +27,7 @@ import { SnappingManager } from "../util/SnappingManager";
import { SearchBox } from "./search/SearchBox";
import { random } from "lodash";
import { DocumentView } from "./nodes/DocumentView";
+import { AudioBox } from "./nodes/AudioBox";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -121,6 +122,9 @@ export class KeyManager {
DragManager.AbortDrag();
} else if (CollectionDockingView.Instance.HasFullScreen) {
CollectionDockingView.Instance.CloseFullScreen();
+ } else if (AudioBox.SelectingRegion) {
+ AudioBox.SelectingRegion = undefined;
+ doDeselect = false;
} else {
doDeselect = !ContextMenu.Instance.closeMenu();
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 2dac44bf8..ce7f3d8d9 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -14,6 +14,7 @@ export class InkStrokeProperties {
private _lastFill = "#D0021B";
private _lastLine = "#D0021B";
private _lastDash = "2";
+ private _inkDocs: { x: number, y: number, width: number, height: number }[] = [];
@observable _lock = false;
@observable _controlBtn = false;
@@ -205,45 +206,42 @@ export class InkStrokeProperties {
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
const ink = Cast(doc.data, InkField)?.inkData;
if (ink) {
-
const newPoints: { X: number, Y: number }[] = [];
const order = controlNum % 4;
for (var i = 0; i < ink.length; i++) {
- if (controlNum === i ||
- (order === 0 && i === controlNum + 1) ||
- (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
- (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
- (order === 3 && i === controlNum - 1) ||
- (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
- (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
- || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
- ) {
- newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
- }
- else {
- newPoints.push({ X: ink[i].X, Y: ink[i].Y });
- }
+ newPoints.push(
+ (controlNum === i ||
+ (order === 0 && i === controlNum + 1) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
+ (order === 3 && i === controlNum - 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2) ||
+ ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
+ ) ?
+ { X: ink[i].X - xDiff, Y: ink[i].Y - yDiff } :
+ { X: ink[i].X, Y: ink[i].Y });
}
const oldx = doc.x;
const oldy = doc.y;
- const xs = ink.map(p => p.X);
- const ys = ink.map(p => p.Y);
- const left = Math.min(...xs);
- const top = Math.min(...ys);
+ const oldxs = ink.map(p => p.X);
+ const oldys = ink.map(p => p.Y);
+ const oldleft = Math.min(...oldxs);
+ const oldtop = Math.min(...oldys);
Doc.GetProto(doc).data = new InkField(newPoints);
- const xs2 = newPoints.map(p => p.X);
- const ys2 = newPoints.map(p => p.Y);
- const left2 = Math.min(...xs2);
- const top2 = Math.min(...ys2);
- const right2 = Math.max(...xs2);
- const bottom2 = Math.max(...ys2);
- doc._height = (bottom2 - top2);
- doc._width = (right2 - left2);
- //if points move out of bounds
+ const newxs = newPoints.map(p => p.X);
+ const newys = newPoints.map(p => p.Y);
+ const newleft = Math.min(...newxs);
+ const newtop = Math.min(...newys);
+ const newright = Math.max(...newxs);
+ const newbottom = Math.max(...newys);
- doc.x = oldx - (left - left2);
- doc.y = oldy - (top - top2);
+ //if points move out of bounds
+ doc._height = (newbottom - newtop) * inkView.props.ScreenToLocalTransform().Scale;
+ doc._width = (newright - newleft) * inkView.props.ScreenToLocalTransform().Scale;
+ doc.x = oldx - (oldleft - newleft) * inkView.props.ScreenToLocalTransform().Scale;
+ doc.y = oldy - (oldtop - newtop) * inkView.props.ScreenToLocalTransform().Scale;
}
}
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8ed6fbd85..8df7f7eef 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -35,7 +35,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
}
private makeMask = () => {
- this.props.Document._backgroundColor = "rgba(0,0,0,0.7)";
this.props.Document.mixBlendMode = "hard-light";
this.props.Document.color = "#9b9b9bff";
//this.props.Document._stayInCollection = true;
@@ -48,6 +47,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
@action
onControlDown = (e: React.PointerEvent, i: number): void => {
//TODO:renew points before controlling
+ InkStrokeProperties.Instance?.control(0.001, 0.001, 1);
setupMoveUpEvents(this, e, this.onControlMove, this.onControlup, (e) => { });
this._controlUndo = UndoManager.StartBatch("DocDecs set radius");
this._prevX = e.clientX;
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 33bd7e77e..d6a455a22 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -369,7 +369,6 @@
height: 55px;
top: 50%;
left: -10px;
- border: 1px solid black;
border-radius: 8px;
position: relative;
z-index: 41; // lm_maximised has a z-index of 40 and this needs to be above that
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5eb8429f0..c1fafe3e6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -28,7 +28,7 @@ import { SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
@@ -46,7 +46,8 @@ import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
import { AudioBox } from './nodes/AudioBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
+import { FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import { LinkDocPreview } from './nodes/LinkDocPreview';
@@ -58,7 +59,7 @@ import { PDFMenu } from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { SearchBox } from './search/SearchBox';
-import { DefaultStyleProvider } from './StyleProvider';
+import { DefaultStyleProvider, StyleProp } from './StyleProvider';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -295,6 +296,33 @@ export class MainView extends React.Component {
doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
}
+
+ /**
+ * add lock and hide button decorations for the "Dashboards" flyout TreeView
+ */
+ DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+ const toggleField = undoBatch(action((e: React.MouseEvent, doc: Doc, field: string) => {
+ e.stopPropagation();
+ doc[field] = doc[field] ? undefined : true;
+ }));
+ switch (property.split(":")[0]) {
+ case StyleProp.Decorations:
+ return !doc || property.includes(":afterHeader") || // bcz: Todo: afterHeader should be generalized into a renderPath that is a list of the documents rendered so far which would mimic much of CSS property selectors
+ DocListCast((Doc.UserDoc().myDashboards as Doc).data).some(dash => dash === doc ||
+ DocListCast(dash.data).some(tabset => tabset === doc)) ? (null) :
+ <>
+ <div className={`styleProvider-treeView-hide${doc.hidden ? "-active" : ""}`} onClick={e => toggleField(e, doc, "hidden")}>
+ <FontAwesomeIcon icon={doc.hidden ? "eye-slash" : "eye"} size="sm" />
+ </div>
+ <div className={`styleProvider-treeView-lock${doc.lockedPosition ? "-active" : ""}`} onClick={e => toggleField(e, doc, "lockedPosition")}>
+ <FontAwesomeIcon icon={doc.lockedPosition ? "lock" : "unlock"} size="sm" />
+ </div>
+ </>;
+ }
+ return DefaultStyleProvider(doc, props, property);
+ }
+
+
@computed get flyout() {
return !this._flyoutWidth ? <div className={`mainView-libraryFlyout-out`}>
{this.docButtons}
@@ -313,8 +341,9 @@ export class MainView extends React.Component {
PanelWidth={this.flyoutWidthFunc}
PanelHeight={this.getContentsHeight}
renderDepth={0}
+ scriptContext={CollectionDockingView.Instance.props.Document}
focus={emptyFunction}
- styleProvider={DefaultStyleProvider}
+ styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? this.DashboardStyleProvider : DefaultStyleProvider}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -386,8 +415,8 @@ export class MainView extends React.Component {
{this.menuPanel}
<div className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
{this.flyout}
- < div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined, }} onPointerDown={this.onFlyoutPointerDown} >
- <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} size="sm" />
+ <div className="mainView-libraryHandle" style={{ display: !this._flyoutWidth ? "none" : undefined, }} onPointerDown={this.onFlyoutPointerDown} >
+ <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
</div>
{this.dockingContent}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 312cfc73e..1d822439a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { runInAction } from 'mobx';
-import { Doc, Opt, StrListCast } from "../../fields/Doc";
+import { Doc, Opt, StrListCast, LayoutSym } from "../../fields/Doc";
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
@@ -105,11 +105,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
case DocumentType.PRES: docColor = docColor || (darkScheme() ? "#3e3e3e" : "white"); break;
case DocumentType.FONTICON: docColor = undefined; break;
case DocumentType.RTF: docColor = docColor || (darkScheme() ? "#2d2d2d" : "#f1efeb"); break;
- case DocumentType.LABEL:
- case DocumentType.INK: docColor = undefined; break;
+ 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.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
case DocumentType.LINK: return "transparent";
case DocumentType.COL:
+ if (StrCast(Doc.LayoutField(doc)).includes("SliderBox")) break;
docColor = docColor ? docColor :
doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group
(Doc.IsSystem(doc) ? (darkScheme() ? "rgb(62,62,62)" : "lightgrey") : // system docs (seen in treeView) get a grayish background
@@ -135,6 +138,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
return StrCast(doc?.boxShadow,
isBackground() || doc?._isGroup || docProps?.LayoutTemplateString ? undefined : // groups have no drop shadow -- they're supposed to be "invisible". LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
`${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`);
+
+ case DocumentType.LABEL:
+ if (doc?.audioStart !== undefined) return "black 2px 2px 1px";
default:
return doc.z ? `#9c9396 ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK ? (`${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 85fcf6384..5670d45f5 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -389,7 +389,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top">
<button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderLeft: "1px solid gray" }}
- onClick={e => TabDocView.PinDoc(targetDoc, isPinned)}>
+ onClick={e => TabDocView.PinDoc(targetDoc, { unpin: isPinned })}>
<FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" />
</button>
</Tooltip>;
@@ -399,7 +399,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
@action
pinWithView = (targetDoc: Opt<Doc>) => {
if (targetDoc) {
- TabDocView.PinDoc(targetDoc, false);
+ TabDocView.PinDoc(targetDoc);
const presArray: Doc[] = PresBox.Instance?.sortArray();
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 344a6c103..564939270 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -213,7 +213,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
TraceMobx();
if (!(this.doc instanceof Doc)) return (null);
const background = this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
- const paddingX = `${NumCast(this.doc._xPadding, 10)}px`;
+ const paddingX = `${NumCast(this.doc._xPadding, 15)}px`;
const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`;
const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 0d03936dc..5ca069fb9 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -24,7 +24,7 @@ import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
import { FieldViewProps } from '../nodes/FieldView';
-import { PresBox, PresMovement } from '../nodes/PresBox';
+import { PresBox, PresMovement, PinProps } from '../nodes/PresBox';
import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
@@ -166,8 +166,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
* Adds a document to the presentation view
**/
@action
- public static async PinDoc(doc: Doc, unpin = false, audioRange?: boolean) {
- if (unpin) console.log('TODO: Remove UNPIN from this location');
+ public static async PinDoc(doc: Doc, pinProps?: PinProps) {
+ if (pinProps?.unpin) console.log('TODO: Remove UNPIN from this location');
//add this new doc to props.Document
const curPres = CurrentUserUtils.ActivePresentation;
if (curPres) {
@@ -183,12 +183,22 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
Doc.AddDocToList(curPres, "data", pinDoc, presSelected);
- if (!audioRange && (pinDoc.type === DocumentType.AUDIO || pinDoc.type === DocumentType.VID)) {
+ if (!pinProps?.audioRange && (pinDoc.type === DocumentType.AUDIO || pinDoc.type === DocumentType.VID)) {
pinDoc.mediaStart = "manual";
pinDoc.mediaStop = "manual";
pinDoc.presStartTime = 0;
pinDoc.presEndTime = pinDoc.type === DocumentType.AUDIO ? doc.duration : NumCast(doc["data-duration"]);
}
+ //save position
+ if (pinProps?.setPosition || pinDoc.isInkMask) {
+ pinDoc.setPosition = true;
+ pinDoc.y = doc.y;
+ pinDoc.x = doc.x;
+ pinDoc.presHideAfter = true;
+ pinDoc.presHideBefore = true;
+ pinDoc.title = doc.title + " (move)";
+ pinDoc.presMovement = PresMovement.None;
+ }
if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true;
const dview = CollectionDockingView.Instance.props.Document;
const fieldKey = CollectionDockingView.Instance.props.fieldKey;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 7a654c7cf..067675038 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -98,10 +98,21 @@
width: unset;
}
- >svg {
- display: none;
+ .right-buttons-container {
+ display: flex;
+ align-items: center;
+ margin-left: 0.25rem;
+ opacity: 0.75;
+
+ >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide, .styleProvider-treeView-lock-active, .styleProvider-treeView-hide-active {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+ }
+
+ >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide {
+ display: none;
+ }
}
-
}
.treeView-header:hover {
@@ -109,10 +120,6 @@
display: inherit;
}
- >svg {
- display: inherit;
- }
-
.treeView-openRight {
display: inline-block;
height: 17px;
@@ -125,6 +132,12 @@
margin-left: 3px;
}
}
+
+ .right-buttons-container {
+ >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide {
+ display: inherit;
+ }
+ }
}
.treeView-header-above {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 93d3be1fc..c4934cf45 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -31,6 +31,7 @@ import { CollectionTreeView } from './CollectionTreeView';
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TreeView.scss";
import React = require("react");
+import { SliderBox } from '../nodes/SliderBox';
export interface TreeViewProps {
document: Doc;
@@ -378,7 +379,8 @@ export class TreeView extends React.Component<TreeViewProps> {
TraceMobx();
const expandKey = this.treeViewExpandedView;
if (["links", "annotations", this.fieldKey].includes(expandKey)) {
- const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
+ const key = expandKey === "annotations" ? this.fieldKey + "-annotations" : expandKey;
+ const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
// if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
// then the modification would be done here
@@ -390,7 +392,7 @@ export class TreeView extends React.Component<TreeViewProps> {
docs.sort((a, b) => NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1);
docs.forEach((d, i) => d.zIndex = i);
}
- const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true);
+ const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
added && (doc.context = this.doc.context);
return added;
};
@@ -466,6 +468,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
</div>;
}
+
@computed get showTitleEditorControl() { return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); }
@computed get headerElements() {
return (Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode) || this.props.treeViewHideHeaderFields() ? (null) :
@@ -567,6 +570,7 @@ export class TreeView extends React.Component<TreeViewProps> {
parentActive={returnTrue}
whenActiveChanged={this.props.whenActiveChanged}
bringToFront={emptyFunction}
+ cantBrush={this.props.treeView.props.cantBrush}
dontRegisterView={BoolCast(this.props.treeView.props.Document.dontRegisterChildViews)}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
@@ -584,7 +588,10 @@ export class TreeView extends React.Component<TreeViewProps> {
}} >
{view}
</div >
- {this.headerElements}
+ <div className={"right-buttons-container"}>
+ {this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations + (Doc.IsSystem(this.props.containingCollection) ? ":afterHeader" : ""))} {/* hide and lock buttons */}
+ {this.headerElements}
+ </div>
</>;
}
@@ -612,15 +619,17 @@ export class TreeView extends React.Component<TreeViewProps> {
}
renderEmbeddedDocument = (asText: boolean) => {
- const panelWidth = asText || StrCast(Doc.LayoutField(this.layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.expandPanelWidth;
- const panelHeight = asText ? this.rtfOutlineHeight : StrCast(Doc.LayoutField(this.layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.expandPanelHeight;
+ const layout = StrCast(Doc.LayoutField(this.layoutDoc));
+ const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name);
+ const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth;
+ const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight;
return <DocumentView key={this.doc[Id]} ref={action((r: DocumentView | null) => this._dref = r)}
Document={this.doc}
DataDoc={undefined}
PanelWidth={panelWidth}
PanelHeight={panelHeight}
- NativeWidth={!asText && this.layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
- NativeHeight={!asText && this.layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+ NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
+ NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
fitContentsToDoc={true}
hideTitle={asText}
LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
@@ -642,6 +651,7 @@ export class TreeView extends React.Component<TreeViewProps> {
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
+ cantBrush={this.props.treeView.props.cantBrush}
bringToFront={returnFalse}
/>;
}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index f16d13a4e..93cdbbb09 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -4,7 +4,6 @@
height: 100%;
position: inherit;
display: flex;
- pointer-events: all;
position: relative;
cursor: default;
@@ -125,7 +124,6 @@
margin-top: auto;
margin-bottom: auto;
width: 100%;
- height: 80%;
position: relative;
padding-right: 5px;
display: flex;
@@ -135,7 +133,6 @@
margin-top: auto;
margin-bottom: auto;
margin-right: 2px;
- width: 30px;
height: 25px;
padding: 2px;
border-radius: 50%;
@@ -163,7 +160,6 @@
.audiobox-timeline {
position: relative;
- height: 80%;
width: 100%;
background: white;
border: gray solid 1px;
@@ -280,16 +276,20 @@
}
}
- .audiobox-marker-container1,
+ .audiobox-marker-timeline,
.audiobox-marker-minicontainer {
position: absolute;
width: 10px;
height: 90%;
top: 2.5%;
- background: gray;
border-radius: 5px;
- box-shadow: black 2px 2px 1px;
- opacity: 0.3;
+
+ .left-resizer {
+ background: dimgrey;
+ }
+ .resizer {
+ background: dimgrey;
+ }
.audiobox-marker {
position: relative;
@@ -303,10 +303,12 @@
.resizer {
position: absolute;
+ top: 0;
right: 0;
+ pointer-events: all;
cursor: ew-resize;
height: 100%;
- width: 2px;
+ width: 10px;
z-index: 100;
}
@@ -320,14 +322,16 @@
.left-resizer {
position: absolute;
left: 0;
+ top : 0;
+ pointer-events: all;
cursor: ew-resize;
height: 100%;
- width: 2px;
+ width: 10px;
z-index: 100;
}
}
- .audiobox-marker-container1:hover,
+ .audiobox-marker-timeline:hover,
.audiobox-marker-minicontainer:hover {
opacity: 0.8;
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 72d504590..77777ff76 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -3,33 +3,32 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import axios from "axios";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
+import { computedFn } from "mobx-utils";
import Waveform from "react-audio-waveform";
import { DateField } from "../../../fields/DateField";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, DateCast, NumCast } from "../../../fields/Types";
+import { Cast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime, numberRange, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, formatTime, numberRange, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DocumentManager } from "../../util/DocumentManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { StyleProp } from "../StyleProvider";
-import "./AudioBox.scss";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { DocumentView } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
-import { LinkAnchorBox } from "./LinkAnchorBox";
import { LinkDocPreview } from "./LinkDocPreview";
+import "./AudioBox.scss";
declare class MediaRecorder {
// whatever MediaRecorder has
@@ -45,15 +44,15 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
public static Enabled = false;
public static NUMBER_OF_BUCKETS = 100;
-
+ static playheadWidth = 30; // width of playhead
+ static heightPercent = 80; // height of timeline in percent of height of audioBox.
static Instance: AudioBox;
static RangeScript: ScriptField;
static LabelScript: ScriptField;
+ static RangePlayScript: ScriptField;
+ static LabelPlayScript: ScriptField;
- // _linkPlayDisposer: IReactionDisposer | undefined;
- // _reactionDisposer: IReactionDisposer | undefined;
- // _scrubbingDisposer: IReactionDisposer | undefined;
- private _disposers: { [name: string]: IReactionDisposer } = {};
+ _disposers: { [name: string]: IReactionDisposer } = {};
_ele: HTMLAudioElement | null = null;
_recorder: any;
_recordStart = 0;
@@ -64,145 +63,100 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
_start: number = 0;
_hold: boolean = false;
_left: boolean = false;
- _first: boolean = false;
+ _dragging = false;
_play: any = null;
-
- _count: Array<any> = [];
_audioRef = React.createRef<HTMLDivElement>();
_timeline: Opt<HTMLDivElement>;
- _duration = 0;
_markerStart: number = 0;
- private _currMarker: any;
+ _currMarker: any;
- @observable _visible: boolean = false;
+ @observable static SelectingRegion: AudioBox | undefined = undefined;
+ @observable static _scrubTime = 0;
@observable _markerEnd: number = 0;
@observable _position: number = 0;
@observable _waveHeight: Opt<number> = this.layoutDoc._height;
- @observable private _paused: boolean = false;
- @observable private static _scrubTime = 0;
+ @observable _paused: boolean = false;
@computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
set audioState(value) { this.dataDoc.audioState = value; }
public static SetScrubTime = action((timeInMillisFrom1970: number) => { AudioBox._scrubTime = 0; AudioBox._scrubTime = timeInMillisFrom1970; });
@computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
@computed get audioDuration() { return NumCast(this.dataDoc.duration); }
- async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); }
+ @computed get markerDocs() { return DocListCast(this.dataDoc[this.annotationKey]); }
+ @computed get links() { return DocListCast(this.dataDoc.links); }
+ @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time
constructor(props: Readonly<FieldViewProps>) {
super(props);
AudioBox.Instance = this;
// onClick play scripts
- AudioBox.RangeScript = AudioBox.RangeScript || ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart), (this.audioEnd))`, { scriptContext: "any" })!;
- AudioBox.LabelScript = AudioBox.LabelScript || ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart))`, { scriptContext: "any" })!;
+ AudioBox.RangeScript = AudioBox.RangeScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.LabelScript = AudioBox.LabelScript || ScriptField.MakeScript(`scriptContext.clickMarker(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.RangePlayScript = AudioBox.RangePlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.audioStart, this.audioEnd)`, { self: Doc.name, scriptContext: "any" })!;
+ AudioBox.LabelPlayScript = AudioBox.LabelPlayScript || ScriptField.MakeScript(`scriptContext.playOnClick(self, this.audioStart)`, { self: Doc.name, scriptContext: "any" })!;
}
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- let linkTime = NumCast(l.anchor2_timecode);
+ const linkTime = NumCast(la2.audioStart, NumCast(la1.audioStart));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
- linkTime = NumCast(l.anchor1_timecode);
}
return { la1, la2, linkTime };
}
componentWillUnmount() {
- this._disposers.reaction?.();
- this._disposers.linkPlay?.();
- this._disposers.scrubbing?.();
- this._disposers.audioStart?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ const ind = DocUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
+ }
+
+ getAnchor = () => {
+ return this.createMarker(this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined));
}
@action
componentDidMount() {
- if (!this.dataDoc.markerAmount) {
- this.dataDoc.markerAmount = 0;
- }
+ 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.audioState = this.path ? "paused" : undefined;
- this._disposers.linkPlay = reaction(() => this.layoutDoc.scrollToLinkID,
- scrollLinkId => {
- if (scrollLinkId) {
- DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => {
- const { linkTime } = this.getLinkData(l);
- setTimeout(() => { this.playFromTime(linkTime); Doc.linkFollowHighlight(l); }, 250);
- });
- Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
- }
- }, { fireImmediately: true });
-
- // for play when link is selected
- this._disposers.reaction = reaction(() => SelectionManager.Views(),
- selected => {
- const sel = selected.length ? selected[0].props.Document : undefined;
- let link;
- sel && DocListCast(this.dataDoc.links).forEach(l => {
- if (l.anchor1 === sel || l.anchor2 === sel && !sel.audioStart) {
- link = this.playLink(sel);
- }
- });
- // for links created during recording
- if (!link) {
- this.layoutDoc.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime());
- this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause();
- }
- });
+
this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
this._disposers.audioStart = reaction(
- () => this.Document._audioStart,
- (audioStart) => {
- if (audioStart !== undefined) {
- if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
- const delay = this._audioRef.current ? 0 : 250; // wait for mainCont and try again to play
- const startTime: number = NumCast(this.Document._audioStart);
- setTimeout(() => this._audioRef.current && this.playFrom(startTime), delay);
- setTimeout(() => { this.Document._currentTimecode = startTime; this.Document._audioStart = undefined; }, 10 + delay);
- }
- }
- },
+ () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? Cast(this.Document._audioStart, "number", null) : undefined,
+ audioStart => audioStart !== undefined && setTimeout(() => {
+ this._audioRef.current && this.playFrom(audioStart);
+ setTimeout(() => {
+ this.Document._currentTimecode = audioStart;
+ this.Document._audioStart = undefined;
+ }, 10);
+ }, this._audioRef.current ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
);
this._disposers.audioStop = reaction(
- () => this.Document._audioStop,
- (audioStop) => {
- if (audioStop !== undefined) {
- if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
- const delay = this._audioRef.current ? 0 : 250; // wait for mainCont and try again to play
- setTimeout(() => this._audioRef.current && this.pause(), delay);
- setTimeout(() => { this.Document._audioStop = undefined; }, 10 + delay);
- }
- }
- },
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
+ audioStop => audioStop !== undefined && setTimeout(() => {
+ this._audioRef.current && this.pause();
+ setTimeout(() => this.Document._audioStop = undefined, 10);
+ }, this._audioRef.current ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
);
}
playLink = (doc: Doc) => {
- let link = false;
- !Doc.AreProtosEqual(doc, this.props.Document) && DocListCast(this.props.Document.links).forEach(l => {
- if (l.anchor1 === doc || l.anchor2 === doc) {
- const { la1, la2, linkTime } = this.getLinkData(l);
- let startTime = linkTime;
- if (la2.audioStart) startTime = NumCast(la2.audioStart);
- if (la1.audioStart) startTime = NumCast(la1.audioStart);
-
- let endTime;
- if (la1.audioEnd) endTime = NumCast(la1.audioEnd);
- if (la2.audioEnd) endTime = NumCast(la2.audioEnd);
-
- if (startTime) {
- link = true;
- this.layoutDoc.playOnSelect && this.recordingStart && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
- }
+ this.links.filter(l => l.anchor1 === doc || l.anchor2 === doc).forEach(l => {
+ const { la1, la2 } = this.getLinkData(l);
+ const startTime = NumCast(la1.audioStart, NumCast(la2.audioStart, null));
+ const endTime = NumCast(la1.audioEnd, NumCast(la2.audioEnd, null));
+ if (startTime !== undefined) {
+ this.layoutDoc.playOnSelect && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
}
});
-
- this.layoutDoc.playOnSelect && this.recordingStart && Doc.AreProtosEqual(doc, this.props.Document) && this.pause();
- return link;
+ doc.annotationOn === this.rootDoc && this.playFrom(NumCast(doc.audioStart), Cast(doc.audioEnd, "number", null));
}
// for updating the timecode
@@ -210,7 +164,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
const htmlEle = this._ele;
if (this.audioState !== "recording" && htmlEle) {
htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
- DocListCast(this.dataDoc.links).map(l => {
+ this.links.map(l => {
const { la1, linkTime } = this.getLinkData(l);
if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) {
Doc.linkFollowHighlight(la1);
@@ -226,16 +180,31 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this.audioState = "paused";
});
- // play audio for documents created during recording
+ // 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
+ playOnClick = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
+ DocumentManager.Instance.getDocumentView(anchorDoc)?.select(false);
+ this.playFrom(seekTimeInSeconds, endTime);
+ }
+
+ // play back the audio from time
+ @action
+ clickMarker = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
+ if (this.layoutDoc.playOnClick) this.playOnClick(anchorDoc, seekTimeInSeconds, endTime);
+ else {
+ DocumentManager.Instance.getDocumentView(anchorDoc)?.select(false);
+ this._ele && (this._ele.currentTime = this.layoutDoc._currentTimecode = seekTimeInSeconds);
+ }
+ }
+ // play back the audio from time
+ @action
playFrom = (seekTimeInSeconds: number, endTime: number = this.audioDuration) => {
clearTimeout(this._play);
- this._duration = endTime - seekTimeInSeconds;
if (Number.isNaN(this._ele?.duration)) {
setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
} else if (this._ele && AudioBox.Enabled) {
@@ -250,7 +219,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._ele.play();
runInAction(() => this.audioState = "playing");
if (endTime !== this.audioDuration) {
- this._play = setTimeout(() => this.pause(), (this._duration) * 1000); // use setTimeout to play a specific duration
+ this._play = setTimeout(() => this.pause(), (endTime - seekTimeInSeconds) * 1000); // use setTimeout to play a specific duration
}
} else {
this.pause();
@@ -275,7 +244,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
- DocUtils.ActiveRecordings.push(this.props.Document);
+ DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
@@ -294,20 +263,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
const funcs: ContextMenuProps[] = [];
funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when link is selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
funcs.push({ description: (this.layoutDoc.hideMarkers ? "Don't hide" : "Hide") + " range markers", event: () => this.layoutDoc.hideMarkers = !this.layoutDoc.hideMarkers, icon: "expand-arrows-alt" });
- funcs.push({ description: (this.layoutDoc.hideLabels ? "Don't hide" : "Hide") + " label markers", event: () => this.layoutDoc.hideLabels = !this.layoutDoc.hideLabels, icon: "expand-arrows-alt" });
funcs.push({ description: (this.layoutDoc.playOnClick ? "Don't play" : "Play") + " markers onClick", event: () => this.layoutDoc.playOnClick = !this.layoutDoc.playOnClick, icon: "expand-arrows-alt" });
funcs.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " markers onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
- // stops the recording
+ // stops the recording
stopRecording = action(() => {
this._recorder.stop();
this._recorder = undefined;
this.dataDoc.duration = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
this.audioState = "paused";
this._stream?.getAudioTracks()[0].stop();
- const ind = DocUtils.ActiveRecordings.indexOf(this.props.Document);
+ const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
});
@@ -371,7 +339,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._paused = true;
this._recorder.pause();
e.stopPropagation();
-
}
// continue the recording
@@ -381,52 +348,58 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._paused = false;
this._recorder.resume();
e.stopPropagation();
-
- }
-
- // return the total time paused to update the correct time
- @computed get pauseTime() {
- return this._pauseEnd - this._pauseStart;
}
// starting the drag event for marker resizing
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
- const rect = (e.target as any).getBoundingClientRect();
- const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.audioDuration;
- this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
- setupMoveUpEvents(this, e,
- action((e: PointerEvent) => {
- this._visible = true;
- this._markerEnd = toTimeline(e.clientX - rect.x);
- if (this._markerEnd < this._markerStart) {
- const tmp = this._markerStart;
- this._markerStart = this._markerEnd;
- this._markerEnd = tmp;
+ const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect();
+ if (rect && e.target !== this._audioRef.current && this.active()) {
+ const wasPaused = this.audioState === "paused";
+ this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
+ wasPaused && this.pause();
+
+ const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.audioDuration;
+ this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
+ AudioBox.SelectingRegion = this;
+ setupMoveUpEvents(this, e,
+ action(e => {
+ this._markerEnd = toTimeline(e.clientX - rect.x);
+ return false;
+ }),
+ action((e, movement) => {
+ this._markerEnd = toTimeline(e.clientX - rect.x);
+ if (this._markerEnd < this._markerStart) {
+ const tmp = this._markerStart;
+ this._markerStart = this._markerEnd;
+ this._markerEnd = tmp;
+ }
+ AudioBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, this._markerEnd);
+ AudioBox.SelectingRegion = undefined;
+ }),
+ e => {
+ this.props.select(false);
+ e.shiftKey && this.createMarker(this._ele!.currentTime);
}
- return false;
- }),
- action((e: PointerEvent, movement: number[]) => {
- (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, toTimeline(e.clientX - rect.x));
- this._visible = false;
- }),
- (e: PointerEvent) => e.shiftKey && this.createMarker(this._ele!.currentTime)
- );
+ , this.props.isSelected(true) || this._isChildActive);
+ }
}
@action
- createMarker(audioStart: number, audioEnd?: number) {
+ createMarker(audioStart?: number, audioEnd?: number) {
+ if (audioStart === undefined) return this.rootDoc;
const marker = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any, isLabel: audioEnd === undefined,
+ 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
});
- 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);
} else {
this.dataDoc[this.annotationKey] = new List<Doc>([marker]);
}
+ return marker;
}
// starting the drag event for marker resizing
@@ -436,12 +409,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._timeline?.setPointerCapture(e.pointerId);
const toTimeline = (screen_delta: number, width: number) => screen_delta / width * this.audioDuration;
setupMoveUpEvents(this, e,
- (e: PointerEvent) => {
+ (e) => {
const rect = (e.target as any).getBoundingClientRect();
this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x, rect.width));
return false;
},
- (e: PointerEvent) => {
+ (e) => {
const rect = (e.target as any).getBoundingClientRect();
this._ele!.currentTime = this.layoutDoc._currentTimecode = toTimeline(e.clientX - rect.x, rect.width);
this._timeline?.releasePointerCapture(e.pointerId);
@@ -452,7 +425,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
// 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.markerDocs.filter(marker => this.isSame(marker, m)).forEach(marker =>
this._left ? marker.audioStart = time : marker.audioEnd = time);
}
@@ -461,48 +434,31 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
return m1.audioStart === m2.audioStart && m1.audioEnd === m2.audioEnd;
}
- // instantiates a new array of size 500 for marker layout
- markers = () => {
- const increment = this.audioDuration / 500;
- this._count = [];
- for (let i = 0; i < 500; i++) {
- this._count.push([increment * i, 0]);
- }
- }
-
// makes sure no markers overlaps each other by setting the correct position and width
- isOverlap = (m: any) => {
- if (this._first) {
- this._first = false;
- this.markers();
- }
+ getLevel = (m: any, placed: { audioStart: number, audioEnd: number, level: number }[]) => {
+ const timelineContentWidth = this.props.PanelWidth() - AudioBox.playheadWidth;
+ const x1 = m.audioStart;
+ const x2 = m.audioEnd === undefined ? m.audioStart + 10 / timelineContentWidth * this.audioDuration : m.audioEnd;
let max = 0;
-
- for (let i = 0; i < 500; i++) {
- if (this._count[i][0] >= m.audioStart && this._count[i][0] <= m.audioEnd) {
- this._count[i][1]++;
-
- if (this._count[i][1] > max) {
- max = this._count[i][1];
- }
+ const overlappedLevels = new Set(placed.map(p => {
+ const y1 = p.audioStart;
+ const y2 = p.audioEnd;
+ if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
+ (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
+ max = Math.max(max, p.level);
+ return p.level;
}
- }
-
- for (let i = 0; i < 500; i++) {
- if (this._count[i][0] >= m.audioStart && this._count[i][0] <= m.audioEnd) {
- this._count[i][1] = max;
- }
- }
+ }));
+ let level = max + 1;
+ for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
- if (this.dataDoc.markerAmount < max) {
- this.dataDoc.markerAmount = max;
- }
- return max - 1;
+ placed.push({ audioStart: x1, audioEnd: x2, level });
+ return level;
}
@computed get selectionContainer() {
- return <div className="audiobox-container" style={{
- left: `${NumCast(this._markerStart) / this.audioDuration * 100}%`,
+ return AudioBox.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{
+ left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.audioDuration * 100}%`,
width: `${Math.abs(this._markerStart - this._markerEnd) / this.audioDuration * 100}%`, height: "100%", top: "0%"
}} />;
}
@@ -515,7 +471,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
color={"darkblue"}
height={this._waveHeight}
barWidth={0.1}
- // pos={this.layoutDoc._currentTimecode} need to correctly resize parent to make this work (not very necessary for function)
pos={this.audioDuration}
duration={this.audioDuration}
peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
@@ -537,30 +492,55 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
);
}
- rangeScript = () => AudioBox.RangeScript;
- labelScript = () => AudioBox.LabelScript;
- static audioStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
- if (property === StyleProp.BackgroundColor) return "transparent";
- if (property === StyleProp.PointerEvents) return "none";
- }
- render() {
- const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
- this._first = true; // for indicating the first marker that is rendered
- const markerDoc = (mark: Doc, script: undefined | (() => ScriptField)) => {
- return <DocumentView {...this.props}
+ rangeClickScript = () => AudioBox.RangeScript;
+ labelClickScript = () => AudioBox.LabelScript;
+ rangePlayScript = () => AudioBox.RangePlayScript;
+ labelPlayScript = () => AudioBox.LabelPlayScript;
+ renderInner = computedFn(function (this: AudioBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
+ const marker = observable({ view: undefined as any });
+ return {
+ marker, view: <DocumentView key="view" {...this.props} ref={action((r: DocumentView | null) => marker.view = r)}
Document={mark}
- pointerEvents={"all"}
+ PanelWidth={() => width}
+ PanelHeight={() => height}
+ focus={() => this.playLink(mark)}
rootSelected={returnFalse}
LayoutTemplate={undefined}
ContainingCollectionDoc={this.props.Document}
removeDocument={this.removeDocument}
- parentActive={returnTrue}
- onClick={this.layoutDoc.playOnClick ? script : undefined}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().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}
+ onDoubleClick={this.layoutDoc.playOnClick ? undefined : doublescript}
ignoreAutoHeight={false}
bringToFront={emptyFunction}
- scriptContext={this} />;
+ scriptContext={this} />
};
- return <div className="audiobox-container" onContextMenu={this.specificContextMenu} onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined} style={{ pointerEvents: !interactive ? "none" : undefined }}>
+ });
+ renderMarker = computedFn(function (this: AudioBox, 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);
+ return <>
+ {inner.view}
+ {!inner.marker.view || !SelectionManager.IsSelected(inner.marker.view) ? (null) :
+ <>
+ <div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} />
+ <div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} />
+ </>}
+ </>;
+ });
+
+ render() {
+ const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
+ const timelineContentWidth = this.props.PanelWidth() - AudioBox.playheadWidth;
+ const timelineContentHeight = (this.props.PanelHeight() * AudioBox.heightPercent / 100) * AudioBox.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 maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
+ return <div className="audiobox-container"
+ onContextMenu={this.specificContextMenu}
+ onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
+ style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined }}>
{!this.path ?
<div className="audiobox-buttons">
<div className="audiobox-dictation" onClick={this.onFile}>
@@ -569,10 +549,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
{this.audioState === "recording" || this.audioState === "paused" ?
<div className="recording" onClick={e => e.stopPropagation()}>
<div className="buttons" onClick={this.recordClick}>
- <FontAwesomeIcon style={{ width: "100%" }} icon={"stop"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ <FontAwesomeIcon icon={"stop"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
<div className="buttons" onClick={this._paused ? this.recordPlay : this.recordPause}>
- <FontAwesomeIcon style={{ width: "100%" }} icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ <FontAwesomeIcon icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
<div className="time">{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}</div>
</div>
@@ -581,74 +561,33 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
RECORD
</button>}
</div> :
- <div className="audiobox-controls" >
- <div className="audiobox-dictation"></div>
- <div className="audiobox-player" >
- <div className="audiobox-playhead" title={this.audioState === "paused" ? "play" : "pause"} onClick={this.onPlay}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div>
- <div className="audiobox-timeline" ref={this.timelineRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }}
- onPointerDown={e => {
- if (e.button === 0 && !e.ctrlKey) {
- const rect = (e.target as any).getBoundingClientRect();
-
- if (e.target !== this._audioRef.current) {
- const wasPaused = this.audioState === "paused";
- this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
- wasPaused && this.pause();
- }
-
- this.onPointerDownTimeline(e);
- }
- }}>
+ <div className="audiobox-controls" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }} >
+ <div className="audiobox-dictation" />
+ <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
+ <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.audioState === "paused" ? "play" : "pause"} onClick={this.onPlay}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div>
+ <div className="audiobox-timeline" style={{ height: `${AudioBox.heightPercent}%` }} ref={this.timelineRef}
+ onClick={e => { e.stopPropagation(); e.preventDefault(); }}
+ onPointerDown={e => e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e)}>
<div className="waveform">
{this.waveform}
</div>
- {DocListCast(this.dataDoc[this.annotationKey]).map((m, i) =>
- (!m.isLabel) ?
- (this.layoutDoc.hideMarkers) ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container1`} key={i}
- title={`${formatTime(Math.round(NumCast(m.audioStart)))}` + " - " + `${formatTime(Math.round(NumCast(m.audioEnd)))}`}
- style={{
- left: `${NumCast(m.audioStart) / this.audioDuration * 100}%`,
- top: `${this.isOverlap(m) * 1 / (this.dataDoc.markerAmount + 1) * 100}%`,
- width: `${(NumCast(m.audioEnd) - NumCast(m.audioStart)) / this.audioDuration * 100}%`, height: `${1 / (this.dataDoc.markerAmount + 1) * 100}%`
- }}
- onClick={e => { this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation(); }} >
- <div className="left-resizer" onPointerDown={e => this.onPointerDown(e, m, true)}></div>
- {markerDoc(m, this.rangeScript)}
- <div className="resizer" onPointerDown={e => this.onPointerDown(e, m, false)}></div>
- </div>
- :
- (this.layoutDoc.hideLabels) ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container`} key={i}
- style={{ left: `${NumCast(m.audioStart) / this.audioDuration * 100}%` }}>
- {markerDoc(m, this.labelScript)}
- </div>
- )}
- {DocListCast(this.dataDoc.links).map((l, i) => {
- const { la1, la2, linkTime } = this.getLinkData(l);
- let startTime = linkTime;
- if (la2.audioStart && !la2.audioEnd) {
- startTime = NumCast(la2.audioStart);
- }
-
- return !linkTime ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container`} key={l[Id]} style={{ left: `${startTime / this.audioDuration * 100}%` }} onClick={e => e.stopPropagation()}>
- <DocumentView {...this.props}
- Document={l}
- rootSelected={returnFalse}
- ContainingCollectionDoc={this.props.Document}
- parentActive={returnTrue}
- bringToFront={emptyFunction}
- styleProvider={AudioBox.audioStyleProvider}
- LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
- />
- <div key={i} className={`audiobox-marker`} onPointerEnter={() => Doc.linkFollowHighlight(la1)}
- onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(startTime); e.stopPropagation(); e.preventDefault(); } }} />
+ {drawMarkers.map((d, i) => {
+ 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}
+ 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,
+ left + AudioBox.playheadWidth,
+ (1 - AudioBox.heightPercent / 100) / 2 * this.props.PanelHeight() + top,
+ timelineContentWidth * timespan / this.audioDuration,
+ timelineContentHeight / maxLevel)}
</div>;
})}
- {this._visible ? this.selectionContainer : null}
-
+ {this.selectionContainer}
<div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} />
{this.audio}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index bfb6e8fc8..09d89170c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -169,6 +169,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
return <div className={"collectionFreeFormDocumentView-container"}
style={{
outline: this.Highlight ? "orange solid 2px" : "",
+ width: this.panelWidth(),
+ height: this.panelHeight(),
transform: this.transform,
transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
zIndex: this.ZInd,
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index e7a94580a..2cac2d0b8 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -160,6 +160,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.AnnotationUri = undefined;
//!this.props.StartLink
} else if (startLink !== endLink) {
+ endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
+ startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
// this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved
if (endLinkView) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e99b0a155..6371ae5fb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,6 +29,7 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
+import { InkingStroke } from "../InkingStroke";
import { InkStrokeProperties } from '../InkStrokeProperties';
import { StyleLayers, StyleProp } from "../StyleProvider";
import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
@@ -51,6 +52,7 @@ export interface DocumentViewSharedProps {
fitContentsToDoc?: boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ setContentView?: (view: { getAnchor: () => Doc }) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
@@ -74,6 +76,7 @@ export interface DocumentViewSharedProps {
dropAction?: dropActionType;
dontRegisterView?: boolean;
ignoreAutoHeight?: boolean;
+ cantBrush?: boolean; // whether the document doesn't show brush highlighting
pointerEvents?: string;
scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
}
@@ -376,14 +379,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const func = () => this.onDoubleClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
+ scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView,
shiftKey: e.shiftKey
}, console.log);
undoBatch(func)();
} else if (!Doc.IsSystem(this.props.Document)) {
if (this.props.Document.type === DocumentType.INK) {
InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true);
- } else {
+ } else if (this.props.Document.type !== DocumentType.LABEL) {
UndoManager.RunInBatch(() => {
const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document;
this.props.addDocTab(fullScreenDoc, "add");
@@ -411,7 +416,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
} else func();
};
if (this.onDoubleClickHandler) {
- this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 500);
+ this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
} else clickFunc();
} else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
@@ -548,7 +553,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined;
if (linkSource && linkSource !== this.props.Document) {
e.stopPropagation();
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, "link", undefined, undefined, undefined, [de.x, de.y]);
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: this._componentView?.getAnchor() || this.rootDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
}
}
@@ -592,9 +597,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
this.props.contextMenuItems?.().forEach(item =>
- item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+ item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
const appearance = cm.findByDescription("UI Controls...");
@@ -688,10 +693,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- makeLink = () => this.props.DocumentView._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+ makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+ setContentView = (view: { getAnchor: () => Doc }) => this._componentView = view;
@observable contentsActive: () => boolean = returnFalse;
@action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
-
+ _componentView: { getAnchor: () => Doc } | undefined;
@computed get contents() {
TraceMobx();
return <div className="documentView-contentsView"
@@ -700,6 +706,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView key={1} {...this.props}
+ setContentView={this.setContentView}
scaling={this.contentScaling}
PanelHeight={this.panelHeight}
contentsActive={this.setContentsActive}
@@ -715,15 +722,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
}
- // used to decide whether a link anchor view should be created or not.
- // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
- // would be good to generalize this some way.
- isNonTemporalLink = (linkDoc: Doc) => {
- const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
- const ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1_timecode : linkDoc.anchor2_timecode;
- return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
- }
-
@undoBatch
hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
@@ -738,7 +736,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink(d));
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
return filtered.map((d, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
@@ -820,7 +818,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
- let highlighting = highlightIndex && ![DocumentType.FONTICON, DocumentType.INK].includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
+ let highlighting = !this.props.cantBrush && highlightIndex && ![DocumentType.FONTICON, DocumentType.INK].includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
@@ -964,9 +962,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
<div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
style={{
+ position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: Math.abs(this.Xshift) > 0.001 ? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth(),
- height: Math.abs(this.YShift) > 0.001 ? this.props.Document._fitWidth ? `${this.panelHeight}px` : `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
+ width: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) > 0.001 ? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth(),
+ height: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.YShift) > 0.001 ? this.props.Document._fitWidth ? `${this.panelHeight}px` : `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
}}>
<DocumentViewInternal {...this.props} {...internalProps} ref={action((r: DocumentViewInternal | null) => this.docView = r)} />
</div>)}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 730ae8f10..badacbc5c 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -102,7 +102,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
if (docRangeFilters) {
let index: number;
while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === facetHeader)) !== -1) {
- docRangeFilters.splice(index, 1);
+ docRangeFilters.splice(index, 3);
}
}
} else {
@@ -122,13 +122,13 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
});
let newFacet: Opt<Doc>;
if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) {
- newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
+ newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
newFacet._textBoxPadding = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
} else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) {
- newFacet = Docs.Create.SliderDocument({ title: facetHeader, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", treeViewOpen: true });
+ newFacet = Docs.Create.SliderDocument({ title: facetHeader, _overflow: "visible", _height: 40, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", treeViewOpen: true });
const newFacetField = Doc.LayoutFieldKey(newFacet);
const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader);
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
@@ -146,13 +146,13 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
newFacet.title = facetHeader;
newFacet.treeViewOpen = true;
newFacet.type = DocumentType.COL;
- const capturedVariables = { layoutDoc: targetDoc, _stayInCollection: true, _hideContextMenu: true, dataDoc: (targetDoc.data as any)[0][DataSym] };
+ const capturedVariables = { layoutDoc: targetDoc, system: true, _stayInCollection: true, _hideContextMenu: true, dataDoc: (targetDoc.data as any)[0][DataSym] };
newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables);
}
newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
}
}
- filterBackground = () => "rgba(105, 105, 105, 0.432)";
+
@computed get scriptField() {
const scriptText = "setDocFilter(this?.target, heading, this.title, checked)";
const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
@@ -184,6 +184,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
DataDoc={Doc.GetProto(facetCollection)}
fieldKey={`${this.props.fieldKey}`}
CollectionView={undefined}
+ cantBrush={true}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
@@ -208,7 +209,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
treeViewHideHeaderFields={true}
onCheckedClick={this.scriptField}
dontRegisterView={true}
- styleProvider={this.filterBackground}
+ styleProvider={this.props.styleProvider}
+ scriptContext={this.props.scriptContext}
moveDocument={returnFalse}
removeDocument={returnFalse}
addDocument={returnFalse} />
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 393ba07e6..b0e7f4ce5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -413,7 +413,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
borderRadius
}} >
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
CollectionView={undefined}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index bc2090a33..3448a4abd 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -67,6 +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));
return (
<div className="labelBox-outerDiv"
onClick={action(() => this.clicked = !this.clicked)}
@@ -88,7 +89,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
paddingBottom: NumCast(this.layoutDoc._yPadding),
whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
}} >
- {StrCast(this.rootDoc[this.fieldKey], StrCast(this.rootDoc.title))}
+ {label.startsWith("#") ? (null) : label}
</div>
<div className="labelBox-fieldKeyParams" >
{!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index e347d1304..d86dfd7b1 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -77,6 +77,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
LinkManager.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
+ e.stopPropagation();
}
} else {
this._timeout && clearTimeout(this._timeout);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 42462000b..aef776563 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -66,6 +66,12 @@ export enum PresColor {
SlideBackground = "#d5dce2",
}
+export class PinProps {
+ audioRange?: boolean;
+ unpin?: boolean;
+ setPosition?: boolean;
+}
+
type PresBoxSchema = makeInterface<[typeof documentSchema]>;
const PresBoxDocument = makeInterface(documentSchema);
@@ -423,8 +429,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
} else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right
} else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
- //awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right
}
// After navigating to the document, if it is added as a presPinView then it will
// adjust the pan and scale to that of the pinView when it was added.
@@ -433,6 +439,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.navigateToView(targetDoc, activeItem);
}
// TODO: Add progressivize for navigating web (storing websites for given frames)
+
}
/**
@@ -536,6 +543,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.next();
}
}
+
await timer(duration); this.next(); // then the created Promise can be awaited
if (i === this.childDocs.length - 1) {
setTimeout(() => {
@@ -709,7 +717,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
audio.presStartTime = NumCast(doc.audioStart);
audio.presEndTime = NumCast(doc.audioEnd);
audio.presDuration = NumCast(doc.audioEnd) - NumCast(doc.audioStart);
- TabDocView.PinDoc(audio, false, true);
+ TabDocView.PinDoc(audio, { audioRange: true });
setTimeout(() => this.removeDocument(doc), 0);
return false;
}
@@ -766,11 +774,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//Regular click
@action
- selectElement = (doc: Doc) => {
+ selectElement = async (doc: Doc) => {
const context = Cast(doc.context, Doc, null);
this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
else this.updateCurrentPresentation(context);
+
+ if (this.activeItem.setPosition &&
+ this.activeItem.y !== undefined &&
+ this.activeItem.x !== undefined &&
+ this.targetDoc.x !== undefined &&
+ this.targetDoc.y !== undefined) {
+ const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms));
+ const time = 10;
+ const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y);
+ const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x);
+
+ for (let i = 0; i < time; i++) {
+ this.targetDoc.x = NumCast(this.targetDoc.x) + xdiff / time;
+ this.targetDoc.y = NumCast(this.targetDoc.y) + ydiff / time;
+ await timer(0.1);
+ }
+ }
}
//Command click
@@ -1711,7 +1736,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
const presData = Cast(this.rootDoc.data, listSpec(Doc));
if (data && presData) {
data.push(doc);
- TabDocView.PinDoc(doc, false);
+ TabDocView.PinDoc(doc);
this.gotoDocument(this.childDocs.length, this.activeItem);
} else {
this.props.addDocTab(doc, "add:right");
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
index 874a1108f..e19f28f08 100644
--- a/src/client/views/nodes/SliderBox-components.tsx
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -11,7 +11,6 @@ const railStyle: React.CSSProperties = {
position: "absolute",
width: "100%",
height: 40,
- top: -13,
borderRadius: 7,
cursor: "pointer",
opacity: 0.3,
@@ -231,7 +230,7 @@ export function Tick({ tick, count, format = defaultFormat }: TickProps) {
<div
style={{
position: "absolute",
- marginTop: 17,
+ marginTop: 20,
width: 1,
height: 5,
backgroundColor: "rgb(200,200,200)",
diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss
index 78015bd70..4206a368d 100644
--- a/src/client/views/nodes/SliderBox.scss
+++ b/src/client/views/nodes/SliderBox.scss
@@ -1,7 +1,19 @@
.sliderBox-outerDiv {
- width: 100%;
+ width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box
height: 100%;
border-radius: inherit;
display: flex;
flex-direction: column;
+ position: relative;
+ .slider-tracks {
+ top: 7px;
+ position: relative;
+ }
+ .slider-ticks {
+ position: relative;
+ }
+ .slider-handles {
+ top: 7px;
+ position: relative;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 2aa71b2bd..bfe07c22b 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -41,7 +41,10 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
onChange = (values: readonly number[]) => runInAction(() => {
this.dataDoc[this.minThumbKey] = values[0];
this.dataDoc[this.maxThumbKey] = values[1];
- Cast(this.layoutDoc.onThumbChanged, ScriptField, null)?.script.run({ self: this.rootDoc, range: values, this: this.layoutDoc });
+ Cast(this.layoutDoc.onThumbChanged, ScriptField, null)?.script.run({
+ self: this.rootDoc,
+ scriptContext: this.props.scriptContext, range: values, this: this.layoutDoc
+ });
})
render() {
@@ -50,10 +53,12 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
return domain[1] <= domain[0] ? (null) : (
<div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()}
style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
- <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
- background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
- fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
- }} >
+ <div className="sliderBox-mainButton"
+ onContextMenu={this.specificContextMenu} style={{
+ background: StrCast(this.layoutDoc.backgroundColor),
+ color: StrCast(this.layoutDoc.color, "black"),
+ fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
+ }} >
<Slider
mode={2}
step={1}
@@ -101,7 +106,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
</Tracks>
<Ticks count={5}>
{({ ticks }) => (
- <div className="slider-tracks">
+ <div className="slider-ticks">
{ticks.map((tick) => (
<Tick
key={tick.id}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 9cdd6465f..76edda847 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -15,12 +15,13 @@
position: absolute;
height: 20%;
width: 100%;
- bottom: -60px;
+ bottom: 0px;
background: white;
border: gray solid 1px;
border-radius: 3px;
z-index: 1000;
overflow: hidden;
+ left: 0px;
.audiobox-current {
width: 1px;
@@ -70,7 +71,7 @@
}
}
- .audiobox-marker-container1,
+ .audiobox-marker-timeline,
.audiobox-marker-minicontainer {
position: absolute;
width: 10px;
@@ -115,6 +116,11 @@
width: 2px;
z-index: 100;
}
+
+ // .contentFittingDocumentView-previewDoc {
+ // width: 100% !important;
+ // transform: none !important;
+ // }
}
.audiobox-marker-container1:hover,
@@ -173,14 +179,14 @@
pointer-events:all;
}
-.timeline-button {
- position: absolute;
- bottom: 15px;
- right: 17%;
- color: lightgrey;
- width: 20px;
+// .timeline-button {
+// position: absolute;
+// bottom: 35px;
+// right: 235px;
+// color: lightgrey;
+// width: 20px;
-}
+// }
.videoBox-play {
width: 25px;
height: 20px;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 060953e7b..67e8d74b3 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -28,6 +28,8 @@ 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";
const path = require('path');
export const timeSchema = createSchema({
@@ -42,6 +44,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
static Instance: VideoBox;
static RangeScript: ScriptField;
static LabelScript: ScriptField;
+ 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;
@@ -57,11 +62,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
_audioRef = React.createRef<HTMLDivElement>();
_markerStart: number = 0;
_left: boolean = false;
- _first: boolean = false;
_count: Array<any> = [];
_duration = 0;
_start: boolean = true;
private _currMarker: any;
+ @observable static SelectingRegion: VideoBox | undefined = undefined;
@observable _visible: boolean = false;
@observable _markerEnd: number = 0;
@observable _forceCreateYouTubeIFrame = false;
@@ -70,6 +75,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]); }
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
public get player(): HTMLVideoElement | null {
@@ -81,8 +87,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
VideoBox.Instance = this;
// onClick play scripts
- VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart), (this.audioEnd))`, { scriptContext: "any" })!;
- VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart))`, { scriptContext: "any" })!;
+ 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" })!;
}
videoLoad = () => {
@@ -253,10 +261,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this.layoutDoc._height = (this.layoutDoc._width || 0) / youtubeaspect;
}
}
-
- if (!this.dataDoc.markerAmount) {
- this.dataDoc.markerAmount = 0;
- }
}
componentWillUnmount() {
@@ -317,23 +321,29 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
}
+ // returns the video and timeline
@computed get content() {
const field = Cast(this.dataDoc[this.fieldKey], VideoField);
const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
+ const h = this.layoutDoc._showTimeline ? `${100 - VideoBox.heightPercent}%` : "100%";
return !field ? <div>Loading</div> :
- <div>
- <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
- style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
- onCanPlay={this.videoLoad}
- controls={VideoBox._showControls}
- onPlay={() => this.Play()}
- onSeeked={this.updateTimecode}
- onPause={() => this.Pause()}
- onClick={e => e.preventDefault()}>
- <source src={field.url.href} type="video/mp4" />
- Not supported.
- </video>
+ <div className="container" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }}>
+ <div className={`${style}`} style={{ width: "100%", height: h, left: "0px" }}>
+ <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ style={{ height: "100%", width: "auto", display: "flex", margin: "auto" }}
+ onCanPlay={this.videoLoad}
+ controls={VideoBox._showControls}
+ onPlay={() => this.Play()}
+ onSeeked={this.updateTimecode}
+ onPause={() => this.Pause()}
+ onClick={e => e.preventDefault()}>
+ <source src={field.url.href} type="video/mp4" />
+ Not supported.
+ </video>
+ {this.uIButtons}
+ </div>
+ {this.renderTimeline}
</div>;
}
@@ -392,7 +402,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
<div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
<FontAwesomeIcon icon="camera" size="lg" />
</div>,
- <div className="timeline-button" key="timeline-button" onPointerDown={this.toggleTimeline}>
+ <div className="timeline-button" key="timeline-button" onPointerDown={this.toggleTimeline} style={{
+ position: "absolute",
+ bottom: "41px",
+ right: this.layoutDoc._showTimeline ? "235px" : "155px",
+ color: "lightgrey",
+ width: "20px"
+ }}>
<FontAwesomeIcon icon={this.layoutDoc._showTimeline ? "eye-slash" : "eye"} style={{ width: "100%" }} />
</div>,
VideoBox._showControls ? (null) : [
@@ -462,7 +478,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
return this.addDocument(doc);
}
- // play back the audio from time
+ // play back the video from time
@action
playFrom = (seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
clearTimeout(this._play);
@@ -497,29 +513,39 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._timeline = timeline;
}
- // starting the drag event for marker resizing
+ // starting the drag event creating a range marker
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
- const rect = (e.target as any).getBoundingClientRect();
- const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.videoDuration;
- this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
- setupMoveUpEvents(this, e,
- action((e: PointerEvent) => {
- this._visible = true;
- this._markerEnd = toTimeline(e.clientX - rect.x);
- if (this._markerEnd < this._markerStart) {
- const tmp = this._markerStart;
- this._markerStart = this._markerEnd;
- this._markerEnd = tmp;
+ const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect();
+ if (rect && e.target !== this._audioRef.current && this.active()) {
+ const wasPaused = !this._playing;
+ this.player!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.videoDuration;
+ wasPaused && this.Pause();
+
+ const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.videoDuration;
+ this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
+ VideoBox.SelectingRegion = this;
+ setupMoveUpEvents(this, e,
+ action(e => {
+ this._markerEnd = toTimeline(e.clientX - rect.x);
+ return false;
+ }),
+ action((e, movement) => {
+ this._markerEnd = toTimeline(e.clientX - rect.x);
+ if (this._markerEnd < this._markerStart) {
+ const tmp = this._markerStart;
+ this._markerStart = this._markerEnd;
+ this._markerEnd = tmp;
+ }
+ VideoBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, this._markerEnd);
+ VideoBox.SelectingRegion = undefined;
+ }),
+ e => {
+ this.props.select(false);
+ e.shiftKey && this.createMarker(this.player!.currentTime);
}
- return false;
- }),
- action((e: PointerEvent, movement: number[]) => {
- (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, toTimeline(e.clientX - rect.x));
- this._visible = false;
- }),
- (e: PointerEvent) => e.shiftKey && this.createMarker(this.player!.currentTime)
- );
+ , this.props.isSelected(true) || this._isChildActive);
+ }
}
@action
@@ -537,6 +563,23 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
}
+ // play back the video from time
+ @action
+ playOnClick = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
+ DocumentManager.Instance.getDocumentView(anchorDoc)?.select(false);
+ this.playFrom(seekTimeInSeconds, endTime);
+ }
+
+ // play back the video from time
+ @action
+ clickMarker = (anchorDoc: Doc, seekTimeInSeconds: number, endTime: number = this.videoDuration) => {
+ if (this.layoutDoc.playOnClick) this.playOnClick(anchorDoc, seekTimeInSeconds, endTime);
+ else {
+ DocumentManager.Instance.getDocumentView(anchorDoc)?.select(false);
+ this.player && (this.player.currentTime = this.layoutDoc._currentTimecode = seekTimeInSeconds);
+ }
+ }
+
// starting the drag event for marker resizing
onPointerDown = (e: React.PointerEvent, m: any, left: boolean): void => {
this._currMarker = m;
@@ -557,6 +600,98 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
emptyFunction);
}
+ // makes sure no markers overlaps each other by setting the correct position and width
+ getLevel = (m: any, placed: { audioStart: number, audioEnd: 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;
+ let max = 0;
+ const overlappedLevels = new Set(placed.map(p => {
+ const y1 = p.audioStart;
+ const y2 = p.audioEnd;
+ if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
+ (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
+ max = Math.max(max, p.level);
+ return p.level;
+ }
+ }));
+ let level = max + 1;
+ for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
+
+ placed.push({ audioStart: x1, audioEnd: 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) {
+ const marker = observable({ view: undefined as any });
+ return {
+ marker, view: <DocumentView key="view" {...this.props} ref={action((r: DocumentView | null) => marker.view = r)}
+ Document={mark}
+ PanelWidth={() => width}
+ PanelHeight={() => height}
+ rootSelected={returnFalse}
+ LayoutTemplate={undefined}
+ ContainingCollectionDoc={this.props.Document}
+ removeDocument={this.removeDocument}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().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}
+ onDoubleClick={this.layoutDoc.playOnClick ? undefined : doublescript}
+ ignoreAutoHeight={false}
+ bringToFront={emptyFunction}
+ scriptContext={this} />
+ };
+ });
+
+ 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);
+ return <>
+ {inner.view}
+ {!inner.marker.view || !SelectionManager.IsSelected(inner.marker.view) ? (null) :
+ <>
+ <div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} />
+ <div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} />
+ </>}
+ </>;
+ });
+
+ // 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 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) => {
+ const m = d.marker;
+ const left = NumCast(m.audioStart) / this.videoDuration;
+ const l = `${NumCast(m.audioStart) / this.videoDuration * 100}%`;
+ const top = d.level / maxLevel * timelineContentHeight;
+ const timespan = m.audioEnd === undefined ? 10 / timelineContentWidth * this.videoDuration : NumCast(m.audioEnd) - NumCast(m.audioStart);
+ 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(); }} >
+ {this.renderMarker(m, this.rangeClickScript, this.rangePlayScript,
+ left,
+ top,
+ timelineContentWidth * timespan / this.videoDuration,
+ timelineContentHeight / maxLevel)}
+ </div>;
+ })}
+ {this.selectionContainer}
+ <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.videoDuration * 100}%`, pointerEvents: "none" }} />
+ </div>;
+ }
+
// updates the marker with the new time
@action
changeMarker = (m: any, time: any) => {
@@ -569,48 +704,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
return m1.audioStart === m2.audioStart && m1.audioEnd === m2.audioEnd;
}
- // instantiates a new array of size 500 for marker layout
- markers = () => {
- const increment = this.videoDuration / 500;
- this._count = [];
- for (let i = 0; i < 500; i++) {
- this._count.push([increment * i, 0]);
- }
- }
-
- // makes sure no markers overlaps each other by setting the correct position and width
- isOverlap = (m: any) => {
- if (this._first) {
- this._first = false;
- this.markers();
- }
- let max = 0;
-
- for (let i = 0; i < 500; i++) {
- if (this._count[i][0] >= m.audioStart && this._count[i][0] <= m.audioEnd) {
- this._count[i][1]++;
-
- if (this._count[i][1] > max) {
- max = this._count[i][1];
- }
- }
- }
-
- for (let i = 0; i < 500; i++) {
- if (this._count[i][0] >= m.audioStart && this._count[i][0] <= m.audioEnd) {
- this._count[i][1] = max;
- }
- }
-
- if (this.dataDoc.markerAmount < max) {
- this.dataDoc.markerAmount = max;
- }
- return max - 1;
- }
-
+ // returns the blue container when dragging
@computed get selectionContainer() {
- return <div className="audiobox-container" style={{
- left: `${NumCast(this._markerStart) / this.videoDuration * 100}%`,
+ return VideoBox.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{
+ left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.videoDuration * 100}%`,
width: `${Math.abs(this._markerStart - this._markerEnd) / this.videoDuration * 100}%`, height: "100%", top: "0%"
}} />;
}
@@ -619,49 +716,34 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
VideoBox.Instance.keyEvents(e);
}
+ // for creating key markers with key events
@action
keyEvents = (e: KeyboardEvent) => {
if (e.target instanceof HTMLInputElement) return;
if (!this._playing) return; // can't create if video is not playing
switch (e.key) {
- case "x":
+ case "x": // currently set to x, but can be a different key
const currTime = this.player!.currentTime;
if (this._start) {
this._markerStart = this.player!.currentTime;
this._start = false;
- console.log("begin");
this._visible = true;
} else {
this.createMarker(this._markerStart, currTime);
this._start = true;
- console.log("end");
this._visible = false;
}
}
}
- rangeScript = () => VideoBox.RangeScript;
- labelScript = () => VideoBox.LabelScript;
+ rangeClickScript = () => VideoBox.RangeScript;
+ labelClickScript = () => VideoBox.LabelScript;
+ rangePlayScript = () => VideoBox.RangePlayScript;
+ labelPlayScript = () => VideoBox.LabelPlayScript;
screenToLocalTransform = () => this.props.ScreenToLocalTransform();
contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
render() {
- const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
- this._first = true; // for indicating the first marker that is rendered
- const markerDoc = (mark: Doc, script: undefined | (() => ScriptField)) => {
- return <DocumentView {...this.props}
- Document={mark}
- pointerEvents={"all"}
- rootSelected={returnFalse}
- LayoutTemplate={undefined}
- ContainingCollectionDoc={this.props.Document}
- removeDocument={this.removeDocument}
- parentActive={returnTrue}
- onClick={this.layoutDoc.playOnClick ? script : undefined}
- ignoreAutoHeight={false}
- bringToFront={emptyFunction}
- scriptContext={this} />;
- };
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad;
return (<div className="videoBox" onContextMenu={this.specificContextMenu}
@@ -672,7 +754,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
borderRadius
}} >
<div className="videoBox-viewer" >
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
forceScaling={true}
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
@@ -689,47 +771,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{this.contentFunc}
</CollectionFreeFormView>
</div>
- {this.uIButtons}
- {!this.layoutDoc._showTimeline ? (null) :
- <div className="audiobox-timeline" ref={this.timelineRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }}
- onPointerDown={e => {
- if (e.button === 0 && !e.ctrlKey) {
- const rect = (e.target as any).getBoundingClientRect();
-
- if (e.target !== this._audioRef.current) {
- const wasPaused = !this._playing;
- this.player!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.videoDuration;
- wasPaused && this.Pause();
- }
- this.onPointerDownTimeline(e);
- }
- }}>
- {DocListCast(this.dataDoc[this.annotationKey]).map((m, i) =>
- (!m.isLabel) ?
- (this.layoutDoc.hideMarkers) ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container1`} key={i}
- title={`${formatTime(Math.round(NumCast(m.audioStart)))}` + " - " + `${formatTime(Math.round(NumCast(m.audioEnd)))}`}
- style={{
- left: `${NumCast(m.audioStart) / this.videoDuration * 100}%`,
- top: `${this.isOverlap(m) * 1 / (this.dataDoc.markerAmount + 1) * 100}%`,
- width: `${(NumCast(m.audioEnd) - NumCast(m.audioStart)) / this.videoDuration * 100}%`, height: `${1 / (this.dataDoc.markerAmount + 1) * 100}%`
- }}
- onClick={e => { this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation(); }} >
- <div className="left-resizer" onPointerDown={e => this.onPointerDown(e, m, true)}></div>
- {markerDoc(m, this.rangeScript)}
- <div className="resizer" onPointerDown={e => this.onPointerDown(e, m, false)}></div>
- </div>
- :
- (this.layoutDoc.hideLabels) ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container`} key={i}
- style={{ left: `${NumCast(m.audioStart) / this.videoDuration * 100}%` }}>
- {markerDoc(m, this.labelScript)}
- </div>
- )}
- {this._visible ? this.selectionContainer : null}
- <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.videoDuration * 100}%`, pointerEvents: "none" }} />
- </div>}
-
</div >);
}
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8e824a447..7fb14b0cc 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -689,7 +689,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
height: NumCast(this.scrollHeight, 50),
pointerEvents: inactiveLayer ? "none" : undefined
}}>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
renderDepth={this.props.renderDepth + 1}
CollectionView={undefined}
fieldKey={this.annotationKey}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2b9910dfb..c129d0204 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -320,12 +320,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DocListCast(this.dataDoc.links).map((l, i) => {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- this._linkTime = NumCast(l.anchor2_timecode);
+ this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart));
audioState = la2.audioState;
if (Doc.AreProtosEqual(la2, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
- this._linkTime = NumCast(l.anchor1_timecode);
audioState = la1.audioState;
}
});
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 02f7ada96..8700a5739 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -713,7 +713,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined,
transform: `scale(${this._zoomed})`
}}>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
isAnnotationOverlay={true}
fieldKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 13d4a6d5a..76f9b66eb 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -30,42 +30,29 @@
.searchBox-lozenge-user,
.searchBox-lozenge-dashboard,
.searchBox-lozenge {
- background-color: #313131;
- border-radius: 5px;
height: 18px;
padding: 4px;
- box-shadow: lightgrey 0.15em 0.15em 0.1em;
- margin: 2px;
- margin-bottom: 4px;
- border-top: dimgrey 1px solid;
- border-left: dimgrey 1px solid;
+ margin-right: 5px;
display: flex;
+ align-items: center;
+ border: grey 1px solid;
.searchBox-logoff,
.searchBox-dashboards {
border-radius: 3px;
background: olivedrab;
color: white;
- position: relative;
display: none;
- margin-left: 3px;
- padding-left: 2px;
- padding-right: 2px;
- padding-bottom: 11px;
- cursor: default;
+ margin-left: 5px;
+ padding: 1px 2px 1px 2px;
+ cursor: pointer;
}
.searchBox-logoff {
background: red;
}
.searchBox-dashSelect{
- background-color: black;
- color: white;
- font-size: 9;
- margin-right: 6;
- border-radius: 5px;
- position: relative;
- height: 15px;
- transform: translate(0,-3px);
+ border: none;
+ background-color: transparent;
&:hover {
cursor: pointer;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index d3fee85a7..d10afdcf9 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -466,7 +466,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
}
@computed get scopeButtons() {
- return <div style={{ height: 25, paddingLeft: "4px", paddingRight: "4px", border: "1px solid gray", borderRadius: "0.3em", borderBottom: !this.open ? "1px solid" : "none", }}>
+ return <div style={{ height: 25, paddingLeft: "4px", paddingRight: "4px"}}>
<form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}>
<div style={{ display: "contents" }}>
<div className="radio" style={{ margin: 0 }}>
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index c4a6433c8..c7e2d02cb 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -512,7 +512,7 @@ export class MobileInterface extends React.Component {
return <div className="docButton"
title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
- onClick={e => TabDocView.PinDoc(this._activeDoc, isPinned)}>
+ onClick={e => TabDocView.PinDoc(this._activeDoc, { unpin: isPinned })}>
<FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
</div>;
} else return (null);