diff options
19 files changed, 148 insertions, 106 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d02742c5..2de1090a2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -603,6 +603,7 @@ export namespace Docs { viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc); viewDoc["acl-Public"] = dataDoc["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; + viewDoc["acl-Override"] = dataDoc["acl-Override"] = "unset"; return Doc.assign(viewDoc, delegateProps, true); } @@ -747,6 +748,8 @@ export namespace Docs { I.author = Doc.CurrentUserEmail; I.rotation = 0; I.data = new InkField(points); + I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add; + I["acl-Override"] = "unset"; return I; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index c3d78a028..116883fcb 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -19,6 +19,7 @@ export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) @observable public DocumentViews: DocumentView[] = []; + @observable LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = []; // singleton instance private static _instance: DocumentManager; @@ -32,6 +33,26 @@ export class DocumentManager { private constructor() { } + @action + public AddView = (view: DocumentView) => { + const linksList = DocListCast(view.props.Document.links); + linksList.forEach(link => { + const linkToDoc = link && LinkManager.getOppositeAnchor(link, view.props.Document); + linkToDoc && DocumentManager.Instance.DocumentViews.filter(dv => Doc.AreProtosEqual(dv.props.Document, linkToDoc)).forEach(dv => { + if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== view.props.LayoutTemplateString) { + this.LinkedDocumentViews.push({ a: dv, b: view, l: link }); + } + }); + }); + this.DocumentViews.push(view); + } + public RemoveView = (view: DocumentView) => { + const index = this.DocumentViews.indexOf(view); + index !== -1 && this.DocumentViews.splice(index, 1); + + this.LinkedDocumentViews.slice().forEach(action((pair, i) => pair.a === view || pair.b === view ? this.LinkedDocumentViews.splice(i, 1) : null)); + } + //gets all views public getDocumentViewsById(id: string) { const toReturn: DocumentView[] = []; @@ -105,25 +126,6 @@ export class DocumentManager { return toReturn; } - @computed - public get LinkedDocumentViews() { - TraceMobx(); - const pairs = DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { - const linksList = DocListCast(dv.props.Document.links); - pairs.push(...linksList.reduce((pairs, link) => { - const linkToDoc = link && LinkManager.getOppositeAnchor(link, dv.props.Document); - linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== docView1.props.LayoutTemplateString) { - pairs.push({ a: dv, b: docView1, l: link }); - } - }); - return pairs; - }, [] as { a: DocumentView, b: DocumentView, l: Doc }[])); - return pairs; - }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]); - - return pairs; - } static addRightSplit = (doc: Doc, finished?: () => void) => { CollectionDockingView.AddSplit(doc, "right"); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 91ffab41d..0169b07fc 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -339,7 +339,6 @@ export namespace DragManager { DragManager.Root().appendChild(dragDiv); } dragLabel.style.display = ""; - SnappingManager.SetIsDragging(true); const scaleXs: number[] = []; const scaleYs: number[] = []; const xs: number[] = []; @@ -410,6 +409,7 @@ export namespace DragManager { const hideSource = options?.hideSource ? true : false; eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource)); + SnappingManager.SetIsDragging(true); let lastX = downX; let lastY = downY; const xFromLeft = downX - elesCont.left; diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index cb15b5081..cb512bca8 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -278,21 +278,24 @@ export class GroupManager extends React.Component<{}> { */ @action createGroup = () => { - if (!this.inputRef.current?.value) { + const { value } = this.inputRef.current!; + if (!value) { alert("Please enter a group name"); return; } - if (this.inputRef.current.value.toLowerCase() === "admin" && this.getGroup("Admin")) { - alert("You cannot override the Admin group"); - return; + if (["admin", "public", "override"].includes(value.toLowerCase())) { + if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) { + alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`); + return; + } } - if (this.getGroup(this.inputRef.current.value)) { + if (this.getGroup(value)) { alert("Please select a unique group name"); return; } - this.createGroupDoc(this.inputRef.current.value, this.selectedUsers?.map(user => user.value)); + this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); this.selectedUsers = null; - this.inputRef.current.value = ""; + this.inputRef.current!.value = ""; this.buttonColour = "#979797"; const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect(); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 4f3cfcd03..694a7f3a7 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -63,7 +63,7 @@ export class LinkManager { // finds all links that contain the given anchor public getAllDirectLinks(anchor: Doc): Doc[] { const related = LinkManager.Instance.getAllLinks().filter(link => link).filter(link => { - const a1 = Cast(link.anchor1, Doc, null) + const a1 = Cast(link.anchor1, Doc, null); const a2 = Cast(link.anchor2, Doc, null); const protomatch1 = Doc.AreProtosEqual(anchor, a1); const protomatch2 = Doc.AreProtosEqual(anchor, a2); diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index b756716bf..06458827a 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -59,6 +59,7 @@ margin-top: -17px; margin-bottom: 10px; font-size: 10px; + min-height: 40px; input { height: 10px; @@ -71,7 +72,8 @@ } } - .layoutDoc-acls { + .layoutDoc-acls, + .myDocs-acls { display: flex; flex-direction: column; float: right; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index a53ef3cde..4f2676728 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -21,6 +21,7 @@ import { GroupMemberView } from "./GroupMemberView"; import "./SharingManager.scss"; import { SelectionManager } from "./SelectionManager"; import { intersection } from "lodash"; +import { SearchBox } from "../views/search/SearchBox"; export interface User { email: string; @@ -75,6 +76,7 @@ export class SharingManager extends React.Component<{}> { @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) private populating: boolean = false; // whether the list of users is populating or not @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used + @observable private myDocAcls: boolean = false; // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -158,7 +160,7 @@ export class SharingManager extends React.Component<{}> { docs.forEach(doc => { doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); - GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc); + distributeAcls(acl, permission as SharingPermissions, doc); if (group instanceof Doc) { const members: string[] = JSON.parse(StrCast(group.members)); @@ -189,7 +191,7 @@ export class SharingManager extends React.Component<{}> { * Called from the properties sidebar to change permissions of a user. */ shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => { - if (shareWith !== "Public") { + if (shareWith !== "Public" && shareWith !== "Override") { const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); docs.forEach(doc => { if (user) this.setInternalSharing(user, permission, doc); @@ -198,7 +200,7 @@ export class SharingManager extends React.Component<{}> { } else { docs.forEach(doc => { - if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("acl-Public", permission, doc); + if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc); }); } } @@ -246,12 +248,11 @@ export class SharingManager extends React.Component<{}> { const key = user.email.replace('.', '_'); const acl = `acl-${key}`; - const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); docs.forEach(doc => { doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); - GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc); + distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc)); @@ -285,13 +286,14 @@ export class SharingManager extends React.Component<{}> { /** * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share */ - private sharingOptions(uniform: boolean) { + private sharingOptions(uniform: boolean, override?: boolean) { const dropdownValues: string[] = Object.values(SharingPermissions); if (!uniform) dropdownValues.unshift("-multiple-"); - return dropdownValues.map(permission => + if (override) dropdownValues.unshift("unset"); + return dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission => ( <option key={permission} value={permission}> - {permission} + {permission === SharingPermissions.Add ? "Can Augment" : permission} </option> ) ); @@ -415,14 +417,20 @@ export class SharingManager extends React.Component<{}> { const groups = this.groupSort === "ascending" ? groupList.slice().sort(this.sortGroups) : this.groupSort === "descending" ? groupList.slice().sort(this.sortGroups).reverse() : groupList; // handles the case where multiple documents are selected - const docs = SelectionManager.SelectedDocuments().length < 2 ? + let docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]); + if (this.myDocAcls) { + const newDocs: Doc[] = []; + SearchBox.foreachRecursiveDoc(docs, doc => newDocs.push(doc)); + docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); + } + const targetDoc = docs[0]; // tslint:disable-next-line: no-unnecessary-callback-wrapper - const admin = docs.map(doc => GetEffectiveAcl(doc)).every(acl => acl === AclAdmin); // if the user has admin access to all selected docs + const admin = this.myDocAcls ? Boolean(docs.length) : docs.map(doc => GetEffectiveAcl(doc)).every(acl => acl === AclAdmin); // if the user has admin access to all selected docs // users in common between all docs const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))); @@ -440,7 +448,7 @@ export class SharingManager extends React.Component<{}> { > <span className={"padding"}>{user.email}</span> <div className="edit-actions"> - {admin ? ( + {admin || this.myDocAcls ? ( <select className={"permissions-dropdown"} value={permissions} @@ -496,7 +504,7 @@ export class SharingManager extends React.Component<{}> { // the list of groups shared with const groupListMap: (Doc | { groupName: string })[] = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true); - groupListMap.unshift({ groupName: "Public" }); + groupListMap.unshift({ groupName: "Public" }, { groupName: "Override" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.groupName)}`; const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); @@ -514,13 +522,13 @@ export class SharingManager extends React.Component<{}> { </div>) : (null)} <div className="edit-actions"> - {admin ? ( + {admin || this.myDocAcls ? ( <select className={"permissions-dropdown"} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)} > - {this.sharingOptions(uniform)} + {this.sharingOptions(uniform, group.groupName === "Override")} </select> ) : ( <div className={"permissions-dropdown"}> @@ -572,9 +580,13 @@ export class SharingManager extends React.Component<{}> { <input type="checkbox" onChange={action(() => this.showUserOptions = !this.showUserOptions)} /> <label style={{ marginRight: 10 }}>Individuals</label> <input type="checkbox" onChange={action(() => this.showGroupOptions = !this.showGroupOptions)} /> <label>Groups</label> </div> - <div className="layoutDoc-acls"> - <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label> + <div className="myDocs-acls"> + <input type="checkbox" onChange={action(() => this.myDocAcls = !this.myDocAcls)} checked={this.myDocAcls} /> <label>My Docs</label> </div> + {Doc.UserDoc().noviceMode ? (null) : + <div className="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label> + </div>} </div> } <div className="main-container"> diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 00754c416..462091f63 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -397,7 +397,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { </div> </form> {Doc.UserDoc().noviceMode ? (null) : <div onPointerDown={this.editOnClickScript} className="onClickFlyout-editScript"> Edit onClick Script</div>} - </div> + </div>; } @computed diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index c2c0d553b..9209cc21a 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -4,7 +4,7 @@ import { Checkbox, Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; -import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc"; +import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkField } from "../../fields/InkField"; import { ComputedField } from "../../fields/ScriptField"; @@ -323,10 +323,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return <select className="permissions-select" value={permission} onChange={e => this.changePermissions(e, user)}> - {dropdownValues.map(permission => { + {dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission => { return ( <option key={permission} value={permission}> - {permission} + {permission === SharingPermissions.Add ? "Can Augment" : permission} </option>); })} </select>; @@ -361,7 +361,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { /** * @returns a row of the permissions panel */ - sharingItem(name: string, admin: boolean, permission: string) { + sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { return <div className="propertiesView-sharingTable-item" key={name + permission} // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }} // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)} @@ -370,7 +370,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {/* {name !== "Me" ? this.notifyIcon : null} */} <div className="propertiesView-sharingTable-item-permission"> {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} - {permission === "Owner" ? this.expansionIcon : null} + {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} </div> </div>; } @@ -380,6 +380,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ @computed get sharingTable() { const AclMap = new Map<symbol, string>([ + [AclUnset, "unset"], [AclPrivate, SharingPermissions.None], [AclReadonly, SharingPermissions.View], [AclAddonly, SharingPermissions.Add], @@ -408,7 +409,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { for (const key of commonKeys) { const name = key.substring(4).replace("_", "."); const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); - if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public"/* && sidebarUsersDisplayed![name] !== false*/) { + if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); } } @@ -421,10 +422,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { // } // }) + const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); // shifts the current user, owner, public to the top of the doc. + tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "unset") : "-multiple-")); tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-")); - tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-")); - if (Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author)) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); + tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); + if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); return <div className="propertiesView-sharingTable"> {tableEntries} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 4c5a71ace..1ab104f6e 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -198,7 +198,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`, immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { - button['target-docFilters'] = Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters instanceof ObjectField ? ObjectField.MakeCopy(Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters as any as ObjectField) : undefined; + button['target-docFilters'] = (Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) instanceof ObjectField ? + ObjectField.MakeCopy((Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) as any as ObjectField) : undefined; button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; }, }; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d9c4d274a..a78923312 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -243,7 +243,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: e.stopPropagation(); return added; } - else if (de.complete.annoDragData) { + else if (de.complete.annoDragData && (!this.props.isAnnotationOverlay || de.complete.annoDragData.dragDocument === this.props.Document)) { e.stopPropagation(); return this.addDocument(de.complete.annoDragData.dropDocument); } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index e35474b81..522f46280 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus added.forEach(d => { for (const [key, value] of Object.entries(this.props.Document[AclSym])) { if (d.author === key.substring(4).replace("_", ".") && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); - else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true); + //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true); //else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); } }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 79a540210..011e10b88 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -122,9 +122,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden"; } - @computed get renderData() { - this._start; - if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) { + @computed.struct get renderData() { + this._start; SnappingManager.GetIsDragging(); + if (!this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) { return undefined; } this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform()); @@ -132,6 +132,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const bcont = this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv); const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv); + for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; + for (let apdiv = bdiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; const a = adiv.getBoundingClientRect(); const b = bdiv.getBoundingClientRect(); const atop = this.visibleY(adiv); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 1a2421bfd..6a1a41ca7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed } from "mobx"; +import { computed, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; @@ -10,12 +10,10 @@ import React = require("react"); import { Utils, emptyFunction } from "../../../../Utils"; import { DocumentType } from "../../../documents/DocumentTypes"; import { SnappingManager } from "../../../util/SnappingManager"; -import { Cast } from "../../../../fields/Types"; @observer export class CollectionFreeFormLinksView extends React.Component { - @computed - get uniqueConnections() { + @computed get uniqueConnections() { const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { if (!drawnPairs.reduce((found, drawnPair) => { const match1 = (connection.a === drawnPair.a && connection.b === drawnPair.b); @@ -37,7 +35,7 @@ export class CollectionFreeFormLinksView extends React.Component { } render() { - return SnappingManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container"> + return <div className="collectionfreeformlinksview-container"> <svg className="collectionfreeformlinksview-svgCanvas"> {this.uniqueConnections} </svg> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3d3599476..32c6fce36 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -208,7 +208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); if (!BoolCast(this.rootDoc.dontRegisterView, this.props.dontRegisterView)) { - DocumentManager.Instance.DocumentViews.push(this); + DocumentManager.Instance.AddView(this); } } @@ -234,8 +234,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._holdDisposer?.(); Doc.UnBrushDoc(this.props.Document); if (!this.props.dontRegisterView) { - const index = DocumentManager.Instance.DocumentViews.indexOf(this); - index !== -1 && DocumentManager.Instance.DocumentViews.splice(index, 1); + DocumentManager.Instance.RemoveView(this); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1e6477f43..26902914a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -557,11 +557,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, - () => (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")); + () => this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%")) !== "0%"); } sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this.CurrentDiv.getBoundingClientRect(); this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; + this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; return false; } @undoBatch @@ -1518,6 +1519,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } + return1000 = () => 1000; + @computed get sidebarCollection() { + return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : + <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} + PanelHeight={this.active() ? this.return1000 : this.props.PanelHeight} + PanelWidth={this.sidebarWidth} + scaleField={this.annotationKey + "-scale"} + annotationsKey={this.annotationKey} + isAnnotationOverlay={true} + focus={this.props.focus} + isSelected={this.props.isSelected} + select={emptyFunction} + active={this.annotationsActive} + ContentScaling={returnOne} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={this.addDocument} + CollectionView={undefined} + ScreenToLocalTransform={this.sidebarScreenToLocal} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} /> + </div>; + } + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0); @@ -1587,28 +1614,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }} /> </div> - {!this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : - <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} - PanelHeight={active ? () => 1000 : this.props.PanelHeight} - PanelWidth={this.sidebarWidth} - scaleField={this.annotationKey + "-scale"} - annotationsKey={this.annotationKey} - isAnnotationOverlay={true} - focus={this.props.focus} - isSelected={this.props.isSelected} - select={emptyFunction} - active={this.annotationsActive} - ContentScaling={returnOne} - whenActiveChanged={this.whenActiveChanged} - removeDocument={this.removeDocument} - moveDocument={this.moveDocument} - addDocument={this.addDocument} - CollectionView={undefined} - ScreenToLocalTransform={this.sidebarScreenToLocal} - renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} /> - </div>} + {this.sidebarCollection} {selected ? <div className="formattedTextBox-sidebar-handle" style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 5px))` }} onPointerDown={this.sidebarDown} /> : (null)} {!this.layoutDoc._showAudio ? (null) : <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index a6ac62ee4..b381bbfa9 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -183,7 +183,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc let newarray: Doc[] = []; while (docs.length > 0) { newarray = []; - docs.forEach(d => { + docs.filter(d => d).forEach(d => { const fieldKey = Doc.LayoutFieldKey(d); const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); const data = d[annos ? fieldKey + "-annotations" : fieldKey]; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 470f9d4be..205831153 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -103,6 +103,7 @@ export const DataSym = Symbol("Data"); export const LayoutSym = Symbol("Layout"); export const FieldsSym = Symbol("Fields"); export const AclSym = Symbol("Acl"); +export const AclUnset = Symbol("AclUnset"); export const AclPrivate = Symbol("AclOwnerOnly"); export const AclReadonly = Symbol("AclReadOnly"); export const AclAddonly = Symbol("AclAddonly"); @@ -112,6 +113,7 @@ export const UpdatingFromServer = Symbol("UpdatingFromServer"); export const CachedUpdates = Symbol("Cached updates"); const AclMap = new Map<string, symbol>([ + ["unset", AclUnset], [SharingPermissions.None, AclPrivate], [SharingPermissions.View, AclReadonly], [SharingPermissions.Add, AclAddonly], @@ -332,7 +334,7 @@ export namespace Doc { export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult { try { return getField(doc[Self], key, ignoreProto); - } catch { + } catch { return doc; } } diff --git a/src/fields/util.ts b/src/fields/util.ts index fe3eea69d..fa1c47055 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -167,11 +167,17 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; if (currentUserGroups.includes("Admin")) return AclAdmin; + if (target[AclSym] && Object.keys(target[AclSym]).length) { // if the acl is being overriden or the property being modified is one of the playground fields (which can be freely modified) if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; + // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned. + // if it's unset, it just goes ahead + const override = target[AclSym]["acl-Override"]; + if (override !== AclUnset && override !== undefined) return target[AclSym]["acl-Override"]; + let effectiveAcl = AclPrivate; const HierarchyMapping = new Map<symbol, number>([ [AclPrivate, 0], @@ -223,14 +229,17 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const dataDoc = target[DataSym]; // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive - if (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) { + if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!)) { target[key] = acl; layoutDocChanged = true; } if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) { - dataDoc[key] = acl; - dataDocChanged = true; + + if (GetEffectiveAcl(dataDoc) === AclAdmin) { + dataDoc[key] = acl; + dataDocChanged = true; + } // maps over the aliases of the document const links = DocListCast(dataDoc.links); @@ -238,22 +247,22 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc // maps over the children of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => { - if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - } + // if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) { + distributeAcls(key, acl, d, inheritingFromCollection, visited); + // } const data = d[DataSym]; - if (data && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) { + if (data) {// && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) { distributeAcls(key, acl, data, inheritingFromCollection, visited); } }); // maps over the annotations of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => { - if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) { - distributeAcls(key, acl, d, inheritingFromCollection, visited); - } + // if (GetEffectiveAcl(d) === AclAdmin && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) { + distributeAcls(key, acl, d, inheritingFromCollection, visited); + // } const data = d[DataSym]; - if (data && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) { + if (data) {// && GetEffectiveAcl(data) === AclAdmin && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) { distributeAcls(key, acl, data, inheritingFromCollection, visited); } }); @@ -271,7 +280,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't - if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; + if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "unset"].includes(value))) return true; // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { |