diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/GroupManager.tsx | 2 | ||||
-rw-r--r-- | src/client/util/SharingManager.tsx | 71 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 57 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 12 |
6 files changed, 113 insertions, 39 deletions
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 6458de0ed..a9059ff3d 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; +import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import Select from 'react-select'; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 2aea73528..8c36a7f3c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -78,7 +78,16 @@ 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; + @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not + + // maps acl symbols to SharingPermissions + private AclMap = new Map<symbol, string>([ + [AclPrivate, SharingPermissions.None], + [AclReadonly, SharingPermissions.View], + [AclAddonly, SharingPermissions.Add], + [AclEdit, SharingPermissions.Edit], + [AclAdmin, SharingPermissions.Admin] + ]); // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -156,6 +165,33 @@ export class SharingManager extends React.Component<{}> { } /** + * Shares the document with a user. + */ + setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { + const { user, sharingDoc } = recipient; + const target = targetDoc || this.targetDoc!; + const acl = `acl-${normalizeEmail(user.email)}`; + const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; + + const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); + docs.forEach(doc => { + doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1; + } + else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1; + } + + distributeAcls(acl, permission as SharingPermissions, doc); + + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); + }); + } + + /** * Sets the permission on the target for the group. * @param group * @param permission @@ -170,6 +206,14 @@ export class SharingManager extends React.Component<{}> { docs.forEach(doc => { doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc); + + if (permission === SharingPermissions.None) { + if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1; + } + else { + if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1; + } + distributeAcls(acl, permission as SharingPermissions, doc); if (group instanceof Doc) { @@ -397,20 +441,12 @@ export class SharingManager extends React.Component<{}> { } distributeOverCollection = (targetDoc?: Doc) => { - const AclMap = new Map<symbol, string>([ - [AclPrivate, SharingPermissions.None], - [AclReadonly, SharingPermissions.View], - [AclAddonly, SharingPermissions.Add], - [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] - ]); - const target = targetDoc || this.targetDoc!; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); docs.forEach(doc => { for (const [key, value] of Object.entries(doc[AclSym])) { - distributeAcls(key, AclMap.get(value)! as SharingPermissions, target); + distributeAcls(key, this.AclMap.get(value)! as SharingPermissions, target); } }); } @@ -471,7 +507,8 @@ export class SharingManager extends React.Component<{}> { const targetDoc = docs[0]; // tslint:disable-next-line: no-unnecessary-callback-wrapper - 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 + const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); + const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin); // 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]))); @@ -535,7 +572,7 @@ export class SharingManager extends React.Component<{}> { <span className={"padding"}>Me</span> <div className="edit-actions"> <div className={"permissions-dropdown"}> - {targetDoc?.[`acl-${Doc.CurrentUserEmailNormalized}`]} + {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"} </div> </div> </div> @@ -545,7 +582,7 @@ export class SharingManager extends React.Component<{}> { // the list of groups shared with const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true); - groupListMap.unshift({ title: "Public" }, { title: "Override" }); + groupListMap.unshift({ title: "Public" });//, { title: "Override" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.title)}`; const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); @@ -623,16 +660,16 @@ export class SharingManager extends React.Component<{}> { </div> <div className="acl-container"> - <div className="myDocs-acls"> + {/* <div className="myDocs-acls"> <input type="checkbox" onChange={action(() => this.myDocAcls = !this.myDocAcls)} checked={this.myDocAcls} /> <label>My Docs</label> - </div> + </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>} - <button className="distribute-button" onClick={() => this.distributeOverCollection()}> + {/* <button className="distribute-button" onClick={() => this.distributeOverCollection()}> Distribute - </button> + </button> */} </div> </div> } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 4aeb4e63a..6e33b5c2f 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -413,7 +413,7 @@ 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"]) || "None") : "-multiple-")); + // 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"]) || "None") : "-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-", !ownerSame)); if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); @@ -897,11 +897,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { /> <div className="propertiesView-acls-checkbox-text">Layout</div> </div>) : (null)} - <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> + {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}> <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}> <FontAwesomeIcon icon="redo-alt" color="white" size="1x" /> </button> - </Tooltip> + </Tooltip> */} </div> {this.sharingTable} </div>} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 344a6c103..18921e9e0 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -215,12 +215,12 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll const background = this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); const paddingX = `${NumCast(this.doc._xPadding, 10)}px`; const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`; - const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined; + // const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined; return !this.treeChildren ? (null) : ( <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> <div className="collectionTreeView-dropTarget" - style={{ background, paddingLeft: paddingX, paddingRight: paddingX, paddingTop, pointerEvents }} + style={{ background, paddingLeft: paddingX, paddingRight: paddingX, paddingTop }}//, pointerEvents }} onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 6f041e5ef..e01a21530 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -3,6 +3,7 @@ .documentView-effectsWrapper { border-radius: inherit; } + .documentView-node, .documentView-node-topmost { position: inherit; @@ -37,14 +38,16 @@ overflow-y: scroll; height: calc(100% - 20px); } + .documentView-linkAnchorBoxAnchor { - display:flex; + display: flex; overflow: hidden; .documentView-node { - width:10px !important; + width: 10px !important; } } + .documentView-treeView { max-height: 1.5em; text-overflow: ellipsis; @@ -52,7 +55,8 @@ white-space: pre; width: 100%; overflow: hidden; - > .documentView-node { + + >.documentView-node { position: absolute; } } @@ -61,14 +65,33 @@ border-radius: inherit; width: 100%; height: 100%; + + .sharingIndicator { + height: 30px; + width: 30px; + border-radius: 50%; + position: absolute; + right: -15; + opacity: 0.9; + pointer-events: auto; + background-color: #9dca96; + letter-spacing: 2px; + font-size: 10px; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + } } .documentView-anchorCont { position: absolute; - top: 0; - left: 0; + top: 0; + left: 0; width: 100%; - height: 100%; + height: 100%; display: inline-block; pointer-events: none; } @@ -81,6 +104,7 @@ top: 0; left: 0; } + .documentView-styleWrapper { position: absolute; display: inline-block; @@ -94,7 +118,8 @@ position: absolute; } - .documentView-titleWrapper, .documentView-titleWrapper-hover { + .documentView-titleWrapper, + .documentView-titleWrapper-hover { overflow: hidden; color: white; transform-origin: top left; @@ -107,8 +132,9 @@ white-space: pre; position: absolute; } + .documentView-titleWrapper-hover { - display:none; + display: none; } .documentView-searchHighlight { @@ -131,18 +157,21 @@ } -.documentView-node:hover, .documentView-node-topmost:hover { - > .documentView-styleWrapper { - > .documentView-titleWrapper-hover { - display:inline-block; +.documentView-node:hover, +.documentView-node-topmost:hover { + >.documentView-styleWrapper { + >.documentView-titleWrapper-hover { + display: inline-block; } } - > .documentView-styleWrapper { - > .documentView-captionWrapper { + + >.documentView-styleWrapper { + >.documentView-captionWrapper { opacity: 1; } } } + .contentFittingDocumentView { position: relative; display: flex; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e99b0a155..e40b23ea5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -7,7 +7,7 @@ import { InkTool } from '../../../fields/InkField'; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; -import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; +import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { emptyFunction, hasDescendantTarget, OmitKeys, returnFalse, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; @@ -40,6 +40,8 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +import { List } from '../../../fields/List'; +import { Tooltip } from '@material-ui/core'; export type DocAfterFocusFunc = (notFocused: boolean) => boolean; export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void; @@ -542,7 +544,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (this.props.Document === CurrentUserUtils.ActiveDashboard) { alert((e.target as any)?.closest?.("*.lm_content") ? "You can't perform this move most likely because you don't have permission to modify the destination." : - "linking to document tabs not yet supported. Drop link on document content."); + "Linking to document tabs not yet supported. Drop link on document content."); return; } const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined; @@ -715,6 +717,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps </div>; } + get indicatorIcon() { + if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas"; + else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users"; + else return "user"; + } + // used to decide whether a link anchor view should be created or not. // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. // would be good to generalize this some way. |