import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; import { Doc, Opt, DocListCastAsync } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; import { Utils } from "../../Utils"; import "./SharingManager.scss"; import { observer } from "mobx-react"; import * as fa from '@fortawesome/free-solid-svg-icons'; import { DocumentView } from "../views/nodes/DocumentView"; import { SelectionManager } from "./SelectionManager"; import { DocumentManager } from "./DocumentManager"; import { CollectionView } from "../views/collections/CollectionView"; import { DictationOverlay } from "../views/DictationOverlay"; import GroupManager, { UserOptions } from "./GroupManager"; import GroupMemberView from "./GroupMemberView"; import Select from "react-select"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { List } from "../../fields/List"; import { distributeAcls, SharingPermissions } from "../../fields/util"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; export interface User { email: string; userDocumentId: string; } interface GroupOptions { label: string; options: UserOptions[]; } // const SharingKey = "sharingPermissions"; // const PublicKey = "publicLinkPermissions"; // const DefaultColor = "black"; const groupType = "!groupType/"; const indType = "!indType/"; interface ValidatedUser { user: User; notificationDoc: Doc; } const storage = "data"; @observer export default class SharingManager extends React.Component<{}> { public static Instance: SharingManager; @observable private isOpen = false; @observable private users: ValidatedUser[] = []; @observable private targetDoc: Doc | undefined; @observable private targetDocView: DocumentView | undefined; // @observable private copied = false; @observable private dialogueBoxOpacity = 1; @observable private overlayOpacity = 0.4; @observable private selectedUsers: UserOptions[] | null = null; @observable private permissions: SharingPermissions = SharingPermissions.Edit; @observable private individualSort: "ascending" | "descending" | "none" = "none"; @observable private groupSort: "ascending" | "descending" | "none" = "none"; private shareDocumentButtonRef: React.RefObject = React.createRef(); // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; // } public open = (target: DocumentView) => { // SelectionManager.DeselectAll(); this.populateUsers().then(action(() => { this.targetDocView = target; this.targetDoc = target.props.Document; DictationOverlay.Instance.hasActiveModal = true; this.isOpen = true; this.permissions = SharingPermissions.Edit; })); } public close = action(() => { this.isOpen = false; // this.users = []; this.selectedUsers = null; setTimeout(action(() => { // this.copied = false; DictationOverlay.Instance.hasActiveModal = false; this.targetDoc = undefined; }), 500); }); constructor(props: {}) { super(props); SharingManager.Instance = this; } componentDidMount() { this.populateUsers(); } populateUsers = async () => { runInAction(() => this.users = []); const userList = await RequestPromise.get(Utils.prepend("/getUsers")); const raw = JSON.parse(userList) as User[]; const evaluating = raw.map(async user => { const isCandidate = user.email !== Doc.CurrentUserEmail; if (isCandidate) { const userDocument = await DocServer.GetRefField(user.userDocumentId); if (userDocument instanceof Doc) { const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc); runInAction(() => { if (notificationDoc instanceof Doc) { this.users.push({ user, notificationDoc }); } }); } } }); return Promise.all(evaluating); } setInternalGroupSharing = (group: Doc, permission: string, targetDoc?: Doc) => { const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); const target = targetDoc || this.targetDoc!; const ACL = `ACL-${StrCast(group.groupName)}`; // fix this - not needed (here and setinternalsharing and removegroup) // target[ACL] = permission; // Doc.GetProto(target)[ACL] = permission; distributeAcls(ACL, permission as SharingPermissions, target); group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List).push(target)) : group.docsShared = new List([target]); users.forEach(({ notificationDoc }) => { DocListCastAsync(notificationDoc[storage]).then(resolved => { if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); }); }); } shareWithAddedMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { DocListCastAsync(group.docsShared).then(docsShared => { docsShared?.forEach(doc => { DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); }); }); } } removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { DocListCastAsync(group.docsShared).then(docsShared => { docsShared?.forEach(doc => { DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); }); }); } } removeGroup = (group: Doc) => { if (group.docsShared) { DocListCastAsync(group.docsShared).then(resolved => { resolved?.forEach(doc => { const ACL = `ACL-${StrCast(group.groupName)}`; // doc[ACL] = doc[DataSym][ACL] = "Not Shared"; distributeAcls(ACL, SharingPermissions.None, doc); const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc)); }); }); } } shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => { const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); if (user) this.setInternalSharing(user, permission, target); else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, target); } // @action setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { const { user, notificationDoc } = recipient; const target = targetDoc || this.targetDoc!; const key = user.email.replace('.', '_'); const ACL = `ACL-${key}`; distributeAcls(ACL, permission as SharingPermissions, target); if (permission !== SharingPermissions.None) { DocListCastAsync(notificationDoc[storage]).then(resolved => { Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); }); } else { DocListCastAsync(notificationDoc[storage]).then(resolved => { Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); }); } } // private setExternalSharing = (permission: string) => { // const sharingDoc = this.sharingDoc; // if (!sharingDoc) { // return; // } // sharingDoc[PublicKey] = permission; // } // private get sharingUrl() { // if (!this.targetDoc) { // return undefined; // } // const baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]); // return `${baseUrl}?sharing=true`; // } // copy = action(() => { // if (this.sharingUrl) { // Utils.CopyText(this.sharingUrl); // this.copied = true; // } // }); private get sharingOptions() { return Object.values(SharingPermissions).map(permission => { return ( ); }); } private focusOn = (contents: string) => { const title = this.targetDoc ? StrCast(this.targetDoc.title) : ""; return ( { let context: Opt; if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) { DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document); } }} onPointerEnter={action(() => { if (this.targetDoc) { Doc.BrushDoc(this.targetDoc); this.dialogueBoxOpacity = 0.1; this.overlayOpacity = 0.1; } })} onPointerLeave={action(() => { this.targetDoc && Doc.UnBrushDoc(this.targetDoc); this.dialogueBoxOpacity = 1; this.overlayOpacity = 0.4; })} > {contents} ); } @action handleUsersChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; } @action handlePermissionsChange = (event: React.ChangeEvent) => { this.permissions = event.currentTarget.value as SharingPermissions; } @action share = () => { if (this.selectedUsers) { this.selectedUsers.forEach(user => { if (user.value.includes(indType)) { this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions); } else { this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions); } }); const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 1.5 * width; TaskCompletionBox.popupY = top - height; TaskCompletionBox.textDisplayed = "Document shared!"; TaskCompletionBox.taskCompleted = true; setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); this.selectedUsers = null; } } sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => { const { email: e1 } = u1.user; const { email: e2 } = u2.user; return e1 < e2 ? -1 : e1 === e2 ? 0 : 1; } sortGroups = (group1: Doc, group2: Doc) => { const g1 = StrCast(group1.groupName); const g2 = StrCast(group2.groupName); return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; } private get sharingInterface() { const groupList = GroupManager.Instance?.getAllGroups() || []; const sortedUsers = this.users.sort(this.sortUsers) .map(({ user: { email } }) => ({ label: email, value: indType + email })); const sortedGroups = groupList.sort(this.sortGroups) .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) })); const options: GroupOptions[] = GroupManager.Instance ? [ { label: 'Individuals', options: sortedUsers }, { label: 'Groups', options: sortedGroups } ] : []; const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users; const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList; const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { const userKey = user.email.replace('.', '_'); const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None); return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : (
{user.email}
); }); userListContents.unshift( (
{this.targetDoc?.author}
Owner
) ); const groupListContents = groups.map(group => { const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None); return permissions === SharingPermissions.None ? null : (
{group.groupName}
GroupManager.Instance.currentGroup = group)}>
); }); const displayUserList = !userListContents?.every(user => user === null); const displayGroupList = !groupListContents?.every(group => group === null); return (
{GroupManager.Instance?.currentGroup ? GroupManager.Instance.currentGroup = undefined)} /> : null} {/*

Manage the public link to {this.focusOn("this document...")}

{!this.linkVisible ? (null) :
{this.sharingUrl}
}
{!this.linkVisible ? (null) :

People with this link

}
*/}

Share {this.focusOn(StrCast(this.targetDoc?.title, "this document"))}

{this.targetDoc?.author !== Doc.CurrentUserEmail ? null :
{this.sharingOptions}
}
this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */}
{/*200*/} { !displayUserList ?
There are no users this document has been shared with.
: userListContents }
this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */}
{/*200*/} { !displayGroupList ?
There are no groups this document has been shared with.
: groupListContents }
); } render() { return ( ); } }