diff options
author | Lionel Han <47760119+IGoByJoe@users.noreply.github.com> | 2020-08-10 09:56:58 -0700 |
---|---|---|
committer | Lionel Han <47760119+IGoByJoe@users.noreply.github.com> | 2020-08-10 09:56:58 -0700 |
commit | e37653aa128d6c4614d45f27520381128bf2a117 (patch) | |
tree | 9518ad475536424f6d6e2e48207a926755ace5f1 /src | |
parent | c2d38b0fe7752a981e2d45fdd74fdbc62c87a6a8 (diff) | |
parent | bf11e55b42406405bac72a0e533b18d792640768 (diff) |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into new_audio
Diffstat (limited to 'src')
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; |