aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLionel Han <47760119+IGoByJoe@users.noreply.github.com>2020-08-10 09:56:58 -0700
committerLionel Han <47760119+IGoByJoe@users.noreply.github.com>2020-08-10 09:56:58 -0700
commite37653aa128d6c4614d45f27520381128bf2a117 (patch)
tree9518ad475536424f6d6e2e48207a926755ace5f1
parentc2d38b0fe7752a981e2d45fdd74fdbc62c87a6a8 (diff)
parentbf11e55b42406405bac72a0e533b18d792640768 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into new_audio
-rw-r--r--src/client/documents/Documents.ts8
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/DocumentManager.ts2
-rw-r--r--src/client/util/GroupManager.tsx2
-rw-r--r--src/client/util/InteractionUtils.tsx60
-rw-r--r--src/client/util/SettingsManager.scss16
-rw-r--r--src/client/util/SettingsManager.tsx8
-rw-r--r--src/client/views/DocComponent.tsx3
-rw-r--r--src/client/views/GestureOverlay.tsx59
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/InkingStroke.tsx30
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/MainViewNotifs.tsx9
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx11
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx7
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx72
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx58
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.tsx4
-rw-r--r--src/client/views/linking/LinkEditor.tsx8
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx9
-rw-r--r--src/client/views/nodes/AudioBox.scss5
-rw-r--r--src/client/views/nodes/AudioBox.tsx365
-rw-r--r--src/client/views/nodes/PresBox.tsx24
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx3
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/fields/Doc.ts2
31 files changed, 472 insertions, 327 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 070068401..a6a697574 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -577,6 +577,8 @@ export namespace Docs {
viewDoc.author = Doc.CurrentUserEmail;
viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
+ if (Doc.UserDoc()?.defaultAclPrivate) viewDoc["ACL-Public"] = dataDoc["ACL-Public"] = "Not Shared";
+
return Doc.assign(viewDoc, delegateProps, true);
}
@@ -865,6 +867,12 @@ export namespace DocUtils {
const facet = filterFacets[facetKey];
const satisfiesFacet = Object.keys(facet).some(value => {
if (facet[value] === "match") {
+ if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc
+ const allKeys = Array.from(Object.keys(d));
+ allKeys.push(...Object.keys(Doc.GetProto(d)));
+ const keys = allKeys.filter(key => key.includes(facetKey.substring(1)));
+ return keys.some(key => Field.toString(d[key] as Field).includes(value));
+ }
return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
}
return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 817125752..4e75a51f6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -436,7 +436,7 @@ export class CurrentUserUtils {
{ toolTip: "Drag a audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc, noviceMode: true },
{ toolTip: "Drag a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc, noviceMode: true },
- { toolTip: "Drag a presentation view", title: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
+ { toolTip: "Drag a presentation view", title: "Present", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true },
{ toolTip: "Drag a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc },
{ toolTip: "Drag a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
// { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 523dbfca0..bd57e7f48 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -152,7 +152,7 @@ export class DocumentManager {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
- docView?.props.focus(annotatedDoc, false);
+ first.props.focus(annotatedDoc, false);
}
}
if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 277e96a89..d03989675 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -366,7 +366,7 @@ export default class GroupManager extends React.Component<{}> {
interactive={true}
contents={contents}
dialogueBoxStyle={{ width: "90%", height: "70%" }}
- closeOnExternalClick={action(() => { this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; })}
+ closeOnExternalClick={action(() => { this.createGroupModalOpen = false; this.selectedUsers = null; TaskCompletionBox.taskCompleted = false; })}
/>
);
}
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 04a750f93..ae3b3e064 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -91,15 +91,61 @@ export namespace InteractionUtils {
return myTouches;
}
- export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
+ export function CreatePoints(points: { X: number, Y: number }[], left: number, top: number,
color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+ let pts: { X: number; Y: number; }[] = [];
+ if (shape) { //if any of the shape are true
+ pts = makePolygon(shape, points);
+ }
+ else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) {
+ for (var i = 0; i < points.length - 3; i += 4) {
+ const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, array);
+ pts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ }
+ else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
+ //pointer is up (first and last points are the same)
+ const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+ newPoints.pop();
+ const bezierCurves = fitCurve(newPoints, parseInt(bezier));
+ for (const curve of bezierCurves) {
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, curve);
+ pts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ } else {
+ pts = points.slice();
+ // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here.
+ if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) {
+ pts.pop();
+ }
+ }
+ if (isNaN(scalex)) {
+ scalex = 1;
+ }
+ if (isNaN(scaley)) {
+ scaley = 1;
+ }
+ console.log(pts.length);
+ return pts;
+ }
+
+
+
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
+ color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
+ dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
let pts: { X: number; Y: number; }[] = [];
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
}
- else if (points.length >= 5 && points[3].X === points[4].X) {
+ else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) {
for (var i = 0; i < points.length - 3; i += 4) {
const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
for (var t = 0; t < 1; t += 0.01) {
@@ -139,6 +185,15 @@ export namespace InteractionUtils {
const dashArray = String(Number(width) * Number(dash));
const defGuid = Utils.GenerateGuid();
const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
+
+ const addables = pts.map((pts, i) =>
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" stroke-width={1} fill="blue"
+ onDoubleClick={(e) => { console.log(i); }} pointerEvents="all" cursor="all-scroll"
+ />
+ </svg>);
+
+
return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
{nodefs ? (null) : <defs>
{arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
@@ -167,6 +222,7 @@ export namespace InteractionUtils {
markerStart={`url(#${arrowStart + "Start" + defGuid})`}
markerEnd={`url(#${arrowEnd + "End" + defGuid})`}
/>
+ {/* {addables} */}
</svg>);
}
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index ca27cfa3c..01dda0aca 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -99,8 +99,8 @@
display: flex;
.modes-select {
- width: 170px;
- margin-right: 65px;
+ // width: 170px;
+ margin-right: 10px;
color: black;
border-radius: 5px;
@@ -109,10 +109,12 @@
}
}
- .modes-playground {
+ .modes-playground,
+ .default-acl {
display: flex;
- .playground-check {
+ .playground-check,
+ .acl-check {
margin-right: 5px;
&:hover {
@@ -122,7 +124,13 @@
.playground-text {
color: black;
+ margin-right: 10px;
}
+
+ .acl-text {
+ color: black;
+ }
+
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8b58880d4..e3b91925a 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -40,7 +40,7 @@ export default class SettingsManager extends React.Component<{}> {
}
public close = action(() => this.isOpen = false);
- public open = action(() => (this.isOpen = true) && SelectionManager.DeselectAll());
+ public open = action(() => this.isOpen = true);
private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
private changePassword = async () => {
@@ -136,13 +136,17 @@ export default class SettingsManager extends React.Component<{}> {
<input className="playground-check" type="checkbox" checked={this.playgroundMode} onChange={this.playgroundModeToggle} />
<div className="playground-text">Playground Mode</div>
</div>
+ <div className="default-acl">
+ <input className="acl-check" type="checkbox" checked={BoolCast(Doc.UserDoc()?.defaultAclPrivate)} onChange={action(() => Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} />
+ <div className="acl-text">Default access private</div>
+ </div>
</div>;
}
@computed get accountsContent() {
return <div className="accounts-content">
<button onClick={this.googleAuthorize} value="data">Link to Google</button>
- <button onClick={GroupManager.Instance?.open}>Manage groups</button>
+ <button onClick={() => GroupManager.Instance?.open()}>Manage groups</button>
</div>;
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 831c246d1..eea133ed9 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -158,7 +158,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
if (this.props.Document[AclSym]) {
added.forEach(d => {
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
+ if (d.author === key.substring(4).replace("_", ".") && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
+ else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
}
});
}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 30df7cf9a..81bb542dd 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -733,19 +733,8 @@ export default class GestureOverlay extends Touchable {
this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
- // this._points.push({ X: left, Y: top });
-
- // this._points.push({ X: left, Y: top });
- // this._points.push({ X: left, Y: top });
-
- // this._points.push({ X: left, Y: top - 1 });
break;
case "triangle":
- // this._points.push({ X: left, Y: bottom });
- // this._points.push({ X: right, Y: bottom });
- // this._points.push({ X: (right + left) / 2, Y: top });
- // this._points.push({ X: left, Y: bottom });
- // this._points.push({ X: left, Y: bottom - 1 });
this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
@@ -779,60 +768,12 @@ export default class GestureOverlay extends Touchable {
}
this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
- // this._points.push({ X: centerX, Y: top });
- // this._points.push({ X: centerX + radius / 2, Y: top });
-
- // this._points.push({ X: right, Y: top + radius / 2 });
- // this._points.push({ X: right, Y: top + radius });
- // this._points.push({ X: right, Y: top + radius });
- // this._points.push({ X: right, Y: bottom - radius / 2 });
-
- // this._points.push({ X: right - radius / 2, Y: bottom });
- // this._points.push({ X: right - radius, Y: bottom });
- // this._points.push({ X: right - radius, Y: bottom });
- // this._points.push({ X: left + radius / 2, Y: bottom });
-
- // this._points.push({ X: left, Y: bottom - radius / 2 });
- // this._points.push({ X: left, Y: bottom - radius });
- // this._points.push({ X: left, Y: bottom - radius });
- // this._points.push({ X: left, Y: top + radius / 2 });
-
- // this._points.push({ X: left + radius / 2, Y: top });
- // this._points.push({ X: left + radius, Y: top });
-
-
-
-
-
-
break;
case "line":
- // const firstx = this._points[0].X;
- // const firsty = this._points[0].Y;
- // const lastx = this._points[this._points.length - 1].X;
- // const lasty = this._points[this._points.length - 1].Y;
- // const fourth = (lastx - firstx) / 4;
- // const m = (lasty - firsty) / (lastx - firstx);
- // const b = firsty - m * firstx;
this._points.push({ X: firstx, Y: firsty });
this._points.push({ X: firstx, Y: firsty });
- this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
- this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
- this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
- this._points.push({ X: firstx + fourth, Y: m * (firstx + fourth) + b });
-
- this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
- this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
- this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
- this._points.push({ X: firstx + 2 * fourth, Y: m * (firstx + 2 * fourth) + b });
-
- this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
- this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
- this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
- this._points.push({ X: firstx + 3 * fourth, Y: m * (firstx + 3 * fourth) + b });
-
this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b });
this._points.push({ X: firstx + 4 * fourth, Y: m * (firstx + 4 * fourth) + b });
break;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 0ea02e3cb..182f6397c 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -108,7 +108,9 @@ export default class KeyManager {
GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
GroupManager.Instance.close();
- CollectionFreeFormViewChrome.Instance.clearKeep();
+ CollectionFreeFormViewChrome.Instance?.clearKeep();
+ window.getSelection()?.empty();
+ document.body.focus();
break;
case "delete":
case "backspace":
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 4a77728b6..e3390426b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -52,8 +52,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
FormatShapePane.Instance.Pinned = true;
}
- private _prevX = 0;
- private _prevY = 0;
+ public _prevX = 0;
+ public _prevY = 0;
private _controlNum = 0;
@action
onControlDown = (e: React.PointerEvent, i: number): void => {
@@ -67,6 +67,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
@action
changeCurrPoint = (i: number) => {
FormatShapePane.Instance._currPoint = i;
+ document.addEventListener("keydown", this.delPts, true);
}
@action
@@ -86,6 +87,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this._controlUndo?.end();
this._controlUndo = undefined;
}
+ @action
+ delPts = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ if (e instanceof KeyboardEvent ? e.key === "-" : true) {
+ FormatShapePane.Instance.deletePoints();
+ }
+ }
+
public static MaskDim = 50000;
render() {
@@ -115,6 +123,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
"none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
+ //points for adding
+ const apoints = InteractionUtils.CreatePoints(data, left, top, strokeColor, strokeWidth, strokeWidth,
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
+ StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
+ StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
+
const controlPoints: { X: number, Y: number, I: number }[] = [];
const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
@@ -146,11 +160,20 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
// }
const dotsize = String(Math.max(width * scaleX, height * scaleY) / 40);
+ const addpoints = apoints.map((pts, i) =>
+
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="invisible" stroke-width={String(Number(dotsize) / 2)} fill="invisible"
+ onPointerDown={(e) => { FormatShapePane.Instance.addPoints(pts.X, pts.Y, apoints, i, controlPoints); }} pointerEvents="all" cursor="all-scroll"
+ />
+ </svg>);
+
const controls = controlPoints.map((pts, i) =>
<svg height="10" width="10">
<circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="red"
- onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll" />
+ onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll"
+ />
</svg>);
const handles = handlePoints.map((pts, i) =>
@@ -193,6 +216,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
</defs>
{hpoints}
{points}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? addpoints : ""}
{FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""}
{FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""}
{FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f5dccd567..4d5dfc99e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -441,7 +441,7 @@ export class MainView extends React.Component {
}
sidebarScreenToLocal = () => new Transform(0, (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
//sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
- mainContainerXf = () => this.sidebarScreenToLocal().translate(-55, 0);
+ mainContainerXf = () => this.sidebarScreenToLocal().translate(-55, -this._buttonBarHeight);
@computed get closePosition() { return 55 + this.flyoutWidth; }
@computed get flyout() {
diff --git a/src/client/views/MainViewNotifs.tsx b/src/client/views/MainViewNotifs.tsx
index ce47e1cf1..89006ebc8 100644
--- a/src/client/views/MainViewNotifs.tsx
+++ b/src/client/views/MainViewNotifs.tsx
@@ -1,12 +1,13 @@
-import { action, computed, observable } from 'mobx';
+import { observable } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils';
-import { SetupDrag, DragManager } from '../util/DragManager';
+import { returnFalse, setupMoveUpEvents } from '../../Utils';
+import { DragManager } from '../util/DragManager';
import "./MainViewNotifs.scss";
import { MainView } from './MainView';
+import { NumCast } from '../../fields/Types';
@observer
@@ -27,7 +28,7 @@ export class MainViewNotifs extends React.Component {
render() {
const length = MainViewNotifs.NotifsCol ? DocListCast(MainViewNotifs.NotifsCol.data).length : 0;
- return <div className="mainNotifs-container" style={{ width: 15, height: 15 }} ref={this._notifsRef}>
+ return <div className="mainNotifs-container" style={{ width: 15, height: 15, top: 12 + NumCast(MainViewNotifs.NotifsCol?.position) * 60 }} ref={this._notifsRef}>
<button className="mainNotifs-badge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}
onPointerDown={this.onPointerDown} >
{length}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 7e096fa37..3691e844f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -513,7 +513,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
if (doc instanceof Doc) {
- tab.titleElement[0].onclick = (e: any) => tab.titleElement[0].focus();
+ tab.titleElement[0].onclick = (e: any) => {
+ if (Date.now() - tab.titleElement[0].lastClick < 1000) tab.titleElement[0].select();
+ tab.titleElement[0].lastClick = Date.now();
+ tab.titleElement[0].focus();
+ };
tab.titleElement[0].onchange = (e: any) => {
tab.titleElement[0].size = e.currentTarget.value.length + 1;
Doc.GetProto(doc).title = e.currentTarget.value, true;
@@ -691,8 +695,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
return (this.props as any).glContainer.parent.parent;
}
get _tab(): any {
- const tab = (this.props as any).glContainer.tab.element[0] as HTMLElement;
- return tab.getElementsByClassName("lm_title")?.[0];
+ const tab = (this.props as any).glContainer.tab?.element[0] as HTMLElement;
+ return tab?.getElementsByClassName("lm_title")?.[0];
}
constructor(props: any) {
super(props);
@@ -757,7 +761,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, "white") }),
(data) => {
const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document));
- this._tab.style.backgroundColor = selected ? data.color : "";
+ this._tab && (this._tab.style.backgroundColor = selected ? data.color : "");
}
);
}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index d11d6a5ba..8a1dd8472 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -189,7 +189,14 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
ContentScaling: returnOne
};
- const field = props.Document[props.fieldKey];
+ let matchedKeys = [props.fieldKey];
+ if (props.fieldKey.startsWith("*")) {
+ const allKeys = Array.from(Object.keys(props.Document));
+ allKeys.push(...Array.from(Object.keys(Doc.GetProto(props.Document))));
+ matchedKeys = allKeys.filter(key => key.includes(props.fieldKey.substring(1)));
+ }
+ const fieldKey = matchedKeys.length ? matchedKeys[0] : props.fieldKey;
+ const field = props.Document[fieldKey];
const doc = FieldValue(Cast(field, Doc));
const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
@@ -210,7 +217,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
};
let contents: any = "incorrect type";
- if (type === undefined) contents = <FieldView {...props} />;
+ if (type === undefined) contents = <FieldView {...props} fieldKey={fieldKey} />;
if (type === "number") contents = typeof field === "number" ? NumCast(field) : "--" + typeof field + "--";
if (type === "string") contents = typeof field === "string" ? (StrCast(field) === "" ? "--" : StrCast(field)) : "--" + typeof field + "--";
if (type === "boolean") contents = typeof field === "boolean" ? (BoolCast(field) ? "true" : "false") : "--" + typeof field + "--";
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index fe3d57bdb..b27af66ba 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,7 +4,7 @@ import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import Switch from 'rc-switch';
-import { DataSym, Doc, HeightSym, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, HeightSym, WidthSym, DocListCastAsync } from "../../../fields/Doc";
import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
@@ -28,6 +28,7 @@ import { CollectionViewType } from "./CollectionView";
import { SnappingManager } from "../../util/SnappingManager";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocUtils } from "../../documents/Documents";
+import { MainViewNotifs } from "../MainViewNotifs";
const _global = (window /* browser */ || global /* node */) as any;
type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -298,6 +299,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
const srcInd = docs.indexOf(doc);
docs.splice(srcInd, 1);
docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc);
+ DocListCastAsync(docs).then(resolvedDocs => {
+ const pos = resolvedDocs?.findIndex(shareDoc => shareDoc.icon === "users") || 0; // hopefully find out if the sharing doc has been moved
+ if (MainViewNotifs.NotifsCol && pos !== -1) MainViewNotifs.NotifsCol.position = pos;
+ });
} else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated
if (targInd === -1) targInd = docs.length;
else targInd = docs.indexOf(newDocs[0]) + 1;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 72aece284..d1aeb1b65 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -182,8 +182,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
childDocs = docsforFilter;
-
- const docFilters = this.docFilters();
const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 837ae7e86..0feec3fbd 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -151,7 +151,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.Document[AclSym]) {
added.forEach(d => {
for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- if (d.author === Doc.CurrentUserEmail && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
+ if (d.author === key.substring(4).replace("_", ".") && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
}
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 509d7cda8..163554db9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1258,7 +1258,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
!this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
this.props.ContainingCollectionView &&
- optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+ optionItems.push({ description: "Undo Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 6263be261..1ffa2fbed 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -163,6 +163,78 @@ export default class FormatShapePane extends AntimodeMenu {
@undoBatch
@action
+ addPoints = (x: number, y: number, pts: { X: number, Y: number }[], index: number, control: { X: number, Y: number }[]) => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ var counter = 0;
+ for (var k = 0; k < index; k++) {
+ control.forEach(pt => (pts[k].X === pt.X && pts[k].Y === pt.Y) && counter++);
+ }
+ //decide where to put the new coordinate
+ const spNum = Math.floor(counter / 2) * 4 + 2;
+
+ for (var i = 0; i < spNum; i++) {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ for (var j = 0; j < 4; j++) {
+ newPoints.push({ X: x, Y: y });
+
+ }
+ for (var i = spNum; i < ink.length; i++) {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ this._currPoint = -1;
+ doc.data = new InkField(newPoints);
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ deletePoints = () => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1 && this._currPoint !== -1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink && ink.length > 4) {
+ const newPoints: { X: number, Y: number }[] = [];
+
+ console.log(ink.length, this._currPoint, Math.floor((this._currPoint + 2) / 4));
+
+ const toRemove = Math.floor(((this._currPoint + 2) / 4));
+ for (var i = 0; i < ink.length; i++) {
+ if (Math.floor((i + 2) / 4) !== toRemove) {
+ console.log(i, toRemove);
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ }
+ this._currPoint = -1;
+ doc.data = new InkField(newPoints);
+ if (newPoints.length === 4) {
+ const newerPoints: { X: number, Y: number }[] = [];
+ newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+ newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y });
+ newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+ newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y });
+ doc.data = new InkField(newerPoints);
+
+ }
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
rotate = (angle: number) => {
const _centerPoints: { X: number, Y: number }[] = [];
SelectionManager.SelectedDocuments().forEach(action(inkView => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index db4b674b5..f1df7998b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,6 +3,8 @@ import AntimodeMenu from "../../AntimodeMenu";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { unimplementedFunction } from "../../../../Utils";
+import { undoBatch } from "../../../util/UndoManager";
+import { Tooltip } from "@material-ui/core";
@observer
export default class MarqueeOptionsMenu extends AntimodeMenu {
@@ -23,34 +25,34 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
render() {
const buttons = [
- <button
- className="antimodeMenu-button"
- title="Create a Collection"
- key="group"
- onPointerDown={this.createCollection}>
- <FontAwesomeIcon icon="object-group" size="lg" />
- </button>,
- <button
- className="antimodeMenu-button"
- title="Summarize Documents"
- key="summarize"
- onPointerDown={this.summarize}>
- <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
- </button>,
- <button
- className="antimodeMenu-button"
- title="Delete Documents"
- key="delete"
- onPointerDown={this.delete}>
- <FontAwesomeIcon icon="trash-alt" size="lg" />
- </button>,
- <button
- className="antimodeMenu-button"
- title="Change to Text"
- key="inkToText"
- onPointerDown={this.inkToText}>
- <FontAwesomeIcon icon="font" size="lg" />
- </button>,
+ <Tooltip key="group" title={<><div className="dash-tooltip">Create a Collection</div></>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.createCollection}>
+ <FontAwesomeIcon icon="object-group" size="lg" />
+ </button>
+ </Tooltip>,
+ <Tooltip key="summarize" title={<><div className="dash-tooltip">Summarize Documents</div></>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.summarize}>
+ <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+ </button>
+ </Tooltip>,
+ <Tooltip key="delete" title={<><div className="dash-tooltip">Delete Documents</div></>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
+ </button>
+ </Tooltip>,
+ <Tooltip key="inkToText" title={<><div className="dash-tooltip">Change to Text</div></>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.inkToText}>
+ <FontAwesomeIcon icon="font" size="lg" />
+ </button>
+ </Tooltip>,
];
return this.getElement(buttons);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 858f33291..1a708d67d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -390,7 +390,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- @action
+ @undoBatch @action
collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
@@ -415,7 +415,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- @action
+ @undoBatch @action
syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
if (e instanceof KeyboardEvent ? e.key === "i" : true) {
@@ -491,7 +491,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
}
- @action
+ @undoBatch @action
summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
index b1c3d3dc5..e0f3eca44 100644
--- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -970,9 +970,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-name">
{this.editableTitle}
<div className="propertiesView-presSelected">
- {PresBox.Instance?._selectedArray.length} selected
+ {PresBox.Instance._selectedArray.length} selected
<div className="propertiesView-selectedList">
- {PresBox.Instance?.listOfSelected}
+ {PresBox.Instance.listOfSelected}
</div>
</div>
</div>
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 5832a2181..c9e39cf7b 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -355,11 +355,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
this.openDropdown = !this.openDropdown;
}
- @undoBatch @action
- changeFollowBehavior = (follow: string) => {
+ @undoBatch
+ changeFollowBehavior = action((follow: string) => {
this.openDropdown = false;
Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
- }
+ });
@computed
get followingDropdown() {
@@ -382,7 +382,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
</div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("onRight")}>
- Always open in right tab
+ Always open in new pane on right
</div>
<div className="linkEditor-followingDropdown-option"
onPointerDown={() => this.changeFollowBehavior("inTab")}>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index b95fccf2a..21c666a4d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -162,7 +162,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "Default") {
- this.props.addDocTab(this.props.destinationDoc, StrCast(linkDoc.followLinkLocation));
+ const annotationOn = this.props.destinationDoc.annotationOn as Doc;
+ this.props.addDocTab(annotationOn instanceof Doc ? annotationOn : this.props.destinationDoc, StrCast(linkDoc.followLinkLocation));
+ if (annotationOn) {
+ setTimeout(() => {
+ const dv = DocumentManager.Instance.getFirstDocumentView(this.props.destinationDoc);
+ dv?.props.focus(this.props.destinationDoc, false);
+ });
+ }
} else {
DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 0d787d9af..c80e3af24 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -8,6 +8,11 @@
position: relative;
cursor: default;
+ .audiobox-inner {
+ width:100%;
+ height: 100%;
+ }
+
.audiobox-buttons {
display: flex;
width: 100%;
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index bc89cb6f9..289388320 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -25,6 +25,7 @@ import { List } from "../../../fields/List";
import { Scripting } from "../../util/Scripting";
import Waveform from "react-audio-waveform";
import axios from "axios";
+import { SnappingManager } from "../../util/SnappingManager";
const _global = (window /* browser */ || global /* node */) as any;
declare class MediaRecorder {
@@ -85,14 +86,21 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
constructor(props: Readonly<FieldViewProps>) {
super(props);
- // onClick play script
- if (!AudioBox.RangeScript) {
- AudioBox.RangeScript = ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart), (this.audioEnd))`, { scriptContext: "any" })!;
- }
+ // 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" })!;
+ }
- if (!AudioBox.LabelScript) {
- AudioBox.LabelScript = ScriptField.MakeScript(`scriptContext.playFrom((this.audioStart))`, { scriptContext: "any" })!;
+ getLinkData(l: Doc) {
+ let la1 = l.anchor1 as Doc;
+ let la2 = l.anchor2 as Doc;
+ let linkTime = NumCast(l.anchor2_timecode);
+ 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() {
@@ -110,7 +118,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
scrollLinkId => {
if (scrollLinkId) {
DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => {
- const linkTime = Doc.AreProtosEqual(l.anchor1 as Doc, this.dataDoc) ? NumCast(l.anchor1_timecode) : NumCast(l.anchor2_timecode);
+ const { linkTime } = this.getLinkData(l);
setTimeout(() => { this.playFromTime(linkTime); Doc.linkFollowHighlight(l); }, 250);
});
Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
@@ -121,34 +129,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
selected => {
const sel = selected.length ? selected[0].props.Document : undefined;
- let link;
- if (sel) {
- // for determining if the link is created after recording (since it will use linkTime rather than creation date)
- DocListCast(this.dataDoc.links).map((l, i) => {
- let la1 = l.anchor1 as Doc;
- let la2 = l.anchor2 as Doc;
- if (la1 === sel || la2 === sel) { // if the selected document is linked to this audio
- let linkTime = NumCast(l.anchor2_timecode);
- let endTime;
- if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- la1 = l.anchor2 as Doc;
- la2 = l.anchor1 as Doc;
- linkTime = NumCast(l.anchor1_timecode);
- }
- if (la2.audioStart) linkTime = NumCast(la2.audioStart);
- if (la1.audioStart) linkTime = NumCast(la1.audioStart);
-
- if (la1.audioEnd) endTime = NumCast(la1.audioEnd);
- if (la2.audioEnd) endTime = NumCast(la2.audioEnd);
-
- if (linkTime) {
- link = true;
- this.layoutDoc.playOnSelect && this.recordingStart && sel && !Doc.AreProtosEqual(sel, this.props.Document) && (endTime ? this.playFrom(linkTime, endTime) : this.playFrom(linkTime));
- }
- }
- });
- }
-
+ const link = sel && DocListCast(this.dataDoc.links).forEach(l => (l.anchor1 === sel || l.anchor2 === sel) && this.playLink(sel), false);
// 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());
@@ -158,18 +139,35 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
}
+ 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.recordingStart && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
+ }
+ }
+ });
+ return link;
+ }
+
// for updating the timecode
timecodeChanged = () => {
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 => {
- let la1 = l.anchor1 as Doc;
- let linkTime = NumCast(l.anchor2_timecode);
- if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- linkTime = NumCast(l.anchor1_timecode);
- la1 = l.anchor2 as Doc;
- }
+ const { la1, linkTime } = this.getLinkData(l);
if (linkTime > NumCast(this.layoutDoc.currentTimecode) && linkTime < htmlEle.currentTime) {
Doc.linkFollowHighlight(la1);
}
@@ -195,7 +193,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
let play;
clearTimeout(play);
this._duration = endTime - seekTimeInSeconds;
- if (this._ele && AudioBox.Enabled) {
+ if (Number.isNaN(this._ele?.duration)) {
+ setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
+ } else if (this._ele && AudioBox.Enabled) {
if (seekTimeInSeconds < 0) {
if (seekTimeInSeconds > -1) {
setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
@@ -349,47 +349,33 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
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;
- }
- return false;
- }),
- action((e: PointerEvent, movement: number[]) => {
- if (Math.abs(movement[0]) > 15) {
- this.createNewMarker(this._markerStart, 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;
}
+ return false;
+ }),
+ action((e: PointerEvent, movement: number[]) => {
+ (Math.abs(movement[0]) > 15) && this.createMarker(this._markerStart, toTimeline(e.clientX - rect.x));
this._visible = false;
}),
- emptyFunction);
- }
- // returns the selection container
- @computed get container() {
- return <div className="audiobox-container" style={{
- left: `${NumCast(this._markerStart) / this.audioDuration * 100}%`,
- width: `${Math.abs(this._markerStart - this._markerEnd) / this.audioDuration * 100}%`, height: "100%", top: "0%"
- }}></div>;
+ (e: PointerEvent) => e.shiftKey && this.createMarker(this._ele!.currentTime)
+ );
}
- // creates a new marker
@action
- createNewMarker(audioStart: number, audioEnd: number) {
- const newMarker = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any, isLabel: false,
+ createMarker(audioStart: number, audioEnd?: number) {
+ const marker = Docs.Create.LabelDocument({
+ title: ComputedField.MakeFunction(`formatToTime(self.audioStart) + "-" + formatToTime(self.audioEnd)`) as any, isLabel: audioEnd === undefined,
useLinkSmallAnchor: true, hideLinkButton: true, audioStart, audioEnd, _showSidebar: false,
_autoHeight: true, annotationOn: this.props.Document
});
- this.addMark(newMarker);
- }
-
- // adds an annotation marker or label
- @action
- addMark(marker: Doc) {
- marker.data = "";
+ 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 {
@@ -403,10 +389,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._left = left;
const rect = (e.target as any).getBoundingClientRect();
const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.audioDuration;
- setupMoveUpEvents(this, e, () => {
- this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x));
- return false;
- },
+ setupMoveUpEvents(this, e,
+ () => {
+ this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x));
+ return false;
+ },
() => this._ele!.currentTime = this.layoutDoc.currentTimecode = toTimeline(e.clientX - rect.x),
emptyFunction);
}
@@ -462,6 +449,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
return max - 1;
}
+ @computed get selectionContainer() {
+ return <div className="audiobox-container" style={{
+ left: `${NumCast(this._markerStart) / this.audioDuration * 100}%`,
+ width: `${Math.abs(this._markerStart - this._markerEnd) / this.audioDuration * 100}%`, height: "100%", top: "0%"
+ }} />;
+ }
+
// returns the audio waveform
@computed get waveform() {
return <Waveform
@@ -515,12 +509,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
labelScript = () => AudioBox.LabelScript;
render() {
- const interactive = this.active() ? "-interactive" : "";
+ const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
this._first = true; // for indicating the first marker that is rendered
this.path && this._buckets.length !== 100 ? this.peaks : null; // render waveform if audio is done recording
const markerDoc = (mark: Doc, script: undefined | (() => ScriptField)) => {
return <DocumentView {...this.props}
Document={mark}
+ focus={() => this.playLink(mark)}
pointerEvents={true}
NativeHeight={returnZero}
NativeWidth={returnZero}
@@ -534,125 +529,113 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
bringToFront={emptyFunction}
scriptContext={this} />;
};
- return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} onClick={!this.path ? this.recordClick : undefined}
- style={{ pointerEvents: !interactive ? "none" : undefined }}>
- {!this.path ?
- <div className="audiobox-buttons">
- <div className="audiobox-dictation" onClick={this.onFile}>
- <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
- </div>
- {this.audioState === "recording" ?
- <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"} />
- </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"} />
- </div>
- <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}</div>
+ return <div className="audiobox-container" onContextMenu={this.specificContextMenu} onClick={!this.path ? this.recordClick : undefined}>
+ <div className="audiobox-inner" style={{ pointerEvents: !interactive ? "none" : undefined }}>
+ {!this.path ?
+ <div className="audiobox-buttons">
+ <div className="audiobox-dictation" onClick={this.onFile}>
+ <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
- :
- <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}>
- RECORD
+ {this.audioState === "recording" ?
+ <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"} />
+ </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"} />
+ </div>
+ <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}</div>
+ </div>
+ :
+ <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}>
+ 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" onClick={e => { e.stopPropagation(); e.preventDefault(); }}
- onPointerDown={e => {
- e.stopPropagation();
- e.preventDefault();
- 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();
+ </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" 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="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);
}
- this.onPointerDownTimeline(e);
- }
- if (e.button === 0 && e.shiftKey) {
- this.addMark(Docs.Create.LabelDocument({ title: ComputedField.MakeFunction(`formatToTime(self.audioStart)`) as any, useLinkSmallAnchor: true, hideLinkButton: true, isLabel: true, audioStart: this._ele!.currentTime, _showSidebar: false, _autoHeight: true, annotationOn: this.props.Document }));
- }
- }}>
- <div className="waveform">
- {this.waveform}
+ 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}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ rootSelected={returnFalse}
+ ContainingCollectionDoc={this.props.Document}
+ parentActive={returnTrue}
+ bringToFront={emptyFunction}
+ backgroundColor={returnTransparent}
+ ContentScaling={returnOne}
+ forcedBackgroundColor={returnTransparent}
+ pointerEvents={false}
+ 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(); } }} />
+ </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.audioDuration * 100}%`, pointerEvents: "none" }} />
+ {this.audio}
+ </div>
+ <div className="current-time">
+ {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}
+ </div>
+ <div className="total-time">
+ {formatTime(Math.round(this.audioDuration))}
</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) => {
- let la1 = l.anchor1 as Doc;
- let la2 = l.anchor2 as Doc;
- let linkTime = NumCast(l.anchor2_timecode);
- if (Doc.AreProtosEqual(la1, this.dataDoc)) {
- la1 = l.anchor2 as Doc;
- la2 = l.anchor1 as Doc;
- linkTime = NumCast(l.anchor1_timecode);
- }
-
- if (la2.audioStart && !la2.audioEnd) {
- linkTime = NumCast(la2.audioStart);
- }
-
- return !linkTime ? (null) :
- <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}container`} key={l[Id]} style={{ left: `${linkTime / this.audioDuration * 100}%` }} onClick={e => e.stopPropagation()}>
- <DocumentView {...this.props}
- Document={l}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- rootSelected={returnFalse}
- ContainingCollectionDoc={this.props.Document}
- parentActive={returnTrue}
- bringToFront={emptyFunction}
- backgroundColor={returnTransparent}
- ContentScaling={returnOne}
- forcedBackgroundColor={returnTransparent}
- pointerEvents={false}
- 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(linkTime); e.stopPropagation(); e.preventDefault(); } }} />
- </div>;
- })}
- {this._visible ? this.container : null}
-
- <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>
- <div className="current-time">
- {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}
- </div>
- <div className="total-time">
- {formatTime(Math.round(this.audioDuration))}
</div>
</div>
- </div>
- }
+ }</div>
</div>;
}
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index b7af4683e..849fc4076 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -83,6 +83,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
@computed get isPres(): boolean {
if (this.selectedDoc?.type === DocumentType.PRES) {
+ document.removeEventListener("keydown", this.keyEvents, true);
document.addEventListener("keydown", this.keyEvents, true);
return true;
} else {
@@ -91,6 +92,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
}
}
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ componentWillUnmount() {
+ document.removeEventListener("keydown", this.keyEvents, true);
+ }
componentDidMount() {
this.rootDoc.presBox = this.rootDoc;
@@ -517,29 +521,41 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
// Key for when the presentaiton is active (according to Selection Manager)
@action
keyEvents = (e: KeyboardEvent) => {
- e.stopPropagation();
- e.preventDefault();
-
+ let handled = false;
+ const anchorNode = document.activeElement as HTMLDivElement;
+ if (anchorNode && anchorNode.className?.includes("lm_title")) return;
if (e.keyCode === 27) { // Escape key
if (this.layoutDoc.presStatus === "edit") this._selectedArray = [];
else this.layoutDoc.presStatus = "edit";
+ handled = true;
} if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all
- if (this.layoutDoc.presStatus === "edit") this._selectedArray = this.childDocs;
+ if (this.layoutDoc.presStatus === "edit") {
+ this._selectedArray = this.childDocs;
+ handled = true;
+ }
} if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back
this.back();
+ handled = true;
} if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next
this.next();
+ handled = true;
} if (e.keyCode === 32) { // spacebar to 'present' or autoplay
if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0);
else this.layoutDoc.presStatus = "manual";
+ handled = true;
}
if (e.keyCode === 8) { // delete selected items
if (this.layoutDoc.presStatus === "edit") {
this._selectedArray.forEach((doc, i) => {
this.removeDocument(doc);
});
+ handled = true;
}
}
+ if (handled) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
}
/**
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b0bf54be6..7d4bd5dd3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -250,8 +250,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
- const lastmodified = "lastmodified";
- (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))) && (this.dataDoc[lastmodified] = new DateField(new Date(Date.now())));
+ (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
if (!this._pause && !this.layoutDoc._timeStampOnEnter) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 1e44a379b..25fc8ff0b 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -628,7 +628,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
const endIndex = 30;
//this._endIndex = endIndex === -1 ? 12 : endIndex;
this._endIndex = 30;
- const headers = new Set<string>(["title", "author", "lastModified", "text"]);
+ const headers = new Set<string>(["title", "author", "*lastModified", "text"]);
if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) {
if (this.noresults === "") {
this.noresults = "No search results :(";
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6bfe91378..b535fea5a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -770,6 +770,7 @@ export namespace Doc {
}
});
copy.author = Doc.CurrentUserEmail;
+ Doc.UserDoc().defaultAclPrivate && (copy["ACL-Public"] = "Not Shared");
return copy;
}
@@ -794,6 +795,7 @@ export namespace Doc {
const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")");
target.layoutKey = targetKey;
applied && (Doc.GetProto(applied).type = templateDoc.type);
+ Doc.UserDoc().defaultAclPrivate && (applied["ACL-Public"] = "Not Shared");
return applied;
}
return undefined;