diff options
Diffstat (limited to 'src')
26 files changed, 848 insertions, 696 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7719b3eef..61a44a39d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -565,6 +565,7 @@ export namespace Docs { // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. dataDoc[fieldKey + "-annotations"] = new List<Doc>(); + dataDoc.aliases = new List<Doc>(); proto.links = ComputedField.MakeFunction("links(self)"); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 61edf8fe4..37ffcb78e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -836,7 +836,7 @@ export class CurrentUserUtils { // Right sidebar is where mobile uploads are contained static setupSharingSidebar(doc: Doc) { if (doc["sidebar-sharing"] === undefined) { - doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Sharing Sidebar" })); + doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias" })); } } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index d18eac374..523dbfca0 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -127,10 +127,6 @@ export class DocumentManager { CollectionDockingView.AddRightSplit(doc); finished?.(); } - // static openInPlace = (doc: Doc, finished?: () => void) => { - // CollectionDockingView.AddTab(doc); - // finished?.(); - // } public jumpToDocument = async ( targetDoc: Doc, // document to display willZoom: boolean, // whether to zoom doc to take up most of screen @@ -180,7 +176,7 @@ export class DocumentManager { const targetDocContextView = getFirstDocView(targetDocContext); targetDocContext._scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. - targetDocContext._viewTransition = "transform 10000ms"; + targetDocContext._viewTransition = "transform 500ms"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); // now find the target document within the context diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss index 51e4fa9e2..9438bdd72 100644 --- a/src/client/util/GroupManager.scss +++ b/src/client/util/GroupManager.scss @@ -92,6 +92,7 @@ .sort-groups { text-align: left; margin-left: 5; + width: 50px; cursor: pointer; } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 229a846a9..277e96a89 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -18,7 +18,7 @@ import { setGroups } from "../../fields/util"; import { DocServer } from "../DocServer"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle); +library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown); /** * Interface for options for the react-select component @@ -42,6 +42,7 @@ export default class GroupManager extends React.Component<{}> { private currentUserGroups: string[] = []; // the list of groups the current user is a member of @observable private buttonColour: "#979797" | "black" = "#979797"; @observable private groupSort: "ascending" | "descending" | "none" = "none"; + private populating: boolean = false; @@ -62,21 +63,24 @@ export default class GroupManager extends React.Component<{}> { * Fetches the list of users stored on the database. */ 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 userDocument = await DocServer.GetRefField(user.userDocumentId); - if (userDocument instanceof Doc) { - const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc); - runInAction(() => { - if (notificationDoc instanceof Doc) { - this.users.push(user.email); - } - }); - } - }); - return Promise.all(evaluating); + if (!this.populating) { + this.populating = true; + 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 userDocument = await DocServer.GetRefField(user.userDocumentId); + if (userDocument instanceof Doc) { + const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc); + runInAction(() => { + if (notificationDoc instanceof Doc) { + this.users.push(user.email); + } + }); + } + }); + return Promise.all(evaluating).then(() => this.populating = false); + } } /** @@ -88,7 +92,6 @@ export default class GroupManager extends React.Component<{}> { const members: string[] = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); }); - setGroups(this.currentUserGroups); }); } @@ -120,6 +123,7 @@ export default class GroupManager extends React.Component<{}> { this.currentGroup = undefined; // this.users = []; this.createGroupModalOpen = false; + TaskCompletionBox.taskCompleted = false; } /** @@ -132,7 +136,6 @@ export default class GroupManager extends React.Component<{}> { /** * @returns a list of all group documents. */ - // private ? getAllGroups(): Doc[] { const groupDoc = this.GroupManagerDoc; return groupDoc ? DocListCast(groupDoc.data) : []; @@ -142,7 +145,6 @@ export default class GroupManager extends React.Component<{}> { * @returns a group document based on the group name. * @param groupName */ - // private? getGroup(groupName: string): Doc | undefined { const groupDoc = this.getAllGroups().find(group => group.groupName === groupName); return groupDoc; @@ -209,8 +211,6 @@ export default class GroupManager extends React.Component<{}> { deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - // TODO look at this later - // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared"); Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); SharingManager.Instance.removeGroup(group); const members: string[] = JSON.parse(StrCast(group.members)); @@ -311,7 +311,9 @@ export default class GroupManager extends React.Component<{}> { <div className="group-create"> <div className="group-heading" style={{ marginBottom: 0 }}> <p><b>New Group</b></p> - <div className={"close-button"} onClick={action(() => this.createGroupModalOpen = false)}> + <div className={"close-button"} onClick={action(() => { + this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; + })}> <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> </div> </div> @@ -319,6 +321,7 @@ export default class GroupManager extends React.Component<{}> { className="group-input" ref={this.inputRef} onKeyDown={this.handleKeyDown} + autoFocus type="text" placeholder="Group name" onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} /> @@ -363,7 +366,7 @@ export default class GroupManager extends React.Component<{}> { interactive={true} contents={contents} dialogueBoxStyle={{ width: "90%", height: "70%" }} - closeOnExternalClick={action(() => this.createGroupModalOpen = false)} + closeOnExternalClick={action(() => { this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; })} /> ); } @@ -405,7 +408,10 @@ export default class GroupManager extends React.Component<{}> { <div className="sort-groups" onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Name {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} + Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> + : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> + : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} /> + } </div> <div className="group-body"> {groups.map(group => diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 8da80ef52..7912db74d 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -84,6 +84,7 @@ .user-sort { text-align: left; margin-left: 10; + width: 100px; cursor: pointer; } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 9d91ce1ba..d50a132f8 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,7 +1,7 @@ import { observable, runInAction, action } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, DocListCastAsync, AclAdmin, DataSym, AclPrivate } from "../../fields/Doc"; +import { Doc, Opt, AclAdmin, AclPrivate, DocListCast } from "../../fields/Doc"; import { DocServer } from "../DocServer"; import { Cast, StrCast } from "../../fields/Types"; import * as RequestPromise from "request-promise"; @@ -21,6 +21,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { List } from "../../fields/List"; import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; +import { library } from "@fortawesome/fontawesome-svg-core"; + +library.add(fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown); + export interface User { email: string; @@ -71,6 +75,7 @@ export default class SharingManager extends React.Component<{}> { // if both showUserOptions and showGroupOptions are false then both are displayed @observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component) @observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component) + private populating: boolean = false; // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -87,13 +92,13 @@ export default class SharingManager extends React.Component<{}> { this.isOpen = true; this.permissions = SharingPermissions.Edit; }); - + this.targetDoc!.author === Doc.CurrentUserEmail && !this.targetDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, this.targetDoc!); } public close = action(() => { this.isOpen = false; this.selectedUsers = null; // resets the list of users and seleected users (in the react-select component) - + TaskCompletionBox.taskCompleted = false; setTimeout(action(() => { // this.copied = false; DictationOverlay.Instance.hasActiveModal = false; @@ -117,24 +122,27 @@ export default class SharingManager extends React.Component<{}> { * Populates the list of validated users (this.users) by adding registered users which have a sidebar-sharing. */ 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["sidebar-sharing"], Doc); - runInAction(() => { - if (notificationDoc instanceof Doc) { - this.users.push({ user, notificationDoc }); - } - }); + if (!this.populating) { + this.populating = true; + 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["sidebar-sharing"], Doc); + runInAction(() => { + if (notificationDoc instanceof Doc) { + this.users.push({ user, notificationDoc }); + } + }); + } } - } - }); - return Promise.all(evaluating); + }); + return Promise.all(evaluating).then(() => this.populating = false); + } } /** @@ -152,13 +160,11 @@ export default class SharingManager extends React.Component<{}> { target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target); // if documents have been shared, add the target to that list if it doesn't already exist, otherwise create a new list with the target - group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]); + group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]); users.forEach(({ notificationDoc }) => { - DocListCastAsync(notificationDoc[storage]).then(resolved => { - if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); // add the target to the notificationDoc if it hasn't already been added - else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); // remove the target from the list if it already exists - }); + if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); // add the target to the notificationDoc if it hasn't already been added + else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); // remove the target from the list if it already exists }); } @@ -170,13 +176,7 @@ export default class SharingManager extends React.Component<{}> { 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)); // add the doc if it isn't already in the list - }); - }); - } + if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => { @@ -194,10 +194,8 @@ export default class SharingManager extends React.Component<{}> { 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)); // remove the doc only if it is in the list - }); + DocListCast(group.docsShared).forEach(doc => { + Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc); // remove the doc only if it is in the list }); } } @@ -208,39 +206,36 @@ export default class SharingManager extends React.Component<{}> { */ removeGroup = (group: Doc) => { if (group.docsShared) { - DocListCastAsync(group.docsShared).then(resolved => { - resolved?.forEach(doc => { - const ACL = `ACL-${StrCast(group.groupName)}`; + DocListCast(group.docsShared).forEach(doc => { + const ACL = `ACL-${StrCast(group.groupName)}`; - distributeAcls(ACL, SharingPermissions.None, doc); + 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)); - }); + 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)); }); } } + /** + * Shares the document with a user. + */ 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}`; + target.author === Doc.CurrentUserEmail && 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); - }); + Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); } else { - DocListCastAsync(notificationDoc[storage]).then(resolved => { - Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); - }); + Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); } } @@ -268,14 +263,17 @@ export default class SharingManager extends React.Component<{}> { // } // }); + /** + * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share + */ private get sharingOptions() { - return Object.values(SharingPermissions).map(permission => { - return ( - <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}> + return Object.values(SharingPermissions).map(permission => + ( + <option key={permission} value={permission}> {permission} </option> - ); - }); + ) + ); } private focusOn = (contents: string) => { @@ -308,16 +306,25 @@ export default class SharingManager extends React.Component<{}> { ); } + /** + * Handles changes in the users selected in react-select + */ @action handleUsersChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; } + /** + * Handles changes in the permission chosen to share with someone with + */ @action handlePermissionsChange = (event: React.ChangeEvent<HTMLSelectElement>) => { this.permissions = event.currentTarget.value as SharingPermissions; } + /** + * Calls the relevant method for sharing, displays the popup, and resets the relevant variables. + */ @action share = () => { if (this.selectedUsers) { @@ -341,18 +348,27 @@ export default class SharingManager extends React.Component<{}> { } } + /** + * Sorting algorithm to sort users. + */ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => { const { email: e1 } = u1.user; const { email: e2 } = u2.user; return e1 < e2 ? -1 : e1 === e2 ? 0 : 1; } + /** + * Sorting algorithm to sort groups. + */ sortGroups = (group1: Doc, group2: Doc) => { const g1 = StrCast(group1.groupName); const g2 = StrCast(group2.groupName); return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; } + /** + * @returns the main interface of the SharingManager. + */ private get sharingInterface() { const groupList = GroupManager.Instance?.getAllGroups() || []; @@ -361,8 +377,8 @@ export default class SharingManager extends React.Component<{}> { const sortedGroups = groupList.slice().sort(this.sortGroups) .map(({ groupName }) => ({ label: StrCast(groupName), value: groupType + StrCast(groupName) })); + // the next block handles the users shown (individuals/groups/both) const options: GroupedOptions[] = []; - if (GroupManager.Instance) { if ((this.showUserOptions && this.showGroupOptions) || (!this.showUserOptions && !this.showGroupOptions)) { options.push({ @@ -393,6 +409,7 @@ export default class SharingManager extends React.Component<{}> { const effectiveAcl = this.targetDoc ? GetEffectiveAcl(this.targetDoc) : AclPrivate; + // the list of users shared with const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { const userKey = user.email.replace('.', '_'); const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`]); @@ -422,6 +439,7 @@ export default class SharingManager extends React.Component<{}> { ); }); + // the owner of the doc and the current user are placed at the top of the user list. userListContents.unshift( ( <div @@ -452,6 +470,7 @@ export default class SharingManager extends React.Component<{}> { ) : null ); + // the list of groups shared with const groupListContents = groups.map(group => { const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`]); @@ -477,6 +496,7 @@ export default class SharingManager extends React.Component<{}> { ); }); + // don't display the group list if all groups are null const displayGroupList = !groupListContents?.every(group => group === null); return ( @@ -538,7 +558,7 @@ export default class SharingManager extends React.Component<{}> { }) }} /> - <select className="permissions-select" onChange={this.handlePermissionsChange}> + <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> {this.sharingOptions} </select> <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> @@ -556,7 +576,9 @@ export default class SharingManager extends React.Component<{}> { <div className="user-sort" onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> - Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */} + Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> + : this.individualSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> + : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />} </div> <div className={"users-list"} style={{ display: "block" }}>{/*200*/} {userListContents} @@ -566,7 +588,9 @@ export default class SharingManager extends React.Component<{}> { <div className="user-sort" onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */} + Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> + : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> + : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />} </div> <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/} diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store Binary files differindex 3ce88292c..c379549d0 100644 --- a/src/client/views/.DS_Store +++ b/src/client/views/.DS_Store diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 804c7a8d4..831c246d1 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,4 +1,4 @@ -import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast } from '../../fields/Doc'; +import { Doc, Opt, DataSym, AclReadonly, AclAddonly, AclPrivate, AclEdit, AclSym, DocListCastAsync, DocListCast, AclAdmin } from '../../fields/Doc'; import { Touchable } from './Touchable'; import { computed, action, observable } from 'mobx'; import { Cast, BoolCast, ScriptCast } from '../../fields/Types'; @@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { List } from '../../fields/List'; import { DateField } from '../../fields/DateField'; import { ScriptField } from '../../fields/ScriptField'; -import { GetEffectiveAcl, SharingPermissions } from '../../fields/util'; +import { GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../fields/util'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -96,7 +96,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T [AclPrivate, SharingPermissions.None], [AclReadonly, SharingPermissions.View], [AclAddonly, SharingPermissions.Add], - [AclEdit, SharingPermissions.Edit] + [AclEdit, SharingPermissions.Edit], + [AclAdmin, SharingPermissions.Admin] ]); lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result; @@ -154,15 +155,14 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T return false; } else { - // if (this.props.Document[AclSym]) { - // added.forEach(d => { - // const dataDoc = d[DataSym]; - // dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym]; - // for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - // dataDoc[key] = d[key] = this.AclMap.get(value); - // } - // }); - // } + 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 (effectiveAcl === AclAddonly) { added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc)); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 005df6fe0..f1169763e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -611,7 +611,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 2 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } - const canDelete = SelectionManager.SelectedDocuments().map(docView => GetEffectiveAcl(docView.props.ContainingCollectionDoc)).some(permission => permission === AclAdmin || permission === AclEdit); + const canDelete = SelectionManager.SelectedDocuments().some(docView => { + const docAcl = GetEffectiveAcl(docView.props.Document); + const collectionAcl = GetEffectiveAcl(docView.props.ContainingCollectionDoc); + return [docAcl, collectionAcl].some(acl => [AclAdmin, AclEdit].includes(acl)); + }); const minimal = bounds.r - bounds.x < 100 ? true : false; const maximizeIcon = minimal ? ( <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d03cb1bb2..b6058db7a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -175,10 +175,8 @@ export class MainView extends React.Component { fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper, fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, - fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft); - //Pres trail icons (just for the sake of merging) - library.add(fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, - fa.faRocket, fa.faExchangeAlt, faBuffer); + fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, + fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, faBuffer); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -439,7 +437,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(0, -this._buttonBarHeight); + mainContainerXf = () => this.sidebarScreenToLocal().translate(-55, 0); @computed get closePosition() { return 55 + this.flyoutWidth; } @computed get flyout() { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 29afd447f..5c584d270 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -221,8 +221,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-linker" style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }} onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> - <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" - /> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" /> </div> <div className="propertiesButtons-title" style={{ @@ -237,31 +236,34 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get pinWithViewButton() { const targetDoc = this.selectedDoc; - const isPinned = targetDoc && Doc.isDocPinned(targetDoc); if (targetDoc) { const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; } - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with this view"}</div></>}> - <div className="propertiesButtons-linker" - style={{ backgroundColor: "white", color: "black" }} - onClick={e => { - if (targetDoc) { - DockedFrameRenderer.PinDoc(targetDoc, false); - const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } - }}> - <div style={{ position: 'absolute', fontSize: 25, fontWeight: 700, transform: 'translate(42%, -7px)', color: 'rgba(0,0,0,0.2)' }}>V</div> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> - </div></Tooltip>; + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with this view"}</div></>} placement="top"> + <div> + <div className="propertiesButtons-linker" + onClick={e => { + if (targetDoc) { + DockedFrameRenderer.PinDoc(targetDoc, false); + const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } + }}> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" /> + <div style={{ position: 'relative', fontSize: 25, fontWeight: 700, transform: 'translate(0, -20px)', color: 'rgba(250,250,250,0.5)' }}>V</div> + </div> + + <div className="propertiesButtons-title">{"View"}</div> + </div> + </Tooltip>; } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 98babc3d2..4204ef5bb 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -54,6 +54,21 @@ transition: all 0.3s; } + .miniPres-button-frame { + justify-self: center; + align-self: center; + align-items: center; + display: grid; + grid-template-columns: auto auto auto; + justify-content: space-around; + font-size: 11; + margin-left: 7; + width: 30; + height: 85%; + background-color: rgba(91, 157, 221, 0.4); + border-radius: 5px; + } + .miniPres-divider { width: 0.5px; height: 80%; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6d4a7f311..7e096fa37 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -870,20 +870,24 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { return currentFrame; } renderMiniPres() { - return <div className="miniPres" style={{ - width: 250, height: 30, background: '#323232' - }}> - <div className="miniPresOverlay" > - <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => PresBox.Instance.startOrResetPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> - <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text">Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length}</div> - {/* <div className="miniPres-button-text">{this.getCurrentFrame}</div> */} - <div className="miniPres-divider"></div> - <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div> + return ( + <div className="miniPres" + style={{ width: 250, height: 30, background: '#323232' }} + > + {<div className="miniPresOverlay"> + <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="miniPres-button" onClick={() => PresBox.Instance.startAutoPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text"> + Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length} + {PresBox.Instance.playButtonFrames} + </div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div> + </div>} </div> - </div>; + ); } renderMiniMap() { return <div className="miniMap" style={{ diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6e15cb887..4d1cb670c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../../fields/util'; import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -148,16 +148,14 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return false; } else { - // if (this.props.Document[AclSym]) { - // // change so it only adds if more restrictive - // added.forEach(d => { - // // const dataDoc = d[DataSym]; - // for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - // // key.substring(4).replace("_", ".") !== Doc.CurrentUserEmail && distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); - // distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); - // } - // }); - // } + 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); + else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); + } + }); + } if (effectiveAcl === AclAddonly) { added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc)); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ffeb3024d..5b1f3c01c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1495,33 +1495,18 @@ interface CollectionFreeFormViewPannableContentsProps { @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ @computed get zoomProgressivize() { - if (this.props.zoomProgressivize) { - console.log("should render"); - return ( - <> - {PresBox.Instance.zoomProgressivizeContainer} - </> - ); - } + return PresBox.Instance && this.props.zoomProgressivize ? PresBox.Instance.zoomProgressivizeContainer : (null); } @computed get progressivize() { - if (this.props.progressivize) { - console.log("should render"); - return ( - <> - {PresBox.Instance.progressivizeChildDocs} - </> - ); - } + return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null); } @computed get presPaths() { const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden"); - if (this.props.presPaths) { - return ( - <> - <div>{PresBox.Instance.order}</div> + if (PresBox.Instance) return ( + <> + {!this.props.presPaths ? (null) : <><div>{PresBox.Instance.order}</div> <svg className={presPaths}> <defs> <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth"> @@ -1542,10 +1527,9 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF </marker> </defs>; {PresBox.Instance.paths} - </svg> - </> - ); - } + </svg></>} + </> + ); } render() { diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index 5b41db90e..5e0c9fcbb 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -119,6 +119,19 @@ font-size: 10px; padding: 10px; margin-left: 5px; + + .change-buttons { + display: flex; + + button { + width: 5; + height: 5; + } + + input { + width: 100%; + } + } } } @@ -233,11 +246,15 @@ .propertiesView-sharingTable { + // whatever's commented out - add it back in when adding the buttons + + // border: 1.5px solid black; border: 1px solid black; - padding: 5px; - border-radius: 6px; - /* width: 170px; */ - margin-right: 10px; + padding: 5px; // remove when adding buttons + border-radius: 6px; // remove when adding buttons + margin-right: 10px; // remove when adding buttons + // width: 100%; + // display: inline-table; background-color: #ececec; max-height: 130px; overflow-y: scroll; @@ -245,9 +262,11 @@ .propertiesView-sharingTable-item { display: flex; + // padding: 5px; padding: 3px; align-items: center; border-bottom: 0.5px solid grey; + cursor: pointer; &:hover .propertiesView-sharingTable-item-name { overflow-x: unset; @@ -405,6 +424,43 @@ } } + + .propertiesView-presTrails { + border-bottom: 1px solid black; + //padding: 8.5px; + + .propertiesView-presTrails-title { + font-weight: bold; + font-size: 12.5px; + padding: 4px; + display: flex; + color: white; + padding-left: 8px; + background-color: rgb(51, 51, 51); + + &:hover { + cursor: pointer; + } + + .propertiesView-presTrails-title-icon { + float: right; + right: 0; + position: absolute; + margin-left: 2px; + margin-right: 9px; + + &:hover { + cursor: pointer; + } + } + } + + .propertiesView-presTrails-content { + font-size: 10px; + padding: 10px; + margin-left: 5px; + } + } } .inking-button { diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index 89f48fc65..1a8ee3ea1 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -2,13 +2,11 @@ import React = require("react"); import { observer } from "mobx-react"; import "./PropertiesView.scss"; import { observable, action, computed, runInAction } from "mobx"; -import { Doc, Field, DocListCast, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt } from "../../../../fields/Doc"; -import { DocumentView } from "../../nodes/DocumentView"; +import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync } from "../../../../fields/Doc"; import { ComputedField } from "../../../../fields/ScriptField"; import { EditableView } from "../../EditableView"; import { KeyValueBox } from "../../nodes/KeyValueBox"; import { Cast, NumCast, StrCast } from "../../../../fields/Types"; -import { listSpec } from "../../../../fields/Schema"; import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView"; import { returnFalse, returnOne, emptyFunction, emptyPath, returnTrue, returnZero, returnEmptyFilter, Utils } from "../../../../Utils"; import { Id } from "../../../../fields/FieldSymbols"; @@ -16,23 +14,26 @@ import { Transform } from "../../../util/Transform"; import { PropertiesButtons } from "../../PropertiesButtons"; import { SelectionManager } from "../../../util/SelectionManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip, Checkbox, Divider } from "@material-ui/core"; +import { Tooltip, Checkbox } from "@material-ui/core"; import SharingManager from "../../../util/SharingManager"; import { DocumentType } from "../../../documents/DocumentTypes"; -import FormatShapePane from "./FormatShapePane"; import { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util"; import { InkField } from "../../../../fields/InkField"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { ColorState, SketchPicker } from "react-color"; -import AntimodeMenu from "../../AntimodeMenu"; import "./FormatShapePane.scss"; import { discovery_v1 } from "googleapis"; import { PresBox } from "../../nodes/PresBox"; import { DocumentManager } from "../../../util/DocumentManager"; +import FormatShapePane from "./FormatShapePane"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; +// import * as fa from '@fortawesome/free-solid-svg-icons'; +// import { library } from "@fortawesome/fontawesome-svg-core"; + +// library.add(fa.faPlus, fa.faMinus, fa.faCog); interface PropertiesViewProps { width: number; @@ -51,7 +52,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get selectedDocumentView() { if (SelectionManager.SelectedDocuments().length) { return SelectionManager.SelectedDocuments()[0]; - } else if (PresBox.Instance._selectedArray.length >= 1) { + } else if (PresBox.Instance._selectedArray.length) { return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); } else { return undefined; } } @@ -70,12 +71,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openLayout: boolean = true; @observable openAppearance: boolean = true; @observable openTransform: boolean = true; + // @observable selectedUser: string = ""; + // @observable addButtonPressed: boolean = false; + //Pres Trails booleans: - @observable openAddSlide: boolean = true; - @observable openPresentationTools: boolean = true; - @observable openPresTransitions: boolean = true; - @observable openPresProgressivize: boolean = true; - @observable openSlideOptions: boolean = true; + @observable openPresTransitions: boolean = false; + @observable openPresProgressivize: boolean = false; + @observable openAddSlide: boolean = false; + @observable openSlideOptions: boolean = false; @observable inActions: boolean = false; @observable _controlBtn: boolean = false; @@ -289,13 +292,20 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } } + /** + * Handles the changing of a user's permissions from the permissions panel. + */ @undoBatch changePermissions = (e: any, user: string) => { SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!); } - getPermissionsSelect(user: string) { + /** + * @returns the options for the permissions dropdown. + */ + getPermissionsSelect(user: string, permission: string) { return <select className="permissions-select" + defaultValue={permission} onChange={e => this.changePermissions(e, user)}> {Object.values(SharingPermissions).map(permission => { return ( @@ -306,6 +316,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </select>; } + /** + * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. + */ @computed get notifyIcon() { return <Tooltip title={<><div className="dash-tooltip">Notify with message</div></>}> <div className="notify-button"> @@ -314,6 +327,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </Tooltip>; } + /** + * ... next to the owner that opens the main SharingManager interface on click. + */ @computed get expansionIcon() { return <Tooltip title={<><div className="dash-tooltip">{"Show more permissions"}</div></>}> <div className="expansion-button" onPointerDown={() => { @@ -326,17 +342,26 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </Tooltip>; } - sharingItem(name: string, effectiveAcl: symbol, permission?: string) { - return <div className="propertiesView-sharingTable-item"> + /** + * @returns a row of the permissions panel + */ + sharingItem(name: string, effectiveAcl: symbol, permission: string) { + return <div className="propertiesView-sharingTable-item" + // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }} + // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)} + > <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div> {/* {name !== "Me" ? this.notifyIcon : null} */} <div className="propertiesView-sharingTable-item-permission"> - {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name) : permission} + {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} {permission === "Owner" ? this.expansionIcon : null} </div> </div>; } + /** + * @returns the sharing and permissiosn panel. + */ @computed get sharingTable() { const AclMap = new Map<symbol, string>([ [AclPrivate, SharingPermissions.None], @@ -349,13 +374,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { const effectiveAcl = GetEffectiveAcl(this.selectedDoc!); const tableEntries = []; + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { if (this.selectedDoc![AclSym]) { for (const [key, value] of Object.entries(this.selectedDoc![AclSym])) { const name = key.substring(4).replace("_", "."); - if (name !== Doc.CurrentUserEmail && name !== this.selectedDoc!.author) tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!)); + if (name !== Doc.CurrentUserEmail && name !== this.selectedDoc!.author/* && sidebarUsersDisplayed![name] !== false*/) tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!)); } } + // if (Doc.UserDoc().sidebarUsersDisplayed) { + // for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) { + // if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None)); + // } + // } + // }) + + // shifts the current user and the owner to the top of the doc. tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === this.selectedDoc!.author ? "Owner" : StrCast(this.selectedDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]))); if (Doc.CurrentUserEmail !== this.selectedDoc!.author) tableEntries.unshift(this.sharingItem(StrCast(this.selectedDoc!.author), effectiveAcl, "Owner")); @@ -754,8 +788,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div>; } - render() { + /** + * Handles adding and removing members from the sharing panel + */ + // handleUserChange = (selectedUser: string, add: boolean) => { + // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc; + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { + // sidebarUsersDisplayed![`display-${selectedUser}`] = add; + // !add && runInAction(() => this.selectedUser = ""); + // }); + // } + render() { if (!this.selectedDoc && !this.isPres) { return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> @@ -807,6 +851,36 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {!this.openSharing ? (null) : <div className="propertiesView-sharing-content"> {this.sharingTable} + {/* <div className="change-buttons"> + <button + onPointerDown={action(() => this.addButtonPressed = !this.addButtonPressed)} + > + <FontAwesomeIcon icon={fa.faPlus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /> + </button> + <button + id="sharingProperties-removeUser" + onPointerDown={() => this.handleUserChange(this.selectedUser, false)} + style={{ backgroundColor: this.selectedUser ? "#121721" : "#777777" }} + ><FontAwesomeIcon icon={fa.faMinus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button> + <button onClick={() => SharingManager.Instance.open(this.selectedDocumentView!)}><FontAwesomeIcon icon={fa.faCog} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button> + {this.addButtonPressed ? + // <input type="text" onKeyDown={this.handleKeyPress} /> : + <select onChange={e => this.handleUserChange(e.target.value, true)}> + <option selected disabled hidden> + Add users + </option> + {SharingManager.Instance.users.map(user => + (<option value={user.user.email}> + {user.user.email} + </option>) + )} + {GroupManager.Instance.getAllGroups().map(group => + (<option value={StrCast(group.groupName)}> + {StrCast(group.groupName)} + </option>))} + </select> : + null} + </div> */} </div>} </div> @@ -874,6 +948,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div>; } if (this.isPres) { + const selectedItem: boolean = PresBox.Instance._selectedArray.length > 0; return <div className="propertiesView" style={{ width: this.props.width }} > <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation @@ -884,63 +959,63 @@ 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> - <div className="propertiesView-settings"> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> - <FontAwesomeIcon icon={"plus"} /> Add new slide - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> - </div> - </div> - {this.openAddSlide ? <div className="propertiesView-settings-content"> - {PresBox.Instance.newDocumentDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" + {!selectedItem ? (null) : <div className="propertiesView-presTrails"> + <div className="propertiesView-presTrails-title" onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> <FontAwesomeIcon icon={"rocket"} /> Transitions - <div className="propertiesView-sharing-title-icon"> + <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> </div> - {this.openPresTransitions ? <div className="propertiesView-sharing-content"> + {this.openPresTransitions ? <div className="propertiesView-presTrails-content"> {PresBox.Instance.transitionDropdown} </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" + </div>} + {!selectedItem ? (null) : <div className="propertiesView-presTrails"> + <div className="propertiesView-presTrails-title" onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> <FontAwesomeIcon icon={"tasks"} /> Progressivize - <div className="propertiesView-sharing-title-icon"> + <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> </div> - {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> + {this.openPresProgressivize ? <div className="propertiesView-presTrails-content"> {PresBox.Instance.progressivizeDropdown} </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" + </div>} + {!selectedItem ? (null) : <div className="propertiesView-presTrails"> + <div className="propertiesView-presTrails-title" onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options - <div className="propertiesView-sharing-title-icon"> + <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> </div> - {this.openSlideOptions ? <div className="propertiesView-sharing-content"> + {this.openSlideOptions ? <div className="propertiesView-presTrails-content"> {PresBox.Instance.optionsDropdown} </div> : null} + </div>} + <div className="propertiesView-presTrails"> + <div className="propertiesView-presTrails-title" + onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> + <FontAwesomeIcon icon={"plus"} /> Add new slide + <div className="propertiesView-presTrails-title-icon"> + <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openAddSlide ? <div className="propertiesView-presTrails-content"> + {PresBox.Instance.newDocumentDropdown} + </div> : null} </div> <div className="propertiesView-sharing"> <div className="propertiesView-sharing-title" diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b9e685b44..c8d4aa603 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -179,7 +179,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); + const effectiveAcl = GetEffectiveAcl(this.props.Document); + (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 }); @@ -570,6 +571,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu alert("Can't delete the active workspace"); } else { SelectionManager.DeselectAll(); + this.props.Document.deleted = true; this.props.removeDocument?.(this.props.Document); } } @@ -762,7 +764,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" }); } - //GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); const effectiveAcl = GetEffectiveAcl(this.props.Document); (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 16821f5bc..a87b0e466 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -1,3 +1,7 @@ +$light-blue: #AEDDF8; +$dark-blue: #5B9FDD; +$light-background: #ececec; + .presBox-cont { position: absolute; display: block; @@ -20,17 +24,13 @@ margin-top: 3px; } - .presBox-toolbar { - display: none; - } - .presBox-toolbar-dropdown { border-radius: 5px; background-color: white; transform: translate(8px, -5px); box-shadow: 1px 1px 4px 4px rgba(0, 0, 0, 0.25); z-index: 1000; - width: 315; + width: calc(100% - 50px); height: max-content; justify-self: center; letter-spacing: normal; @@ -40,7 +40,7 @@ font-size: 13; } - .presBox-toolbar.active { + .presBox-toolbar { position: relative; display: inline-flex; align-items: center; @@ -59,7 +59,7 @@ } .toolbar-button.active { - color: #AEDDF8; + color: $light-blue; } .toolbar-transitionButtons { @@ -85,7 +85,6 @@ display: flex; width: max-content; height: 25px; - /* background-color: pink; */ justify-content: center; transform: rotate(90deg); align-items: center; @@ -120,7 +119,7 @@ .dropdown.active { transform: rotate(180deg); - color: #AEDDF8; + color: $light-blue; opacity: 0.8; } @@ -128,10 +127,7 @@ position: relative; display: inline; font-family: Roboto; - /* background-color: white; */ color: black; - width: 100%; - height: max-content; z-index: 100; transition: 0.7s; @@ -157,7 +153,6 @@ margin-left: 5px; margin-top: 5px; margin-bottom: 5px; - /* border-radius: 5px; */ margin-right: 5px; width: max-content; justify-content: center; @@ -166,52 +161,6 @@ padding-left: 10px; } - // .toolbar-slider { - // position: relative; - // align-self: center; - // justify-self: left; - // -webkit-appearance: none; - // background-color: #5b9ddd; - // overflow: hidden; - // margin-top: 5px; - // width: 100%; - // max-width: 200px; - - // } - - // .toolbar-slider:focus { - // outline: none; - // } - - // .effectDirection { - // justify-self: center; - // align-self: center; - // align-items: center; - // justify-content: center; - // grid-template-columns: 13px 13px 13px; - // display: grid; - // } - - // .toolbar-slider::-webkit-slider-thumb { - // -webkit-appearance: none; - // background-color: #5b9ddd; - // box-shadow: -100 vw 0 0 100vw pink; - // width: 12px; - // height: 12px; - // } - - // .toolbar-slider::-webkit-slider-runnable-track { - // position: relative; - // align-self: center; - // justify-self: left; - // -webkit-appearance: none; - // background-color: #5b9ddd; - // margin-top: 5px; - // border: solid 1px black; - // border-radius: 10px; - // height: 5px; - // left: 0px; - // } .presBox-subheading { font-size: 11; font-weight: 400; @@ -235,20 +184,20 @@ .toolbar-slider:focus { outline: none; } - + .toolbar-slider::-webkit-slider-runnable-track { - height: 10px; - -webkit-appearance: none; - margin-top: -1px; + height: 10px; + -webkit-appearance: none; + margin-top: -1px; } - + .toolbar-slider::-webkit-slider-thumb { - width: 10px; - -webkit-appearance: none; - height: 10px; - cursor: ew-resize; - background: #5b9ddd; - box-shadow: -100vw 0 0 100vw #aedef8; + width: 10px; + -webkit-appearance: none; + height: 10px; + cursor: ew-resize; + background: #5b9ddd; + box-shadow: -100vw 0 0 100vw #aedef8; } } @@ -298,7 +247,7 @@ justify-self: left; margin-top: 5px; padding-left: 10px; - background-color: rgb(222, 222, 222); + background-color: $light-background; border: solid 1px black; min-width: 80px; max-width: 200px; @@ -323,7 +272,7 @@ cursor: pointer; position: relative; height: 100%; - background: #d5dce2; + background: $light-background; display: flex; align-items: center; justify-content: center; @@ -417,7 +366,7 @@ font-size: 10.5; font-weight: 200; height: 20; - background-color: #dedede; + background-color: $light-background; border: solid 1px black; display: flex; margin-top: 5px; @@ -487,7 +436,7 @@ .presBox-dropdownOptions { position: absolute; - top: 29px; + top: 24px; left: -1px; z-index: 200; width: 85%; @@ -509,10 +458,10 @@ grid-template-columns: auto 20%; position: relative; border: solid 1px black; - background-color: #dedede; + background-color: $light-background; border-radius: 5px; font-size: 10; - height: 30; + height: 25; padding-left: 5px; align-items: center; margin-top: 5px; @@ -583,6 +532,8 @@ align-items: center; justify-content: center; border-radius: 4px; + padding-left: 7px; + padding-right: 7px; border-bottom-right-radius: 0; border-top-right-radius: 0; } @@ -597,6 +548,8 @@ align-items: center; justify-content: center; border-radius: 4px; + padding-left: 7px; + padding-right: 7px; border-bottom-left-radius: 0; border-top-left-radius: 0; } @@ -633,7 +586,7 @@ align-items: center; justify-content: center; transform: translate(0px, -1px); - background-color: #dedede; + background-color: $light-background; width: 40px; height: 15px; align-self: center; @@ -647,7 +600,7 @@ .layout-container { padding: 5px; display: grid; - background-color: #dedede; + background-color: $light-background; grid-template-columns: repeat(auto-fit, minmax(90px, 100px)); width: 100%; border: solid 1px black; @@ -722,6 +675,7 @@ position: relative; width: 100%; background: gray; + min-height: 35px; padding-top: 5px; padding-bottom: 5px; display: grid; @@ -746,7 +700,7 @@ min-width: 150px; } - + select { background: #323232; @@ -782,8 +736,7 @@ .presBox-button.present { display: flex; - min-width: 100px; - width: 100px; + width: max-content; position: absolute; right: 10px; @@ -792,19 +745,21 @@ } } - + .miniPresOverlay { background-color: #323232; color: white; border-radius: 5px; grid-template-rows: 100%; height: 25; - width: 200; + width: max-content; + min-width: max-content; justify-content: space-evenly; align-items: center; display: flex; position: absolute; right: 10px; + transition: all 0.2s; .miniPres-button-text { display: flex; @@ -812,6 +767,10 @@ width: max-content; font-family: Roboto; font-weight: 400; + margin-left: 3px; + margin-right: 3px; + padding-right: 3px; + padding-left: 3px; letter-spacing: normal; border-radius: 5px; align-items: center; @@ -825,10 +784,27 @@ border-right: solid 1px #5a5a5a; } + .miniPres-button-frame { + justify-self: center; + align-self: center; + align-items: center; + display: grid; + grid-template-columns: auto auto auto; + justify-content: space-around; + font-size: 11; + margin-left: 7; + width: 30; + height: 85%; + background-color: rgba(91, 157, 221, 0.4); + border-radius: 5px; + } + .miniPres-button { display: flex; height: 20; min-width: 20; + margin-left: 3px; + margin-right: 3px; border-radius: 100%; align-items: center; justify-content: center; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index ccf1fb8e3..230137584 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -20,17 +20,10 @@ import { Docs, DocUtils } from "../../documents/Documents"; import { PrefetchProxy } from "../../../fields/Proxy"; import { ScriptField } from "../../../fields/ScriptField"; import { Scripting } from "../../util/Scripting"; -import { InkingStroke } from "../InkingStroke"; -import { HighlightSpanKind } from "typescript"; -import { SearchUtil } from "../../util/SearchUtil"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; -import { child } from "serializr"; import { List } from "../../../fields/List"; import { Tooltip } from "@material-ui/core"; import { CollectionFreeFormViewChrome } from "../collections/CollectionMenu"; -import { conformsTo } from "lodash"; -import { translate } from "googleapis/build/src/apis/translate"; -import { DragManager, dropActionType } from "../../util/DragManager"; import { actionAsync } from "mobx-utils"; import { SelectionManager } from "../../util/SelectionManager"; import { AudioBox } from "./AudioBox"; @@ -41,9 +34,26 @@ const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - private treedropDisposer?: DragManager.DragDropDisposer; static Instance: PresBox; + @observable _isChildActive = false; + @observable _moveOnFromAudio: boolean = true; + @observable _presTimer!: NodeJS.Timeout; + + @observable _selectedArray: Doc[] = []; + @observable _sortedSelectedArray: Doc[] = []; + @observable _eleArray: HTMLElement[] = []; + @observable _dragArray: HTMLElement[] = []; + + @observable private transitionTools: boolean = false; + @observable private newDocumentTools: boolean = false; + @observable private progressivizeTools: boolean = false; + @observable private moreInfoTools: boolean = false; + @observable private playTools: boolean = false; + @observable private presentTools: boolean = false; + @observable private pathBoolean: boolean = false; + @observable private expandBoolean: boolean = false; + @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } @@ -63,8 +73,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } - - + @computed get selectedDocumentView() { + if (SelectionManager.SelectedDocuments().length) { + return SelectionManager.SelectedDocuments()[0]; + } else if (PresBox.Instance._selectedArray.length) { + return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + } else { return undefined; } + } + @computed get isPres(): boolean { + if (this.selectedDoc?.type === DocumentType.PRES) { + document.addEventListener("keydown", this.keyEvents, true); + return true; + } else { + document.removeEventListener("keydown", this.keyEvents, true); + return false; + } + } + @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } componentDidMount() { this.rootDoc.presBox = this.rootDoc; @@ -72,33 +97,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.rootDoc._replacedChrome = "replaced"; this.layoutDoc.presStatus = "edit"; this.layoutDoc._gridGap = 5; - // document.addEventListener("keydown", this.keyEvents, false); - } - - componentWillUnmount() { - this.treedropDisposer?.(); - } - - onPointerOver = () => { - document.addEventListener("keydown", this.keyEvents, true); - } - - onPointerLeave = () => { - document.removeEventListener("keydown", this.keyEvents, true); } updateCurrentPresentation = () => { Doc.UserDoc().activePresentation = this.rootDoc; } - @observable _moveOnFromAudio: boolean = false; - + /** + * Called when the user moves to the next slide in the presentation trail. + */ @undoBatch @action next = () => { this.updateCurrentPresentation(); const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); - const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const presTargetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); const childDocs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]); const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); @@ -114,29 +128,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); if (presTargetDoc.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc); // Case 2: Audio or video therefore wait to play the audio or video before moving on - } else if ((presTargetDoc.type === DocumentType.VID || presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio) { - if (presTargetDoc.type === DocumentType.AUDIO) { - AudioBox.Instance.playFrom(0); - this._moveOnFromAudio = true; - } - if (presTargetDoc.type === DocumentType.VID) { - this._moveOnFromAudio = true; - } + } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio) { + AudioBox.Instance.playFrom(0); + this._moveOnFromAudio = true; // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide - } else if (activeNext !== undefined) { - if (!presTargetDoc.presProgressivize) { - const nextTagDoc = Cast(this.childDocs[this.itemIndex + 1].presentationTargetDoc, Doc, null); - const nextChildDocs = DocListCast(nextTagDoc[Doc.LayoutFieldKey(presTargetDoc)]); - nextChildDocs.forEach((doc, i) => { - doc.opacity = 1; - }); - } + } else if (this.childDocs[this.itemIndex + 1] !== undefined) { const nextSelected = this.itemIndex + 1; this.gotoDocument(nextSelected, this.itemIndex); - this._moveOnFromAudio = false; + const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); + if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) { + } else { this._moveOnFromAudio = false }; } } + /** + * Called when the user moves back + * Design choice: If there are frames within the presentation, moving back will not + * got back through the frames but instead directly to the next point in the presentation. + */ @undoBatch @action back = () => { @@ -149,6 +158,93 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } + //The function that is called when a document is clicked or reached through next or back. + //it'll also execute the necessary actions if presentation is playing. + public gotoDocument = action((index: number, fromDoc: number) => { + this.updateCurrentPresentation(); + Doc.UnBrushAllDocs(); + if (index >= 0 && index < this.childDocs.length) { + this.rootDoc._itemIndex = index; + const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); + if (presTargetDoc?.lastFrame !== undefined) { + presTargetDoc.currentFrame = 0; + } + this.navigateToElement(this.childDocs[index]); //Handles movement to element + this._selectedArray = [this.childDocs[index]]; //Update selected array + this.onHideDocument(); //Handles hide after/before + } + }); + + /** + * This method makes sure that cursor navigates to the element that + * has the option open and last in the group. + * Design choice: If the next document is not in presCollection or + * presCollection itself then if there is a presCollection it will add + * a new tab. If presCollection is undefined it will open the document + * on the right. + */ + navigateToElement = async (curDoc: Doc) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const srcContext = await DocCastAsync(targetDoc.context); + const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); + const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; + this.turnOffEdit(); + + if (this.itemIndex >= 0) { + if (targetDoc) { + if (srcContext) this.layoutDoc.presCollection = srcContext; + } else if (targetDoc) this.layoutDoc.presCollection = targetDoc; + } + if (collectionDocView) { + if (srcContext && srcContext !== presCollection) { + // Case 1: new srcContext inside of current collection so add a new tab to the current pres collection + collectionDocView.props.addDocTab(srcContext, "inPlace"); + } + } + this.updateCurrentPresentation(); + const docToJump = curDoc; + const willZoom = false; + + //docToJump stayed same meaning, it was not in the group or was the last element in the group + if (targetDoc.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { + this.zoomProgressivizeNext(targetDoc); + } else if (docToJump === curDoc) { + //checking if curDoc has navigation open + if (curDoc.presNavButton && targetDoc) { + await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); + } else if (curDoc.presZoomButton && targetDoc) { + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); + } + } else { + //awaiting jump so that new scale can be found, since jumping is async + targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); + } + // After navigating to the document, if it is added as a presPinView then it will + // adjust the pan and scale to that of the pinView when it was added. + // TODO: Add option to remove presPinView + if (activeItem.presPinView) { + targetDoc._panX = activeItem.presPinViewX; + targetDoc._panY = activeItem.presPinViewY; + targetDoc._viewScale = activeItem.presPinViewScale; + } + // If openDocument is selected then it should open the document for the user + if (collectionDocView && activeItem.openDocument) { + collectionDocView.props.addDocTab(activeItem, "inPlace"); + } + // If website and has presWebsite data associated then on click it should + // go back to that specific website + // TODO: Add progressivize for navigating web (storing websites for given frames) + if (targetDoc.presWebsiteData) { + targetDoc.data = targetDoc.presWebsiteData; + } + } + + /** + * Uses the viewfinder to progressivize through the different views of a single collection. + * @param presTargetDoc: document for which internal zoom is used + */ zoomProgressivizeNext = (presTargetDoc: Doc) => { const srcContext = Cast(presTargetDoc.context, Doc, null); const docView = DocumentManager.Instance.getDocumentView(presTargetDoc); @@ -190,6 +286,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } + /** + * For 'Hide Before' and 'Hide After' buttons making sure that + * they are hidden each time the presentation is updated. + */ @action onHideDocument = () => { this.childDocs.forEach((doc, index) => { @@ -213,97 +313,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> }); } - /** - * This method makes sure that cursor navigates to the element that - * has the option open and last in the group. If not in the group, and it has - * the option open, navigates to that element. - */ - navigateToElement = async (curDoc: Doc) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - const srcContext = await DocCastAsync(targetDoc.context); - const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); - const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; - this.turnOffEdit(); - - if (this.itemIndex >= 0) { - if (targetDoc) { - if (srcContext) this.layoutDoc.presCollection = srcContext; - } else if (targetDoc) this.layoutDoc.presCollection = targetDoc; - } - if (collectionDocView) { - if (srcContext && srcContext !== presCollection) { - // Case 1: new srcContext inside of current collection so add a new tab to the current pres collection - collectionDocView.props.addDocTab(srcContext, "inPlace"); - } - } - // else if (srcContext) { - // console.log("Case 2: srcContext - not open and collection containing this document exists, so open collection that contains it and then await zooming in on document"); - // this.props.addDocTab(srcContext, "onRight"); - // } else if (!srcContext) { - // console.log("Case 3: !srcContext - no collection containing this document, therefore open document itself on right"); - // this.props.addDocTab(targetDoc, "onRight"); - // } - this.updateCurrentPresentation(); - const docToJump = curDoc; - const willZoom = false; - - //docToJump stayed same meaning, it was not in the group or was the last element in the group - if (targetDoc.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { - this.zoomProgressivizeNext(targetDoc); - } else if (docToJump === curDoc) { - //checking if curDoc has navigation open - if (curDoc.presNavButton && targetDoc) { - await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); - } else if (curDoc.presZoomButton && targetDoc) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); - } - } else { - //awaiting jump so that new scale can be found, since jumping is async - targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); - } - if (activeItem.presPinView) { - targetDoc._panX = activeItem.presPinViewX; - targetDoc._panY = activeItem.presPinViewY; - targetDoc._viewScale = activeItem.presPinViewScale; - } - if (collectionDocView && activeItem.openDocument) { - collectionDocView.props.addDocTab(activeItem, "inPlace"); - } - if (targetDoc.presWebsiteData) { - targetDoc.data = targetDoc.presWebsiteData; - } - } - - //The function that is called when a document is clicked or reached through next or back. - //it'll also execute the necessary actions if presentation is playing. - public gotoDocument = action((index: number, fromDoc: number) => { - this.updateCurrentPresentation(); - Doc.UnBrushAllDocs(); - if (index >= 0 && index < this.childDocs.length) { - this.rootDoc._itemIndex = index; - const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); - if (presTargetDoc?.lastFrame !== undefined) { - presTargetDoc.currentFrame = 0; - } - this.navigateToElement(this.childDocs[index]); - this._selectedArray = [this.childDocs[index]]; - // this.hideIfNotPresented(index); - // this.showAfterPresented(index); - // this.hideDocumentInPres(); - this.onHideDocument(); - // this.onHideAfterPresClick(); - } - }); - - @observable _presTimer!: NodeJS.Timeout; - - //The function that starts or resets presentaton functionally, depending on status flag. + //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @undoBatch @action - startOrResetPres = (startSlide: number) => { + startAutoPres = (startSlide: number) => { this.updateCurrentPresentation(); const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); @@ -326,15 +340,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. + // TODO: Ensure resetPresentation is called when the presentation is closed resetPresentation = () => { this.updateCurrentPresentation(); - this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1); this.rootDoc._itemIndex = 0; - // this.layoutDoc.presStatus = false; } - //The function that starts the presentation, also checking if actions should be applied - //directly at start. + @action togglePath = () => this.pathBoolean = !this.pathBoolean; + @action toggleExpand = () => this.expandBoolean = !this.expandBoolean; + + /** + * The function that starts the presentation at the given index, also checking if actions should be applied + * directly at start. + * @param startIndex: index that the presentation will start at + */ startPresentation = (startIndex: number) => { this.updateCurrentPresentation(); this.childDocs.map(doc => { @@ -348,55 +367,70 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> }); } + /** + * The method called to open the presentation as a minimized view + * TODO: Look at old updateMinimize and compare... + */ updateMinimize = () => { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); this.turnOffEdit(); if (srcContext) { if (srcContext.miniPres) { - document.removeEventListener("keydown", this.minimizeEvents, false); srcContext.miniPres = false; CollectionDockingView.AddRightSplit(this.rootDoc); } else { - document.addEventListener("keydown", this.minimizeEvents, false); srcContext.miniPres = true; this.props.addDocTab?.(this.rootDoc, "close"); } } } + /** + * Called when the user changes the view type + * Either 'List' (stacking) or 'Slides' (carousel) + */ @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; - viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here + // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here + viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); this.rootDoc._viewType = viewType; if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 5; }); + /** + * When the movement dropdown is changes + */ @undoBatch movementChanged = action((movement: string) => { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - if (movement === 'zoom') { - activeItem.presZoomButton = !activeItem.presZoomButton; - if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom'; - else activeItem.presMovement = 'None'; - activeItem.presNavButton = false; - } else if (movement === 'nav') { - activeItem.presZoomButton = false; - activeItem.presNavButton = !activeItem.presNavButton; - if (activeItem.presNavButton) activeItem.presMovement = 'Pan'; - else activeItem.presMovement = 'None'; - } else if (movement === 'switch') { - targetDoc.presTransition = 0; - activeItem.presSwitchButton = !activeItem.presSwitchButton; - if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut'; - else activeItem.presMovement = 'None'; - } else { - activeItem.presMovement = 'None'; - activeItem.presZoomButton = false; - activeItem.presNavButton = false; - activeItem.presSwitchButton = false; + switch (movement) { + case 'zoom': //Pan and zoom + activeItem.presZoomButton = !activeItem.presZoomButton; + if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom'; + else activeItem.presMovement = 'None'; + activeItem.presNavButton = false; + break; + case 'pan': //Pan + activeItem.presZoomButton = false; + activeItem.presNavButton = !activeItem.presNavButton; + if (activeItem.presNavButton) activeItem.presMovement = 'Pan'; + else activeItem.presMovement = 'None'; + break; + case 'jump': //Jump Cut + targetDoc.presTransition = 0; + activeItem.presSwitchButton = !activeItem.presSwitchButton; + if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut'; + else activeItem.presMovement = 'None'; + break; + case 'none': default: + activeItem.presMovement = 'None'; + activeItem.presZoomButton = false; + activeItem.presNavButton = false; + activeItem.presSwitchButton = false; + break; } }); @@ -421,13 +455,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) - // KEYS - @observable _selectedArray: Doc[] = []; - @observable _sortedSelectedArray: Doc[] = []; - @observable _eleArray: HTMLElement[] = []; - @observable _dragArray: HTMLElement[] = []; - - //TODO: Update to radix / quick sort + /** + * For sorting the array so that the order is maintained when it is dropped. + */ @action sortArray = (): Doc[] => { const sort: Doc[] = this._selectedArray; @@ -439,6 +469,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return sort; } + /** + * Method to get the list of selected items in the order in which they have been selected + */ @computed get listOfSelected() { const list = this._selectedArray.map((doc: Doc, index: any) => { const activeItem = Cast(doc, Doc, null); @@ -459,9 +492,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - this._selectedArray.push(this.childDocs[this.childDocs.indexOf(doc)]); - this._eleArray.push(ref); - this._dragArray.push(drag); + if (!this._selectedArray.includes(doc)) { + this._selectedArray.push(this.childDocs[this.childDocs.indexOf(doc)]); + this._eleArray.push(ref); + this._dragArray.push(drag); + } } //Shift click @@ -478,20 +513,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } - @action - minimizeEvents = (e: KeyboardEvent) => { - e.stopPropagation(); - e.preventDefault(); - if (e.keyCode === 27) { // Escape key - this.layoutDoc.presStatus = "edit"; - } if (e.keyCode === 37) { // left(37) / a(65) / up(38) to go back - this.back(); - } if (e.keyCode === 39) { // right (39) / d(68) / down(40) to go to next - this.next(); - } - } - - //Esc click + // Key for when the presentaiton is active (according to Selection Manager) @action keyEvents = (e: KeyboardEvent) => { e.stopPropagation(); @@ -502,12 +524,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> else this.layoutDoc.presStatus = "edit"; } if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all if (this.layoutDoc.presStatus === "edit") this._selectedArray = this.childDocs; - } if (e.keyCode === 37) { // left(37) / a(65) / up(38) to go back - if (this.layoutDoc.presStatus !== "edit") this.back(); - } if (e.keyCode === 39) { // right (39) / d(68) / down(40) to go to next - if (this.layoutDoc.presStatus !== "edit") this.next(); + } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back + this.back(); + } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next + this.next(); } if (e.keyCode === 32) { // spacebar to 'present' or autoplay - if (this.layoutDoc.presStatus !== "edit") this.startOrResetPres(0); + if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0); else this.layoutDoc.presStatus = "manual"; } if (e.keyCode === 8) { // delete selected items @@ -519,15 +541,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } - @observable private transitionTools: boolean = false; - @observable private newDocumentTools: boolean = false; - @observable private progressivizeTools: boolean = false; - @observable private moreInfoTools: boolean = false; - @observable private playTools: boolean = false; - @observable private presentTools: boolean = false; - @observable private pathBoolean: boolean = false; - @observable private expandBoolean: boolean = false; - + /** + * + */ @undoBatch @action viewPaths = async () => { @@ -551,16 +567,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const fit = srcContext?._fitToBox; } + // Adds the index in the pres path graphically @computed get order() { const order: JSX.Element[] = []; this.childDocs.forEach((doc, index) => { const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(targetDoc.context, Doc, null); + // Case A: Document is contained within the colleciton if (this.rootDoc.presCollection === srcContext) { order.push( <div className="pathOrder" style={{ top: NumCast(targetDoc.y), left: NumCast(targetDoc.x) }}> <div className="pathOrder-frame">{index + 1}</div> </div>); + // Case B: Document is not inside of the collection } else { order.push( <div className="pathOrder" style={{ top: 0, left: 0 }}> @@ -571,6 +590,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return order; } + /** + * Method called for viewing paths which adds a single line with + * points at the center of each document added. + * Design choice: When this is called it sets _fitToBox as true so the + * user can have an overview of all of the documents in the collection. + * (Design needed for when documents in presentation trail are in another + * collection) + */ @computed get paths() { let pathPoints = ""; this.childDocs.forEach((doc, index) => { @@ -601,9 +628,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> />); } - @action togglePath = () => this.pathBoolean = !this.pathBoolean; - @action toggleExpand = () => this.expandBoolean = !this.expandBoolean; - /** * The function that is called on click to turn fading document after presented option on/off. * It also makes sure that the option swithches from hide-after to this one, since both @@ -627,13 +651,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } - // WHAT IS THIS? - @action - dropdownToggle = (menu: string) => { - const dropMenu = document.getElementById('presBox' + menu + 'Dropdown'); - if (dropMenu) dropMenu.style.display === 'none' ? dropMenu.style.display = 'block' : dropMenu.style.display = 'none'; - } - + // Converts seconds to ms and updates presTransition setTransitionTime = (number: String) => { const timeInMS = Number(number) * 1000; const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); @@ -641,6 +659,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (targetDoc) targetDoc.presTransition = timeInMS; } + // Converts seconds to ms and updates presDuration setDurationTime = (number: String) => { const timeInMS = Number(number) * 1000; const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); @@ -648,29 +667,28 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (targetDoc) targetDoc.presDuration = timeInMS; } + @computed get transitionDropdown() { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); if (activeItem && targetDoc) { const transitionSpeed = targetDoc.presTransition ? String(Number(targetDoc.presTransition) / 1000) : 0.5; - const duration = targetDoc.presDuration ? String(Number(targetDoc.presDuration) / 1000) : 2; + let duration = targetDoc.presDuration ? String(Number(targetDoc.presDuration) / 1000) : 2; + if (targetDoc.type === DocumentType.AUDIO) duration = NumCast(targetDoc.duration); const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> Movement - <div className="presBox-dropdown" - onPointerDown={e => e.stopPropagation()} - // onClick={() => this.dropdownToggle('Movement')} - > + <div className="presBox-dropdown" onPointerDown={e => e.stopPropagation()}> {activeItem.presMovement} <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} /> <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}> <div className={`presBox-dropdownOption ${activeItem.presMovement === 'None' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('none')}>None</div> <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('zoom')}>Pan and Zoom</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('nav')}>Pan</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Jump cut' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('switch')}>Jump cut</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('pan')}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Jump cut' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('jump')}>Jump cut</div> </div> </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "inline-flex" : "none" }}> @@ -694,8 +712,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="presBox-subheading">Slide Duration</div> <div className="ribbon-property"> {duration} s </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={duration} style={{ transform: 'rotate(0deg)' }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> - <div className={"slider-headers"}> + <input type="range" step="0.1" min="0.1" max="10" value={duration} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> + <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> <div className="slider-text">Short</div> <div className="slider-text">Medium</div> <div className="slider-text">Long</div> @@ -783,12 +801,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Start automatically</div> - <div className="ribbon-button" style={{ display: "flex", backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Start manually</div> + <div className="ribbon-button" style={{ backgroundColor: activeItem.playAuto ? "#aedef8" : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> + <div className="ribbon-button" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : "#aedef8" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> </div> <div className="ribbon-doubleButton" style={{ display: "flex" }}> <div className="ribbon-button" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={() => activeItem.openDocument = !activeItem.openDocument}>Open document</div> </div> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.presPinView ? "#aedef8" : "" }} + onClick={() => { + activeItem.presPinView = !activeItem.presPinView; + if (activeItem.presPinView) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + }}>Presentation pin view</div> + </div> <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div> </div> @@ -838,12 +870,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> Slide Title: <br></br> - {/* <div className="dropdown-textInput"> */} <input className="ribbon-textInput" placeholder="..." type="text" name="fname" ref={this.inputRef} onChange={(e) => { e.stopPropagation(); runInAction(() => this.title = e.target.value); }}></input> - {/* </div> */} </div> <div className="ribbon-box"> Choose type: @@ -893,12 +923,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (freeform && layout) doc = this.createTemplate(layout, title); if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title }); const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); - const data = Cast(presCollection.data, listSpec(Doc)); + const data = Cast(presCollection?.data, listSpec(Doc)); const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && doc && presData) { data.push(doc); DockedFrameRenderer.PinDoc(doc, false); this.gotoDocument(this.childDocs.length, this.itemIndex); + } else { + this.props.addDocTab(doc as Doc, "onRight"); } } @@ -916,7 +948,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" }); const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" }); const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" }); - const content = Docs.Create.TextDocument("Click to change texte", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); + const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" }); const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" }); switch (layout) { @@ -941,20 +973,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return doc; } - // Dropdown that appears for autoplay - @computed get playDropdown() { - return ( - <div className={`dropdown-play ${this.playTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="dropdown-play-button" onClick={() => this.startOrResetPres(this.itemIndex)}> - Present from current slide - </div> - <div className="dropdown-play-button" onClick={() => this.startOrResetPres(0)}> - Present from first slide - </div> - </div> - ); - } - // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( @@ -962,19 +980,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="dropdown-play-button" onClick={this.updateMinimize}> Minimize </div> - <div className="dropdown-play-button" onClick={() => { this.startOrResetPres(0); this.turnOffEdit(); }}> + <div className="dropdown-play-button" onClick={(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(); }))}> Sidebar view </div> </div> ); } - // For toggling the options when the user wants to select play - @action togglePlay = () => { this.playTools = !this.playTools; }; - - // For toggling the options when the user wants to select play - @action togglePresent = () => { this.presentTools = !this.presentTools; }; - // Case in which the document has keyframes to navigate to next key frame @undoBatch @action @@ -986,10 +998,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - // let lastFrame: number = 0; - // childDocs.forEach((doc) => { - // if (NumCast(doc.appearFrame) > lastFrame) lastFrame = NumCast(doc.appearFrame); - // }); CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1); @@ -1027,6 +1035,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } + /** + * Returns the collection type as a string for headers + */ @computed get stringType(): string { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); @@ -1064,10 +1075,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Viewfinder</div> {/* <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editSnapZoomProgressivize ? "#aedef8" : "" }} onClick={this.editSnapZoomProgressivize}>Snapshot</div> */} </div> - <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> + {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> <div className="ribbon-button" onClick={this.progressivizeText}>Text progressivize</div> <div className="ribbon-button" style={{ display: activeItem.textProgressivize ? "flex" : "none", backgroundColor: targetDoc.editTextProgressivize ? "#aedef8" : "" }} onClick={this.editTextProgressivize}>Edit</div> - </div> + </div> */} <div className="ribbon-doubleButton" style={{ display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll progressivize</div> <div className="ribbon-button" style={{ display: activeItem.scrollProgressivize ? "flex" : "none", backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> @@ -1099,7 +1110,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } turnOffEdit = () => { - this.childDocs.forEach((doc, index) => { + this.childDocs.forEach((doc) => { + doc.editSnapZoomProgressivize = false; + doc.editZoomProgressivize = false; + doc.editScrollProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); targetDoc.editSnapZoomProgressivize = false; targetDoc.editZoomProgressivize = false; @@ -1376,8 +1390,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const doc = document.getElementById('resizable'); if (doc && tagDocView) { - const scale = tagDocView.childScaling(); - const scale2 = tagDocView.props.ScreenToLocalTransform().Scale; + const scale2 = tagDocView.childScaling(); + const scale3 = tagDocView.props.ScreenToLocalTransform().Scale; + const scale = NumCast(targetDoc._viewScale); + console.log("scale: " + NumCast(targetDoc._viewScale)); let height = doc.offsetHeight; let width = doc.offsetWidth; let top = doc.offsetTop; @@ -1414,9 +1430,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // } //Bottom right if (this._isDraggingBR) { - const newHeight = height += (e.movementY * scale2); + const newHeight = height += (e.movementY * scale); doc.style.height = newHeight + 'px'; - const newWidth = width += (e.movementX * scale2); + const newWidth = width += (e.movementX * scale); doc.style.width = newWidth + 'px'; // Bottom left } else if (this._isDraggingBL) { @@ -1428,26 +1444,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> doc.style.left = newLeft + 'px'; // Top right } else if (this._isDraggingTR) { - const newWidth = width += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + const newWidth = width += (e.movementX * scale); doc.style.width = newWidth + 'px'; - const newHeight = height -= (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + const newHeight = height -= (e.movementY * scale); doc.style.height = newHeight + 'px'; - const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + const newTop = top += (e.movementY * scale); doc.style.top = newTop + 'px'; // Top left } else if (this._isDraggingTL) { - const newWidth = width -= (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + const newWidth = width -= (e.movementX * scale); doc.style.width = newWidth + 'px'; - const newHeight = height -= (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + const newHeight = height -= (e.movementY * scale); doc.style.height = newHeight + 'px'; - const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + const newTop = top += (e.movementY * scale); doc.style.top = newTop + 'px'; - const newLeft = left += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + const newLeft = left += (e.movementX * scale); doc.style.left = newLeft + 'px'; } else if (this._isDragging) { - const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + const newTop = top += (e.movementY * scale); doc.style.top = newTop + 'px'; - const newLeft = left += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + const newLeft = left += (e.movementX * scale); doc.style.left = newLeft + 'px'; } this.updateList(targetDoc, targetDoc["viewfinder-width-indexed"], width); @@ -1487,20 +1503,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get zoomProgressivizeContainer() { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - if (targetDoc.editZoomProgressivize) { + if (targetDoc) { + const vfLeft: number = this.checkList(targetDoc, targetDoc["viewfinder-left-indexed"]); + const vfWidth: number = this.checkList(targetDoc, targetDoc["viewfinder-width-indexed"]); + const vfTop: number = this.checkList(targetDoc, targetDoc["viewfinder-top-indexed"]); + const vfHeight: number = this.checkList(targetDoc, targetDoc["viewfinder-height-indexed"]); return ( <> - <div id="resizable" className="resizable" onPointerDown={this.onPointerMid} style={{ width: this.checkList(targetDoc, targetDoc["viewfinder-width-indexed"]), height: this.checkList(targetDoc, targetDoc["viewfinder-height-indexed"]), top: this.checkList(targetDoc, targetDoc["viewfinder-top-indexed"]), left: this.checkList(targetDoc, targetDoc["viewfinder-left-indexed"]), position: 'absolute' }}> + {!targetDoc.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerMid} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}> <div className='resizers'> <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerTL}></div> <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerTR}></div> <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerBL}></div> <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerBR}></div> </div> - </div> + </div>} </> ); - } else return null; + } } @computed get progressivizeChildDocs() { @@ -1585,116 +1605,107 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get toolbar() { const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - if (activeItem) { - return ( - <> - <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}><FontAwesomeIcon icon={"plus"} /> - <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> - </div></Tooltip> - <div className="toolbar-divider" /> - <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}><div className={`toolbar-button ${this.pathBoolean ? "active" : ""}`}> - <FontAwesomeIcon icon={"exchange-alt"} onClick={this.viewPaths} /> - </div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{this.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> - <div className={`toolbar-button ${this.expandBoolean ? "active" : ""}`} onClick={() => { this.toggleExpand(); this.childDocs.forEach((doc, ind) => { if (this.expandBoolean) doc.presExpandInlineButton = true; else doc.presExpandInlineButton = false; }); }}> - <FontAwesomeIcon icon={"eye"} /> - </div> - </Tooltip> - {/* <div className="toolbar-button"><FontAwesomeIcon title={"Portal"} icon={"eye"} onClick={this.toolbarTest} /></div> */} - <div className="toolbar-divider" /> - {/* <Tooltip title={<><div className="dash-tooltip">{"Transitions"}</div></>}><div className={`toolbar-button ${this.transitionTools ? "active" : ""}`} onClick={this.toggleTransitionTools}> - <FontAwesomeIcon icon={"rocket"} /> - <div style={{ display: this.props.PanelWidth() > 430 ? "block" : "none" }} className="toolbar-buttonText"> Transitions</div> - <FontAwesomeIcon className={`dropdown ${this.transitionTools ? "active" : ""}`} icon={"angle-down"} /> - </div></Tooltip> - <div className="toolbar-divider" /> - <Tooltip title={<><div className="dash-tooltip">{"Progressivize"}</div></>}><div className={`toolbar-button ${this.progressivizeTools ? "active" : ""}`} onClick={this.toggleProgressivize}> - <FontAwesomeIcon icon={"tasks"} /> - <div style={{ display: this.props.PanelWidth() > 430 ? "block" : "none" }} className="toolbar-buttonText"> Progressivize</div> - <FontAwesomeIcon className={`dropdown ${this.progressivizeTools ? "active" : ""}`} icon={"angle-down"} /> - </div></Tooltip> - <div className="toolbar-divider" /> */} - {/* <div className="toolbar-button" style={{ position: 'absolute', right: 23, transform: 'rotate(45deg)', fontSize: 16 }}> - <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"thumbtack"} /> + return ( + <div id="toolbarContainer" className={'presBox-toolbar'} style={{ display: this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }}> + <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> + <FontAwesomeIcon icon={"plus"} /> + <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> + </div></Tooltip> + <div className="toolbar-divider" /> + <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}> + <div style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3 }} className={`toolbar-button ${this.pathBoolean ? "active" : ""}`} onClick={this.childDocs.length > 1 ? this.viewPaths : undefined}> + <FontAwesomeIcon icon={"exchange-alt"} /> </div> - <div className={`toolbar-button ${this.moreInfoTools ? "active" : ""}`} onClick={this.toggleMoreInfo}> - <div className={`toolbar-moreInfo ${this.moreInfoTools ? "active" : ""}`}> - <div className="toolbar-moreInfoBall" /> - <div className="toolbar-moreInfoBall" /> - <div className="toolbar-moreInfoBall" /> - </div> - </div> */} - </> - ); - } else { - return ( - <> - <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> - <FontAwesomeIcon icon={"plus"} /> - <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> - </div></Tooltip> - {/* <div className="toolbar-button" style={{ position: 'absolute', right: 23, transform: 'rotate(45deg)', fontSize: 16 }}> - <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"thumbtack"} /> + </Tooltip> + <Tooltip title={<><div className="dash-tooltip">{this.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> + <div style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }} className={`toolbar-button ${this.expandBoolean ? "active" : ""}`} onClick={() => { if (this.childDocs.length > 0) this.toggleExpand(); this.childDocs.forEach((doc, ind) => { if (this.expandBoolean) doc.presExpandInlineButton = true; else doc.presExpandInlineButton = false; }); }}> + <FontAwesomeIcon icon={"eye"} /> </div> - <div className={`toolbar-button ${this.moreInfoTools ? "active" : ""}`} onClick={this.toggleMoreInfo}> - <div className={`toolbar-moreInfo ${this.moreInfoTools ? "active" : ""}`}> - <div className="toolbar-moreInfoBall" /> - <div className="toolbar-moreInfoBall" /> - <div className="toolbar-moreInfoBall" /> - </div> - </div> */} - </> - ); - } + </Tooltip> + <div className="toolbar-divider" /> + </div> + ); } - render() { - this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields + /** + * Top panel containes: + * viewPicker: The option to choose between List and Slides view for the presentaiton trail + * presentPanel: The button to start the presentation / open minimized view of the presentation + */ + @computed get topPanel() { const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - // const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { - // return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); - // }; - return <div onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > + return ( <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}> <select className="presBox-viewPicker" + style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option> </select> - <div className="presBox-presentPanel"> + <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length > 0 ? 1 : 0.3 }}> <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}> - <div className="presBox-button-left" onClick={() => this.layoutDoc.presStatus = "manual"}><FontAwesomeIcon icon={"play-circle"} /> Present </div> - <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} onClick={e => { e.stopPropagation; this.togglePresent(); }}> + <div className="presBox-button-left" onClick={() => { if (this.childDocs.length > 0) this.layoutDoc.presStatus = "manual" }}> + <FontAwesomeIcon icon={"play-circle"} /> + <div style={{ display: this.props.PanelWidth() > 200 ? "inline-flex" : "none" }}> Present</div> + </div> + <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} + onClick={(action(() => { + if (this.childDocs.length > 0) this.presentTools = !this.presentTools; + }))}> <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> {this.presentDropdown} </div> </span> - <div className="miniPresOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> - {/* <span className={`presBox-button ${this.layoutDoc.presStatus !== "edit" ? "active" : ""}`}> - <div className="presBox-button-left" onClick={() => this.startOrResetPres(0)}> - <FontAwesomeIcon icon={"clock"} /> - <FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /> - </div> - <div className={`presBox-button-right ${this.playTools ? "active" : ""}`} onClick={e => { e.stopPropagation; this.togglePlay(); }}> - <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.playTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> - {this.playDropdown} - </div> - </span> */} - <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => this.startOrResetPres(this.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> - <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text">Slide {this.itemIndex + 1} / {this.childDocs.length}</div> - <div className="miniPres-divider"></div> - <div className="miniPres-button" onClick={() => this.layoutDoc.presStatus = "edit"}> - <FontAwesomeIcon icon={"times"} /> - </div> - </div> + {this.playButtons} </div> </div> - <div id="toolbarContainer" className={`presBox-toolbar ${this.layoutDoc.presStatus === "edit" ? "active" : ""}`}> {this.toolbar} </div> + ); + } + + @computed get playButtonFrames() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + return ( + <> + {targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> + <div>{targetDoc.currentFrame}</div> + <div className="miniPres-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> + <div>{targetDoc.lastFrame}</div> + </div> : null} + </> + ); + } + + @computed get playButtons() { + // Case 1: There are still other frames and should go through all frames before going to next slide + return (<div className="miniPresOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> + <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="miniPres-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> + Slide {this.itemIndex + 1} / {this.childDocs.length} + {this.playButtonFrames} + </div> + <div className="miniPres-divider"></div> + {this.props.PanelWidth() > 250 ? <div className="miniPres-button-text" onClick={() => this.layoutDoc.presStatus = "edit"}>EXIT</div> + : <div className="miniPres-button" onClick={() => this.layoutDoc.presStatus = "edit"}> + <FontAwesomeIcon icon={"times"} /> + </div>} + </div>); + } + + render() { + // calling this method for keyEvents + this.isPres; + // needed to ensure that the childDocs are loaded for looking up fields + this.childDocs.slice(); + const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > + {this.topPanel} + {this.toolbar} {this.newDocumentToolbarDropdown} <div className="presBox-listCont"> {mode !== CollectionViewType.Invalid ? diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index af4bd77c7..cc37cf586 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -577,7 +577,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const mainBulletText: string[] = []; const mainBulletList: Doc[] = []; if (list) { - const newBullets: Doc[] = this.recursiveProgressivize(1, list); + const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0]; mainBulletList.push.apply(mainBulletList, newBullets); } console.log(mainBulletList.length); @@ -591,7 +591,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.props.addDocument?.(doc); } - recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): Doc[] => { + recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): [Doc[], number] => { const mainBulletList: Doc[] = []; let b = d ? d : 0; let yLoc = y ? y : 0; @@ -607,29 +607,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (listItem.childElementCount > 1) { b++; nestCount++; - count = before ? count + nestCount + "." : nestCount + "."; yLoc += height; + count = before ? count + nestCount + "." : nestCount + "."; const text = listItem.getElementsByTagName("p")[0].innerText; const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _height: height, x: xLoc, y: 10 + (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + // yLoc += NumCast(bullet1._height); mainBulletList.push(bullet1); const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count); - mainBulletList.push.apply(mainBulletList, newList); - b += newList.length; + mainBulletList.push.apply(mainBulletList, newList[0]); yLoc += newList.length * (55 - ((nestDepth + 1) * 5)); } else { b++; nestCount++; - count = before ? count + nestCount + "." : nestCount + "."; yLoc += height; + count = before ? count + nestCount + "." : nestCount + "."; const text = listItem.innerText; const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _height: height, x: xLoc, y: 10 + (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + // yLoc += NumCast(bullet1._height); mainBulletList.push(bullet1); } } }); - return mainBulletList; + return [mainBulletList, yLoc]; } recordDictation = () => { diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index fa70b2a41..1e776384a 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -1,6 +1,10 @@ +$light-blue: #AEDDF8; +$dark-blue: #5B9FDD; +$light-background: #ececec; + .presElementBox-item { display: grid; - grid-template-columns: max-content max-content max-content; + grid-template-columns: max-content max-content max-content max-content; background-color: #d5dce2; font-family: Roboto; letter-spacing: normal; @@ -26,7 +30,7 @@ z-index: -1; width: calc(100% + 200px); height: calc(100% + 8px); - background-color: #AEDDF8; + background-color: $light-blue; } .presElementBox-highlightTop { @@ -69,7 +73,7 @@ .presElementBox-active { color: black; border-radius: 6px; - border: solid 2px #5B9FDD; + border: solid 2px $dark-blue; } .presElementBox-buttons { @@ -118,7 +122,7 @@ padding-left: 10px; padding-right: 10px; letter-spacing: normal; - max-width: max-content; + width: max-content; text-overflow: ellipsis; overflow: hidden; white-space: pre; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 11ffde9dd..a6dbb76ef 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -189,7 +189,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]} LibraryPath={emptyPath} fitToBox={true} - backgroundColor={() => "darkgrey"} + backgroundColor={this.props.backgroundColor} rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} @@ -238,7 +238,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc e.stopPropagation(); e.preventDefault(); if (element) { - console.log(element.className); if (PresBox.Instance._eleArray.includes(element)) { setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); } @@ -254,11 +253,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } startDrag = (e: PointerEvent, down: number[], delta: number[]) => { - // const ele: HTMLElement[] = PresBox.Instance._eleArray.map(doc => doc); const activeItem = this.rootDoc; const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray().map(doc => doc)); - // let value = this.getValue(this._heading); - // value = typeof value === "string" ? `"${value}"` : value; const dragItem: HTMLElement[] = []; PresBox.Instance._dragArray.map(ele => { const drag = ele; @@ -330,14 +326,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc <div className="presElementBox-number"> {`${this.indexInPres + 1}.`} </div> - <div ref={this._dragRef} className="presElementBox-name"> + <div ref={this._dragRef} className="presElementBox-name" style={{ maxWidth: (PresBox.Instance.toolbarWidth - 70) }}> {`${this.targetDoc?.title}`} </div> <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.transition}</div></Tooltip> <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.duration}</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Presentation pin view"}</div></>}><div className="presElementBox-time" style={{ fontWeight: 700, display: this.rootDoc.presPinView && PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>V</div></Tooltip> <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div className="presElementBox-closeIcon" - // onPointerDown={e => e.stopPropagation()} onClick={e => { this.props.removeDocument?.(this.rootDoc); e.stopPropagation(); @@ -363,6 +359,4 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc </div> ); } -} - -// this.layoutDoc.title !== "pres element template"
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6163fedbb..6bfe91378 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -508,9 +508,9 @@ export namespace Doc { alias.aliasOf = doc; alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); alias.author = Doc.CurrentUserEmail; + alias[AclSym] = doc[AclSym]; - if (!doc.aliases) doc.aliases = new List<Doc>([alias]); - else Doc.AddDocToList(doc, "aliases", alias); + Doc.AddDocToList(doc[DataSym], "aliases", alias); return alias; } diff --git a/src/fields/util.ts b/src/fields/util.ts index 44a3317db..4c71572db 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -220,7 +220,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc // maps over the aliases of the document if (target.aliases) { DocListCast(target.aliases).map(alias => { - distributeAcls(key, acl, alias); + distributeAcls(key, acl, alias, inheritingFromCollection); }); } |