diff options
Diffstat (limited to 'src')
33 files changed, 643 insertions, 650 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 39781a5ce..7a06e1bc1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -402,20 +402,20 @@ export class CurrentUserUtils { this.setupActiveMobileMenu(doc); } return [ - { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc }, { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc }, - { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' }, - { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, - { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, + { title: "Drag a cat image", label: "Image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' }, + { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc }, + { title: "Drag a screengrabber", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' }, + // { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` }, - { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' }, + { title: "Drag a button", label: "Button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' }, { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc }, { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' }, { title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc }, - { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, + // { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, { title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, - { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, + // { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, @@ -423,8 +423,6 @@ export class CurrentUserUtils { // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc }, { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, - { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } diff --git a/src/client/util/GroupManager.scss b/src/client/util/GroupManager.scss index 544a79e98..8a2c616b1 100644 --- a/src/client/util/GroupManager.scss +++ b/src/client/util/GroupManager.scss @@ -1,23 +1,61 @@ -@import "../views/globalCssVariables"; +// @import "../views/globalCssVariables"; .group-interface { - background-color: whitesmoke !important; - color: grey; - width: 450px; + // background-color: whitesmoke !important; + // color: grey; + width: 550px; height: 300px; .dialogue-box { - width: 450; - height: 300; + .group-create { + display: flex; + flex-direction: column; + height: 90%; + justify-content: space-between; + // flex-basis: 30%; + margin-left: 5px; + + input { + border-radius: 5px; + // border: none; + padding: 8px; + min-width: 100%; + // margin: 4px 0 4px 0; + border: 1px solid hsl(0, 0%, 80%); + outline: none; + height: 30; + + &:focus { + // border: unset; + border: 2.5px solid #2684FF; + } + } + + p { + font-size: 20px; + text-align: left; + color: black; + } + + button { + align-self: flex-end; + } + } } + // .dialogue-box { + // width: 450; + // height: 300; + // } + button { - background: $lighter-alt-accent; + // background: $lighter-alt-accent; + align-self: center; outline: none; border-radius: 5px; border: 0px; - color: #fcfbf7; - text-transform: uppercase; + // color: #fcfbf7; + text-transform: none; letter-spacing: 2px; font-size: 75%; padding: 10px; @@ -36,12 +74,6 @@ border-radius: 10px; } - button { - width: 100%; - align-self: center; - background: $darker-alt-accent; - } - .delete-button { background: rgb(227, 86, 86); } @@ -55,33 +87,39 @@ } .group-heading { - letter-spacing: .5em; - } - - - .group-body { display: flex; - justify-content: space-between; - max-height: 80%; + align-items: center; + margin-bottom: 25px; - .group-create { - display: flex; - flex-direction: column; - flex-basis: 30%; - margin-left: 5px; + p { + font-size: 20px; + text-align: left; + // margin: 0 0 20px 0; + margin-right: 15px; + color: black; + // width: 60%; + } + } - input { - border-radius: 5px; - border: none; - padding: 4px; - min-width: 100%; - margin: 4px 0 4px 0; - } + .main-container { + display: flex; + flex-direction: column; + .sort-groups { + text-align: left; + margin-left: 5; + cursor: pointer; } - .group-content { - padding-left: 1em; + .group-body { + // display: flex; + justify-content: space-between; + // max-height: 80%; + height: 220; + background-color: #e8e8e8; + // flex-direction: column; + + // padding-left: 1em; padding-right: 1em; justify-content: space-around; text-align: left; @@ -91,17 +129,23 @@ .group-row { display: flex; - position: relative; + // position: relative; margin-bottom: 5px; - min-height: 40px; - border: 1px solid; - border-radius: 10px; + min-height: 30px; + // border: 1px solid; + // border-radius: 10px; align-items: center; .group-name { - position: relative; + // position: relative; max-width: 65%; - left: 10; + // left: 10; + margin: 0 10; + color: black; + } + + .group-info { + cursor: pointer; } button { @@ -112,10 +156,6 @@ } } - ::placeholder { - color: $intermediate-color; - } - input { border-radius: 5px; border: none; @@ -126,11 +166,4 @@ } } - - h1 { - color: $dark-color; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 120%; - } }
\ No newline at end of file diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 23bdd248b..2d8930660 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -12,11 +12,12 @@ import { Utils } from "../../Utils"; import * as RequestPromise from "request-promise"; import Select from 'react-select'; import "./GroupManager.scss"; -import { StrCast } from "../../fields/Types"; +import { StrCast, Cast } from "../../fields/Types"; import GroupMemberView from "./GroupMemberView"; import { setGroups } from "../../fields/util"; +import { DocServer } from "../DocServer"; -library.add(fa.faWindowClose); +library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle); export interface UserOptions { label: string; @@ -33,47 +34,44 @@ export default class GroupManager extends React.Component<{}> { @observable private users: string[] = []; // list of users populated from the database. @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown. @observable currentGroup: Opt<Doc>; // the currently selected group. + @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box. - currentUserGroups: string[] = []; + private currentUserGroups: string[] = []; + @observable private buttonColour: "#979797" | "black" = "#979797"; + @observable private groupSort: "ascending" | "descending" | "none" = "none"; + constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; } - // sets up the list of users componentDidMount() { - this.populateUsers().then(resolved => runInAction(() => this.users = resolved)); - - // this.getAllGroups().forEach(group => { - // const members: string[] = JSON.parse(StrCast(group.members)); - // if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(group); - // }); - DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { - groups?.forEach(group => { - const members: string[] = JSON.parse(StrCast(group.members)); - if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); - }); - }) - .finally(() => setGroups(this.currentUserGroups)); - - // (this.GroupManagerDoc?.data as List<Doc>).forEach(group => { - // Promise.resolve(group).then(resolvedGroup => { - // const members: string[] = JSON.parse(StrCast(resolvedGroup.members)); - // if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(resolvedGroup); - // }); - // }); - + this.populateUsers(); } /** - * Fetches the list of users stored on the database and @returns a list of the emails. + * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList: User[] = JSON.parse(await RequestPromise.get(Utils.prepend("/getUsers"))); - // const currentUserIndex = userList.findIndex(user => user.email === Doc.CurrentUserEmail); - // currentUserIndex !== -1 && userList.splice(currentUserIndex, 1); - return userList.map(user => user.email); + runInAction(() => this.users = []); + const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const raw = JSON.parse(userList) as User[]; + const evaluating = raw.map(async user => { + // const isCandidate = user.email !== Doc.CurrentUserEmail; + // if (isCandidate) { + const userDocument = await DocServer.GetRefField(user.userDocumentId); + if (userDocument instanceof Doc) { + const notificationDoc = await Cast(userDocument.rightSidebarCollection, Doc); + runInAction(() => { + if (notificationDoc instanceof Doc) { + this.users.push(user.email); + } + }); + } + // } + }); + return Promise.all(evaluating); } /** @@ -90,6 +88,15 @@ export default class GroupManager extends React.Component<{}> { open = () => { SelectionManager.DeselectAll(); this.isOpen = true; + this.populateUsers(); + DocListCastAsync(this.GroupManagerDoc?.data).then(groups => { + groups?.forEach(group => { + const members: string[] = JSON.parse(StrCast(group.members)); + if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); + }); + + setGroups(this.currentUserGroups); + }); } /** @@ -99,6 +106,8 @@ export default class GroupManager extends React.Component<{}> { close = () => { this.isOpen = false; this.currentGroup = undefined; + // this.users = []; + this.createGroupModalOpen = false; } /** @@ -277,6 +286,7 @@ export default class GroupManager extends React.Component<{}> { */ @action createGroup = () => { + // this.createGroupModalOpen = true; if (!this.inputRef.current?.value) { alert("Please enter a group name"); return; @@ -288,21 +298,87 @@ export default class GroupManager extends React.Component<{}> { this.createGroupDoc(this.inputRef.current.value, this.selectedUsers?.map(user => user.value)); this.selectedUsers = null; this.inputRef.current.value = ""; + this.buttonColour = "#979797"; + } + + private get groupCreationModal() { + const contents = ( + <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)}> + <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> + </div> + </div> + <input + className="group-input" + ref={this.inputRef} + onKeyDown={this.handleKeyDown} + type="text" + placeholder="Group name" + onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} /> + <Select + isMulti={true} + isSearchable={true} + options={this.options} + onChange={this.handleChange} + placeholder={"Select users"} + value={this.selectedUsers} + closeMenuOnSelect={false} + styles={{ + dropdownIndicator: (base, state) => ({ + ...base, + transition: '0.5s all ease', + transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined + }), + multiValue: (base) => ({ + ...base, + maxWidth: "50%", + + '&:hover': { + maxWidth: "unset" + } + }) + }} + /> + <button onClick={this.createGroup} + style={{ background: this.buttonColour }} + disabled={this.buttonColour === "#979797"} + > + Create + </button> + </div> + ); + + return ( + <MainViewModal + isDisplayed={this.createGroupModalOpen} + interactive={true} + contents={contents} + dialogueBoxStyle={{ width: "70%", height: "70%" }} + closeOnExternalClick={action(() => this.createGroupModalOpen = false)} + /> + ); } /** * A getter that @returns the main interface for the GroupManager. */ private get groupInterface() { + + const sortGroups = (d1: Doc, d2: Doc) => { + const g1 = StrCast(d1.groupName); + const g2 = StrCast(d2.groupName); + + return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; + }; + + let groups = this.getAllGroups(); + groups = this.groupSort === "ascending" ? groups.sort(sortGroups) : this.groupSort === "descending" ? groups.sort(sortGroups).reverse() : groups; + return ( <div className="group-interface"> - {/* <MainViewModal - contents={this.editingInterface} - isDisplayed={this.currentGroup ? true : false} - interactive={true} - dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} - overlayDisplayedOpacity={this.overlayOpacity} - /> */} + {this.groupCreationModal} {this.currentGroup ? <GroupMemberView group={this.currentGroup} @@ -310,43 +386,38 @@ export default class GroupManager extends React.Component<{}> { /> : null} <div className="group-heading"> - <h1>Groups</h1> + <p><b>Manage Groups</b></p> + <button onClick={action(() => this.createGroupModalOpen = true)}> + <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group + </button> <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} /> + <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> </div> </div> - <div className="group-body"> - <div className="group-create"> - <button onClick={this.createGroup}>Create group</button> - <input ref={this.inputRef} onKeyDown={this.handleKeyDown} type="text" placeholder="Group name" /> - <Select - isMulti={true} - isSearchable={true} - options={this.options} - onChange={this.handleChange} - placeholder={"Select users"} - value={this.selectedUsers} - closeMenuOnSelect={false} - styles={{ - dropdownIndicator: (base, state) => ({ - ...base, - transition: '0.5s all ease', - transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : undefined - }) - }} - /> + <div className="main-container"> + <div + className="sort-groups" + onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> + Name {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */} </div> - <div className="group-content"> - {this.getAllGroups().map(group => - <div className="group-row"> - <div className="group-name">{group.groupName}</div> - <button onClick={action(() => this.currentGroup = group)}> - {this.hasEditAccess(group) ? "Edit" : "View"} - </button> + <div className="group-body"> + {groups.map(group => + <div + className="group-row" + key={StrCast(group.groupName)} + > + <div className="group-name" >{group.groupName}</div> + <div className="group-info" onClick={action(() => this.currentGroup = group)}> + <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + </div> + {/* <button onClick={action(() => this.currentGroup = group)}> + {this.hasEditAccess(group) ? "Edit" : "View"} + </button> */} </div> )} </div> </div> + </div> ); } diff --git a/src/client/util/GroupMemberView.scss b/src/client/util/GroupMemberView.scss index 7833c485f..a34e5b989 100644 --- a/src/client/util/GroupMemberView.scss +++ b/src/client/util/GroupMemberView.scss @@ -1,18 +1,23 @@ -@import "../views/globalCssVariables"; +// @import "../views/globalCssVariables"; .editing-interface { - background-color: whitesmoke !important; - color: grey; + // background-color: whitesmoke !important; + // color: grey; width: 100%; height: 100%; + // color: black; + + hr { + margin-top: 20; + } button { - background: $darker-alt-accent; + // background: $darker-alt-accent; outline: none; border-radius: 5px; border: 0px; color: #fcfbf7; - text-transform: uppercase; + text-transform: none; letter-spacing: 2px; font-size: 75%; padding: 10px; @@ -32,13 +37,41 @@ .editing-header { margin-bottom: 5; + .group-title { + font-weight: bold; + font-size: 15; + text-align: center; + border: none; + outline: none; + color: black; + margin-top: -5; + height: 20; + text-overflow: ellipsis; + + &:hover { + text-overflow: visible; + overflow-x: auto; + } + } + + .sort-emails { + float: left; + margin: -18 0 0 5; + cursor: pointer; + } + .group-buttons { display: flex; margin-top: 5; + margin-bottom: 25; .add-member-dropdown { - width: 100%; + width: 65%; margin: 0 5; + + input { + height: 30; + } } } } @@ -46,12 +79,16 @@ .editing-contents { overflow-y: auto; // max-height: 67%; - height: 67%; + height: 65%; width: 100%; + color: black; + margin-top: -15px; .editing-row { display: flex; align-items: center; + margin-bottom: 10px; + position: relative; // border: 1px solid; // border-radius: 10px; @@ -60,6 +97,13 @@ min-width: 65%; word-break: break-all; padding: 0 5; + text-align: left; + } + + .remove-button { + position: absolute; + right: 10; + cursor: pointer; } } } diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index 742caa676..ebe9830ba 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -4,14 +4,14 @@ import { observer } from "mobx-react"; import GroupManager, { UserOptions } from "./GroupManager"; import { library } from "@fortawesome/fontawesome-svg-core"; import { StrCast } from "../../fields/Types"; -import { action } from "mobx"; +import { action, observable } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as fa from '@fortawesome/free-solid-svg-icons'; import Select from "react-select"; import { Doc } from "../../fields/Doc"; import "./GroupMemberView.scss"; -library.add(fa.faWindowClose); +library.add(fa.faTimes, fa.faTrashAlt); interface GroupMemberViewProps { group: Doc; @@ -21,17 +21,29 @@ interface GroupMemberViewProps { @observer export default class GroupMemberView extends React.Component<GroupMemberViewProps> { + @observable private memberSort: "ascending" | "descending" | "none" = "none"; + private get editingInterface() { - const members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : []; + let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : []; + members = this.memberSort === "ascending" ? members.sort() : this.memberSort === "descending" ? members.sort().reverse() : members; + const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : []; + console.log(this.props.group, options); + console.log(GroupManager.Instance.options); + + return (!this.props.group ? null : <div className="editing-interface"> <div className="editing-header"> - <b>{this.props.group.groupName}</b> + <input + className="group-title" + value={StrCast(this.props.group.groupName)} + onChange={e => this.props.group.groupName = e.currentTarget.value} + > + </input> <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}> - <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} /> + <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> </div> - {GroupManager.Instance.hasEditAccess(this.props.group) ? <div className="group-buttons"> <div className="add-member-dropdown"> @@ -54,14 +66,27 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button> </div> : null} + <div + className="sort-emails" + onClick={action(() => this.memberSort = this.memberSort === "ascending" ? "descending" : this.memberSort === "descending" ? "none" : "ascending")}> + Emails {this.memberSort === "ascending" ? "↑" : this.memberSort === "descending" ? "↓" : ""} {/* → */} + </div> </div> + <hr /> <div className="editing-contents"> {members.map(member => ( - <div className="editing-row"> + <div + className="editing-row" + key={member} + > <div className="user-email"> {member} </div> - {GroupManager.Instance.hasEditAccess(this.props.group) ? <button onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> Remove </button> : null} + {GroupManager.Instance.hasEditAccess(this.props.group) ? + <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> + <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} /> + </div> + : null} </div> ))} </div> @@ -75,6 +100,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp isDisplayed={true} interactive={true} contents={this.editingInterface} + dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />; } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index 13c65042c..6d394a38d 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -56,7 +56,7 @@ .settings-type { display: flex; flex-direction: column; - flex-basis: 30%; + flex-basis: 45%; } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index ecc17eec1..d54a39943 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -12,6 +12,8 @@ import { CurrentUserUtils } from "./CurrentUserUtils"; import { Utils } from "../../Utils"; import { Doc } from "../../fields/Doc"; import GroupManager from "./GroupManager"; +import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager"; +import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; library.add(fa.faWindowClose); @@ -84,6 +86,14 @@ export default class SettingsManager extends React.Component<{}> { noviceToggle = (event: any) => { Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode; } + @action + googleAuthorize = (event: any) => { + GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true) + } + @action + hypothesisAuthorize = (event: any) => { + HypothesisAuthenticationManager.Instance.fetchAccessToken(true) + } private get settingsInterface() { return ( @@ -97,7 +107,9 @@ export default class SettingsManager extends React.Component<{}> { <div className="settings-body"> <div className="settings-type"> <button onClick={this.onClick} value="password">reset password</button> - <button onClick={this.noviceToggle} value="data">{`toggle ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button> + <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button> + <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button> + <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button> <button onClick={() => window.location.assign(Utils.prepend("/logout"))}> {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"} </button> diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 2708876a3..572b94ffb 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -1,4 +1,4 @@ -@import "../views/globalCssVariables"; +// @import "../views/globalCssVariables"; .sharing-interface { // display: flex; @@ -6,10 +6,10 @@ width: 600px; height: 360px; - .dialogue-box { - width: 450; - height: 300; - } + // .dialogue-box { + // width: 450; + // height: 300; + // } .overlay { transform: translate(-20px, -20px); @@ -40,6 +40,10 @@ .user-search { width: 90%; + + input { + height: 30; + } } .permissions-select { @@ -60,11 +64,19 @@ .main-container { display: flex; - + margin-top: -10px; .individual-container, .group-container { width: 50%; + display: flex; + flex-direction: column; + + .user-sort { + text-align: left; + margin-left: 10; + cursor: pointer; + } .share-title { margin-top: 20px; @@ -74,7 +86,7 @@ .groups-list, .users-list { font-style: italic; - background: gainsboro; + background: #e8e8e8; // border: 1px solid black; padding-left: 10px; padding-right: 10px; @@ -88,7 +100,7 @@ justify-content: center; // color: red; color: black; - height: 255px; + height: 250px; margin: 0 2; @@ -101,12 +113,12 @@ } button { - background: $darker-alt-accent; + // background: $darker-alt-accent; outline: none; border-radius: 5px; border: 0px; color: #fcfbf7; - text-transform: uppercase; + text-transform: none; letter-spacing: 2px; font-size: 75%; padding: 0 10; @@ -171,6 +183,10 @@ padding: 0; align-items: center; + .group-info { + cursor: pointer; + } + &:hover .padding { white-space: unset; } @@ -181,13 +197,13 @@ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - max-width: 48%; + max-width: 40%; } .permissions-dropdown { border: none; height: 25; - background: gainsboro; + background-color: #e8e8e8; } .edit-actions { diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 050ff0c4e..be86b183f 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -22,7 +22,7 @@ import Select from "react-select"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { List } from "../../fields/List"; -library.add(fa.faCopy); +library.add(fa.faCopy, fa.faTimes); export interface User { email: string; @@ -75,6 +75,9 @@ export default class SharingManager extends React.Component<{}> { @observable private overlayOpacity = 0.4; @observable private selectedUsers: UserOptions[] | null = null; @observable private permissions: SharingPermissions = SharingPermissions.Edit; + @observable private individualSort: "ascending" | "descending" | "none" = "none"; + @observable private groupSort: "ascending" | "descending" | "none" = "none"; + // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; @@ -140,7 +143,7 @@ export default class SharingManager extends React.Component<{}> { setInternalGroupSharing = (group: Doc, permission: string) => { const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); + const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); const target = this.targetDoc!; const ACL = `ACL-${StrCast(group.groupName)}`; @@ -152,8 +155,6 @@ export default class SharingManager extends React.Component<{}> { // 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(res => console.log(res)); DocListCastAsync(notificationDoc[storage]).then(resolved => { if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target); else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); @@ -161,8 +162,8 @@ export default class SharingManager extends React.Component<{}> { }); } - shareWithAddedMember = (group: Doc, email: string) => { - const user: ValidatedUser = this.users.find(user => user.user.email === email)!; + shareWithAddedMember = (group: Doc, emailId: string) => { + const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { DocListCastAsync(group.docsShared).then(docsShared => { @@ -174,8 +175,8 @@ export default class SharingManager extends React.Component<{}> { } } - removeMember = (group: Doc, email: string) => { - const user: ValidatedUser = this.users.find(user => user.user.email === email)!; + removeMember = (group: Doc, emailId: string) => { + const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { DocListCastAsync(group.docsShared).then(docsShared => { @@ -195,9 +196,9 @@ export default class SharingManager extends React.Component<{}> { doc[ACL] = "Not Shared"; const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email)); + const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc)); + users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc)); }); }); @@ -331,24 +332,6 @@ export default class SharingManager extends React.Component<{}> { ); } - private computePermissions = (userKey: string) => { - // const sharingDoc = this.sharingDoc; - // if (!sharingDoc) { - // return SharingPermissions.None; - // } - // const metadata = sharingDoc[userKey] as Doc | string; - - if (!this.targetDoc) return SharingPermissions.None; - - const ACL = `ACL-${userKey}`; - const permission = StrCast(this.targetDoc[ACL]); - - // if (!metadata) { - // return SharingPermissions.None; - // } - return StrCast(this.targetDoc[ACL], SharingPermissions.None); - } - @action handleUsersChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; @@ -372,29 +355,47 @@ export default class SharingManager extends React.Component<{}> { this.selectedUsers = null; } + sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => { + const { email: e1 } = u1.user; + const { email: e2 } = u2.user; + return e1 < e2 ? -1 : e1 === e2 ? 0 : 1; + } + + sortGroups = (group1: Doc, group2: Doc) => { + const g1 = StrCast(group1.groupName); + const g2 = StrCast(group2.groupName); + return g1 < g2 ? -1 : g1 === g2 ? 0 : 1; + } + private get sharingInterface() { - const existOtherUsers = this.users.length > 0; - const existGroups = GroupManager.Instance?.getAllGroups().length > 0; - // const manager = this.sharingDoc!; + const groupList = GroupManager.Instance?.getAllGroups() || []; + + const sortedUsers = this.users.sort(this.sortUsers) + .map(({ user: { email } }) => ({ label: email, value: "!indType/" + email })); + const sortedGroups = groupList.sort(this.sortGroups) + .map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) })); const options: GroupOptions[] = GroupManager.Instance ? [ { label: 'Individuals', - options: GroupManager.Instance.options.map(({ label, value }) => ({ label, value: "!indType/" + value })) + options: sortedUsers }, { label: 'Groups', - options: GroupManager.Instance.getAllGroups().map(({ groupName }) => ({ label: StrCast(groupName), value: "!groupType/" + StrCast(groupName) })) + options: sortedGroups } ] : []; - const userListContents: (JSX.Element | null)[] = this.users.map(({ user, notificationDoc }) => { // can't use async here + const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users; + const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList; + + const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { // can't use async here const userKey = user.email.replace('.', '_'); // const userKey = user.userDocumentId; - const permissions = this.computePermissions(userKey); + const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`], SharingPermissions.None); // const color = ColorMapping.get(permissions); // console.log(manager); @@ -402,7 +403,7 @@ export default class SharingManager extends React.Component<{}> { // const usersShared = StrCast(metadata?.usersShared, ""); // console.log(usersShared) - return permissions === SharingPermissions.None ? null : ( + return permissions === SharingPermissions.None || user.email === this.targetDoc?.author ? null : ( <div key={userKey} className={"container"} @@ -423,9 +424,24 @@ export default class SharingManager extends React.Component<{}> { ); }); + userListContents.unshift( + ( + <div + key={"owner"} + className={"container"} + > + <span className={"padding"}>{this.targetDoc?.author}</span> + <div className="edit-actions"> + <div className={"permissions-dropdown"}> + Owner + </div> + </div> + </div> + ) + ); - const groupListContents = GroupManager.Instance?.getAllGroups().map(group => { - const permissions = this.computePermissions(StrCast(group.groupName)); + const groupListContents = groups.map(group => { + const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`], SharingPermissions.None); // const color = ColorMapping.get(permissions); return permissions === SharingPermissions.None ? null : ( @@ -433,7 +449,10 @@ export default class SharingManager extends React.Component<{}> { key={StrCast(group.groupName)} className={"container"} > - <span className={"padding"}>{group.groupName}</span> + <div className={"padding"}>{group.groupName}</div> + <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}> + <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + </div> <div className="edit-actions"> <select className={"permissions-dropdown"} @@ -443,14 +462,13 @@ export default class SharingManager extends React.Component<{}> { > {this.sharingOptions} </select> - <button onClick={action(() => GroupManager.Instance.currentGroup = group)}>Edit</button> </div> </div> ); }); - const displayUserList = userListContents?.every(user => user === null); - const displayGroupList = groupListContents?.every(group => group === null); + const displayUserList = !userListContents?.every(user => user === null); + const displayGroupList = !groupListContents?.every(group => group === null); return ( <div className={"sharing-interface"}> @@ -493,7 +511,7 @@ export default class SharingManager extends React.Component<{}> { <div className="sharing-contents"> <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p> <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} /> + <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> </div> {this.targetDoc?.author !== Doc.CurrentUserEmail ? null : @@ -517,9 +535,14 @@ export default class SharingManager extends React.Component<{}> { } <div className="main-container"> <div className={"individual-container"}> - <div className={"users-list"} style={{ display: displayUserList ? "flex" : "block" }}>{/*200*/} + <div + className="user-sort" + onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> + Individuals {this.individualSort === "ascending" ? "↑" : this.individualSort === "descending" ? "↓" : ""} {/* → */} + </div> + <div className={"users-list"} style={{ display: !displayUserList ? "flex" : "block" }}>{/*200*/} { - displayUserList ? + !displayUserList ? <div className={"none"} > @@ -531,9 +554,15 @@ export default class SharingManager extends React.Component<{}> { </div> </div> <div className={"group-container"}> - <div className={"groups-list"} style={{ display: displayGroupList ? "flex" : "block" }}>{/*200*/} + <div + className="user-sort" + onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> + Groups {this.groupSort === "ascending" ? "↑" : this.groupSort === "descending" ? "↓" : ""} {/* → */} + + </div> + <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/} { - displayGroupList ? + !displayGroupList ? <div className={"none"} > diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 99840047f..68ebd8e14 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -19,6 +19,7 @@ export interface OriginalMenuProps { export interface SubmenuProps { description: string; subitems: ContextMenuProps[]; + noexpand?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; } @@ -96,6 +97,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}> {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)} </div>; + if (!("noexpand" in this.props)) { + return <> + {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)} + </>; + } return ( <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }} onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}> diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a3d24b3b6..c188618f4 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -11,7 +11,6 @@ import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs, DocUtils } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; -import { UndoManager } from "../util/UndoManager"; import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView'; import { ParentDocSelector } from './collections/ParentDocumentSelector'; import './collections/ParentDocumentSelector.scss'; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d7324e1a6..444d2fe50 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -17,14 +17,11 @@ import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; import { DocumentView } from "./nodes/DocumentView"; import React = require("react"); -import { Id, Copy, Update } from '../../fields/FieldSymbols'; import e = require('express'); import { CollectionDockingView } from './collections/CollectionDockingView'; import { SnappingManager } from '../util/SnappingManager'; import { HtmlField } from '../../fields/HtmlField'; import { InkData, InkField, InkTool } from "../../fields/InkField"; -import { update } from 'serializr'; -import { Transform } from "../util/Transform"; import { Tooltip } from '@material-ui/core'; library.add(faCaretUp); @@ -579,7 +576,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <FontAwesomeIcon size="lg" icon="cog" /> </div></Tooltip>} <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} > - <span style={{ width: "100%", display: "inline-block" }}>{`${this.selectionTitle}`}</span> + <span style={{ width: "100%", display: "inline-block", cursor: "move" }}>{`${this.selectionTitle}`}</span> </div> </>; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a48b7b673..2f34cbc05 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -608,8 +608,7 @@ export default class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - if (InkOptionsMenu.Instance._double === "") { - + if (!InkOptionsMenu.Instance._keepMode) { this.InkShape = ""; } } @@ -640,7 +639,7 @@ export default class GestureOverlay extends Touchable { this._points = []; } //get out of ink mode after each stroke= - if (InkOptionsMenu.Instance._double === "") { + if (!InkOptionsMenu.Instance._keepMode) { Doc.SetSelectedTool(InkTool.None); InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum; SetActiveArrowStart("none"); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6148e66dc..4350f8da4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,4 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; + import { faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, @@ -6,7 +7,8 @@ import { faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, - faHeading, faRulerCombined, faFillDrip, faUnlink + faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, + faPaintRoller, faBars, faBrush, faShapes, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -147,7 +149,8 @@ export class MainView extends React.Component { faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, - faHeading, faRulerCombined, faFillDrip, faUnlink); + faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, + faPaintRoller, faBars, faBrush, faShapes, faEllipsisH); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 0b73a6ad7..249715511 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -21,7 +21,7 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps> const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; return !p.isDisplayed ? (null) : ( - <div style={{ pointerEvents: p.isDisplayed ? p.interactive ? "all" : "none" : "none" }}> + <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}> <div className={"dialogue-box"} style={{ diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 37d8dd23b..5c3a8185c 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -124,7 +124,9 @@ export class OverlayView extends React.Component { ele = <div key={Utils.GenerateGuid()} className="overlayView-wrapperDiv" style={{ transform: `translate(${options.x}px, ${options.y}px)`, width: options.width, - height: options.height + height: options.height, + top: 0, + left: 0 }}>{ele}</div>; this._elements.push(ele); return remove; diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 1344b70f4..8f1cd5311 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -108,17 +108,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume }, 1500); } - onContextMenu = (e: React.MouseEvent): void => { - // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped()) { - ContextMenu.Instance.addItem({ - description: "Make Hero Image", event: () => { - const index = NumCast(this.layoutDoc._itemIndex); - (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField); - }, icon: "plus" - }); - } - } _downX = 0; _downY = 0; onPointerDown = (e: React.PointerEvent) => { @@ -184,7 +173,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume const index = NumCast(this.layoutDoc._itemIndex); const translateX = (1 - index) / this.childLayoutPairs.length * 100; - return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}> + return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}> <div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}> {this.content} </div> diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index bd0e4fc9a..b3fecfb91 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -83,18 +83,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) </>; } - - onContextMenu = (e: React.MouseEvent): void => { - // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped()) { - ContextMenu.Instance?.addItem({ - description: "Make Hero Image", event: () => { - const index = NumCast(this.layoutDoc._itemIndex); - (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField); - }, icon: "plus" - }); - } - } _downX = 0; _downY = 0; onPointerDown = (e: React.PointerEvent) => { @@ -119,7 +107,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) } render() { - return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}> + return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}> {this.content} {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} </div>; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index ce6872695..3794088d4 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import { basename } from 'path'; import CursorField from "../../../fields/CursorField"; import { Doc, Opt, Field } from "../../../fields/Doc"; @@ -19,6 +19,7 @@ import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; import React = require("react"); import * as rp from 'request-promise'; +import ReactLoading from 'react-loading'; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc | Doc[]) => boolean; @@ -377,13 +378,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: }); } } + this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY); + batch.end(); + } + slowLoadDocuments = async (files: File[], options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number) => { + runInAction(() => CollectionSubViewLoader.Waiting = "block"); + const disposer = OverlayView.Instance.addElement( + <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 }); generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options)); if (generatedDocuments.length) { const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d)); if (set) { - this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); + UndoManager.RunInBatch(() => this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!), "drop"); } else { - generatedDocuments.forEach(this.addDocument); + UndoManager.RunInBatch(() => generatedDocuments.forEach(this.addDocument), "drop"); } completed?.(); } else { @@ -391,13 +399,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 })); } } - batch.end(); + disposer(); } } return CollectionSubView; } +export class CollectionSubViewLoader { + @observable public static Waiting = "none"; +} + import { DragManager, dropActionType } from "../../util/DragManager"; import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -405,4 +417,5 @@ import { DocumentType } from "../../documents/DocumentTypes"; import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; import { CollectionView } from "./CollectionView"; import { SelectionManager } from "../../util/SelectionManager"; +import { OverlayView } from "../OverlayView"; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 26c41f524..dbd39d8df 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -780,7 +780,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll onClicks.push({ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 47b6dc058..682061b34 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -275,7 +275,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) }); } addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" }); - !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" }); + !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" }); } onContextMenu = (e: React.MouseEvent): void => { @@ -297,7 +297,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus if (this.props.Document.childClickedOpenTemplateView instanceof Doc) { layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" }); } - layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); + !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" }); @@ -317,7 +317,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus icon: "edit", event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), })); - !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); if (!Doc.UserDoc().noviceMode) { const more = cm.findByDescription("More..."); diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx index 56ba9e96a..9d9ce7f36 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx @@ -11,6 +11,7 @@ import { DocumentType } from "../../../documents/DocumentTypes"; import { SelectionManager } from "../../../util/SelectionManager"; import AntimodeMenu from "../../AntimodeMenu"; import "./FormatShapePane.scss"; +import { undoBatch } from "../../../util/UndoManager"; @observer export default class FormatShapePane extends AntimodeMenu { @@ -118,6 +119,7 @@ export default class FormatShapePane extends AntimodeMenu { } } + @undoBatch @action rotate = (degrees: number) => { this.selectedInk?.forEach(action(inkView => { @@ -160,7 +162,7 @@ export default class FormatShapePane extends AntimodeMenu { colorPicker(setter: (color: string) => {}) { return <div className="btn-group-palette" key="colorpicker" > {this._palette.map(color => - <button className="antimodeMenu-button" key={color} onPointerDown={action(() => setter(color))} style={{ zIndex: 1001, position: "relative" }}> + <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}> <div className="color-previewII" style={{ backgroundColor: color }} /> </button>)} </div>; @@ -171,11 +173,11 @@ export default class FormatShapePane extends AntimodeMenu { type="text" value={value} onChange={e => setter(e.target.value)} autoFocus /> - <button className="antiMenu-Buttonup" key="up" onPointerDown={action(() => this.upDownButtons("up", key))}> + <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}> ˄ </button> <br /> - <button className="antiMenu-Buttonup" key="down" onPointerDown={action(() => this.upDownButtons("down", key))} style={{ marginTop: -8 }}> + <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}> ˅ </button> </>; @@ -183,7 +185,7 @@ export default class FormatShapePane extends AntimodeMenu { colorButton(value: string, setter: () => {}) { return <> - <button className="antimodeMenu-button" key="fill" onPointerDown={action(e => setter())} style={{ position: "absolute", right: 80 }}> + <button className="antimodeMenu-button" key="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}> <FontAwesomeIcon icon="fill-drip" size="lg" /> <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} /> </button> @@ -206,10 +208,10 @@ export default class FormatShapePane extends AntimodeMenu { @computed get propertyGroupItems() { const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={action(() => this.unFilled = true)} /> + <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} /> No Fill <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={action(() => this.solidFil = true)} /> + <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} /> Solid Fill <br /> <br /> {this.solidFil ? "Color" : ""} @@ -218,22 +220,22 @@ export default class FormatShapePane extends AntimodeMenu { </div>; const markers = <> - <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={action(() => this.markHead = this.markHead ? "" : "arrow")} /> + <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} /> Arrow Head <br /> - <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={action(() => this.markTail = this.markTail ? "" : "arrow")} /> + <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} /> Arrow End <br /> </>; const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={action(() => this.unStrokd = true)} /> + <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} /> No Line <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={action(() => this.solidStk = true)} /> + <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} /> Solid Line <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={action(() => this.dashdStk = "2")} /> + <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} /> Dash Line <br /> <br /> @@ -253,7 +255,7 @@ export default class FormatShapePane extends AntimodeMenu { <br /> <br /> Width {this.widInput} <br /> <br /> - <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={action(() => this._lock = !this._lock)} /> + <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} /> Lock Ratio <br /> <br /> Rotation {this.rotInput} diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx index 15707ad9e..23e1c194a 100644 --- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx @@ -3,7 +3,7 @@ import AntimodeMenu from "../../AntimodeMenu"; import { observer } from "mobx-react"; import { observable, action, computed } from "mobx"; import "./InkOptionsMenu.scss"; -import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke"; +import { ActiveInkColor, ActiveFillColor, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke"; import { Scripting } from "../../../util/Scripting"; import { InkTool } from "../../../../fields/InkField"; import { ColorState } from "react-color"; @@ -14,30 +14,16 @@ import { SelectionManager } from "../../../util/SelectionManager"; import { DocumentView } from "../../../views/nodes/DocumentView"; import { Document } from "../../../../fields/documentSchemas"; import { DocumentType } from "../../../documents/DocumentTypes"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons"; -import { Cast, StrCast, BoolCast } from "../../../../fields/Types"; +import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; +import { BoolCast } from "../../../../fields/Types"; import FormatShapePane from "./FormatShapePane"; -library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve); - - - @observer export default class InkOptionsMenu extends AntimodeMenu { static Instance: InkOptionsMenu; private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""]; private _width = ["1", "5", "10", "100"]; - // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"]; - // private _icons = ["O", "∆", "ロ", "➜", "-"]; - // private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",]; - // private _icons = ["O", "∆", "ロ", "⎯⎯⎯", "✖︎", " "]; - //arrowStart and arrowEnd must match and defs must exist in Inking Stroke - // private _arrowStart = ["arrowStart", "arrowStart", "dot", "dot", "none"]; - // private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"]; - // private _arrowIcons = ["→", "↔︎", "•", "••", " "]; private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"]; private _head = ["", "", "arrow", "", "", "arrow", "", "", ""]; private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""]; @@ -46,56 +32,25 @@ export default class InkOptionsMenu extends AntimodeMenu { @observable _shapesNum = this._shape.length; @observable _selected = this._shapesNum; - @observable private collapsed: boolean = false; - @observable _double = ""; + @observable _collapsed = false; + @observable _keepMode = false; @observable _colorBtn = false; @observable _widthBtn = false; @observable _fillBtn = false; - // @observable _arrowBtn = false; - // @observable _dashBtn = false; - // @observable _shapeBtn = false; - - constructor(props: Readonly<{}>) { super(props); InkOptionsMenu.Instance = this; this._canFade = false; // don't let the inking menu fade away this.Pinned = BoolCast(Doc.UserDoc()["menuInkOptions-pinned"]); - } @action toggleMenuPin = (e: React.MouseEvent) => { Doc.UserDoc()["menuInkOptions-pinned"] = this.Pinned = !this.Pinned; - if (!this.Pinned) { - // this.fadeOut(true); - } - } - - @action - protected toggleCollapse = (e: React.MouseEvent) => { - this.collapsed = !this.collapsed; - setTimeout(() => { - const x = Math.min(this._left, window.innerWidth - InkOptionsMenu.Instance.width); - InkOptionsMenu.Instance.jumpTo(x, this._top, true); - }, 0); - } - - - - - getColors = () => { - return this._palette; } - // @action - // changeArrow = (arrowStart: string, arrowEnd: string) => { - // SetActiveArrowStart(arrowStart); - // SetActiveArrowEnd(arrowEnd); - // } - @action changeColor = (color: string, type: string) => { const col: ColorState = { @@ -115,313 +70,117 @@ export default class InkOptionsMenu extends AntimodeMenu { const doc = Document(element.rootDoc); if (doc.type === DocumentType.INK) { switch (field) { - case "width": - doc.strokeWidth = Number(value); - break; - case "color": - doc.color = String(value); - break; - case "fill": - doc.fillColor = String(value); - break; - case "bezier": - // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300; - break; - case "dash": - doc.strokeDash = Number(value); - default: - break; + case "width": doc.strokeWidth = Number(value); break; + case "color": doc.color = String(value); break; + case "fill": doc.fillColor = String(value); break; + case "dash": doc.strokeDash = value; } } })); } - - @action - changeBezier = (e: React.PointerEvent): void => { - SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : ""); - this.editProperties(0, "bezier"); - } - @action - changeDash = (e: React.PointerEvent): void => { - SetActiveDash(ActiveDash() === "0" ? "2" : "0"); - this.editProperties(ActiveDash(), "strokeDash"); - } - @computed get drawButtons() { - const drawButtons = <div className="btn-draw" key="draw"> - {this._draw.map((icon, i) => { - return <button - className="antimodeMenu-button" - key={icon} - onPointerDown={action(() => { - this._double = ""; - - if (this._selected !== i) { - this._selected = i; - Doc.SetSelectedTool(InkTool.Pen); - SetActiveArrowStart(this._head[i]); - SetActiveArrowEnd(this._end[i]); - SetActiveBezierApprox("300"); - - GestureOverlay.Instance.InkShape = this._shape[i]; - } else { - this._selected = this._shapesNum; - Doc.SetSelectedTool(InkTool.None); - SetActiveArrowStart(""); - SetActiveArrowEnd(""); - GestureOverlay.Instance.InkShape = ""; - SetActiveBezierApprox("0"); - - } - console.log(this._selected); - - })} - onDoubleClick={action(() => { - console.log("double"); - this._double = this._draw[i]; - if (this._selected !== i) { - this._selected = i; - Doc.SetSelectedTool(InkTool.Pen); - SetActiveArrowStart(this._head[i]); - SetActiveArrowEnd(this._end[i]); - SetActiveBezierApprox("300"); - - GestureOverlay.Instance.InkShape = this._shape[i]; - } else { - this._selected = this._shapesNum; - Doc.SetSelectedTool(InkTool.None); - SetActiveArrowStart(""); - SetActiveArrowEnd(""); - GestureOverlay.Instance.InkShape = ""; - SetActiveBezierApprox("0"); - - } - - - - })} + const func = action((i: number, keep: boolean) => { + this._keepMode = keep; + if (this._selected !== i) { + this._selected = i; + Doc.SetSelectedTool(InkTool.Pen); + SetActiveArrowStart(this._head[i]); + SetActiveArrowEnd(this._end[i]); + SetActiveBezierApprox("300"); + + GestureOverlay.Instance.InkShape = this._shape[i]; + } else { + this._selected = this._shapesNum; + Doc.SetSelectedTool(InkTool.None); + SetActiveArrowStart(""); + SetActiveArrowEnd(""); + GestureOverlay.Instance.InkShape = ""; + SetActiveBezierApprox("0"); + } + }); + return <div className="btn-draw" key="draw"> + {this._draw.map((icon, i) => + <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}> {this._draw[i]} - </button>; - })}</div>; - return drawButtons; + </button>)} + </div>; } - // @computed get arrowPicker() { - // var currIcon; - // for (var i = 0; i < this._arrowStart.length; i++) { - // if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) { - // currIcon = this._arrowIcons[i]; - // if (this._arrowIcons[i] === " ") { - // currIcon = "➤"; - // } - // } - // } - // var arrowPicker = <button - // className="antimodeMenu-button" - // key="arrow" - // onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)} - // style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> - // {currIcon} - // </button>; - // if (this._arrowBtn) { - // arrowPicker = <div className="btn2-group" key="arrows"> - // {arrowPicker} - // {this._arrowStart.map((arrowStart, i) => { - // return <button - // className="antimodeMenu-button" - // key={arrowStart} - // onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })} - // style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> - // {this._arrowIcons[i]} - // </button>; - // })} - // </div>; - // } - // return arrowPicker; - // } + toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => { + return <button className="antimodeMenu-button" key={key} title={key} + onPointerDown={action(e => setter())} + style={{ backgroundColor: value ? "121212" : "" }}> + <FontAwesomeIcon icon={icon} size="lg" /> + {ele} + </button>; + } @computed get widthPicker() { - var widthPicker = <button - className="antimodeMenu-button" - key="width" - onPointerDown={action(e => this._widthBtn = !this._widthBtn)} - style={{ backgroundColor: this._widthBtn ? "121212" : "" }}> - <FontAwesomeIcon icon="bars" size="lg" /> - </button>; - if (this._widthBtn) { - widthPicker = <div className="btn2-group" key="width"> + var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null); + return !this._widthBtn ? widthPicker : + <div className="btn2-group" key="width"> {widthPicker} - {this._width.map(wid => { - return <button - className="antimodeMenu-button" - key={wid} + {this._width.map(wid => + <button className="antimodeMenu-button" key={wid} onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })} style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}> {wid} - </button>; - })} + </button>)} </div>; - } - return widthPicker; } - - @computed get colorPicker() { - var colorPicker = <button - className="antimodeMenu-button" - key="color" - title="colorChanger" - onPointerDown={action(e => this._colorBtn = !this._colorBtn)} - style={{ backgroundColor: this._colorBtn ? "121212" : "" }}> - <FontAwesomeIcon icon="pen-nib" size="lg" /> - <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div> - - </button>; - if (this._colorBtn) { - colorPicker = <div className="btn-group" key="color"> + var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib", + <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />); + return !this._colorBtn ? colorPicker : + <div className="btn-group" key="color"> {colorPicker} - {this._palette.map(color => { - return <button - className="antimodeMenu-button" - key={color} + {this._palette.map(color => + <button className="antimodeMenu-button" key={color} onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })} style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}> {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */} - <div className="color-previewII" style={{ backgroundColor: color }}></div> - </button>; - })} + <div className="color-previewII" style={{ backgroundColor: color }} /> + </button>)} </div>; - } - return colorPicker; - } - @computed get formatPane() { - return <button className="antimodeMenu-button" key="format" - title="toggle foramatting pane" - onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)} - style={{ backgroundColor: this._fillBtn ? "121212" : "" }}> - <FontAwesomeIcon icon="chevron-right" size="lg" /> - </button>; } - @computed get fillPicker() { - var fillPicker = <button - className="antimodeMenu-button" - key="fill" - title="fillChanger" - onPointerDown={action(e => this._fillBtn = !this._fillBtn)} - style={{ backgroundColor: this._fillBtn ? "121212" : "" }}> - <FontAwesomeIcon icon="fill-drip" size="lg" /> - <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div> - </button>; - if (this._fillBtn) { - fillPicker = <div className="btn-group" key="fill" > + var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip", + <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />); + return !this._fillBtn ? fillPicker : + <div className="btn-group" key="fill" > {fillPicker} - {this._palette.map(color => { - return <button - className="antimodeMenu-button" - key={color} + {this._palette.map(color => + <button className="antimodeMenu-button" key={color} onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })} style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}> <div className="color-previewII" style={{ backgroundColor: color }}></div> - </button>; - })} + </button>)} </div>; - } - return fillPicker; } - // @computed get shapePicker() { - // var currIcon; - // if (GestureOverlay.Instance.InkShape === "") { - // currIcon = <FontAwesomeIcon icon="shapes" size="lg" />; - // } else { - // for (var i = 0; i < this._icons.length; i++) { - // if (GestureOverlay.Instance.InkShape === this._buttons[i]) { - // currIcon = this._icons[i]; - // } - // } - // } - // var shapePicker = <button - // className="antimodeMenu-button" - // key="shape" - // onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)} - // style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}> - // {currIcon} - // </button>; - // if (this._shapeBtn) { - // shapePicker = <div className="btn2-group" key="shape"> - // {shapePicker} - // {this._buttons.map((btn, i) => { - // var ttl = btn; - // if (btn === "") { - // ttl = "no shape"; - // } - // if (btn === "noRec") { - // ttl = "disable shape recognition"; - // } - // return <button - // className="antimodeMenu-button" - // title={`Draw ${btn}`} - // key={ttl} - // onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })} - // style={{ backgroundColor: this._shapeBtn ? "121212" : "", fontSize: "20" }}> - // {this._icons[i]} - // </button>; - // })} - // </div>; - // } - // return shapePicker; - // } - - @computed get bezierButton() { - return <button - className="antimodeMenu-button" - title="Bezier changer" - key="bezier" - onPointerDown={e => this.changeBezier(e)} - style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}> - <FontAwesomeIcon icon="bezier-curve" size="lg" /> - - </button>; - } - - @computed get dashButton() { - return <button - className="antimodeMenu-button" - title="dash changer" - key="dash" - onPointerDown={e => this.changeDash(e)} - style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}> - <FontAwesomeIcon icon="ellipsis-h" size="lg" /> - + @computed get formatPane() { + return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane" + onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)} + style={{ backgroundColor: this._fillBtn ? "121212" : "" }}> + <FontAwesomeIcon icon="chevron-right" size="lg" /> </button>; } render() { - const buttons = [ - // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}> - // <FontAwesomeIcon icon="arrows-alt" size="lg" /> - // </button>, - // this.shapePicker, - // this.bezierButton, + return this.getElement([ this.widthPicker, this.colorPicker, this.fillPicker, this.drawButtons, this.formatPane, - // this.arrowPicker, - // this.dashButton, - <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}> + <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this._collapsed ? "none" : undefined }}> <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> </button> - ]; - - // return this.getElement(buttons); - return this.getElement(buttons); + ]); } } Scripting.addGlobal(function activatePen(penBtn: any) { diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 2015ca930..188b88c41 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -1,7 +1,7 @@ import { action, computed, Lambda, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc, Opt } from '../../../../fields/Doc'; +import { Doc, Opt, WidthSym } from '../../../../fields/Doc'; import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; import { makeInterface } from '../../../../fields/Schema'; @@ -248,8 +248,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { */ onContextMenu = () => { const displayOptionsMenu: ContextMenuProps[] = []; - displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" }); - displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" }); + displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" }); ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 016893efd..0015e4bc7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -770,6 +770,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); + optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); @@ -783,14 +784,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" }); onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" }); onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" }); - !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); const funcs: ContextMenuProps[] = []; if (this.layoutDoc.onDragStart) { funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined }); - cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" }); + cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" }); } const more = cm.findByDescription("More..."); @@ -817,9 +818,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); }); + moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); } - moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" }); moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); @@ -827,18 +827,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const help = cm.findByDescription("Help..."); const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : []; - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); - helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); - cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" }); + //!Doc.UserDoc().novice && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); + !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); + cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); const existingAcls = cm.findByDescription("Privacy..."); const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : []; - aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" }); - aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" }); - aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" }); - aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" }); - aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" }); - aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" }); + aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" }); + aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" }); + aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" }); !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" }); cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" }); diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 68b00a5be..fe0f067ad 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -18,6 +18,7 @@ text-align: center; font-size: 8px; margin-top:4px; + letter-spacing: normal; } svg { diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 5e8dd2497..86e9a4527 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined }}> <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" /> - {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>} + {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 6)} </div>} </button>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d16aa528c..4eba21eab 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -157,29 +157,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); - funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); - funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); - // funcs.push({ - // description: "Reset Native Dimensions", event: action(async () => { - // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); - // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); - // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { - // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; - // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); - // } else { - // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); - // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; - // } - // }), icon: "expand-arrows-alt" - // }); - - const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); - const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; - modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); - modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); - //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); - !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" }); + if (!Doc.UserDoc().noviceMode) { + funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); + funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); + // funcs.push({ + // description: "Reset Native Dimensions", event: action(async () => { + // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH; + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight(); + // } else { + // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth(); + // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW; + // } + // }), icon: "expand-arrows-alt" + // }); + + const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); + const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; + modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); + modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); + !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + } ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 836ef4149..0dfbdc5cf 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument }, icon: "trash" }); - ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" }); + ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "asterisk" }); } @undoBatch diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 01fbcb020..39eed3621 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -479,7 +479,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); }); changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); - appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); const uicontrols: ContextMenuProps[] = []; uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); @@ -489,10 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" }); - appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" }); this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ + + !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + + const funcs: ContextMenuProps[] = []; + + const highlighting: ContextMenuProps[] = []; + ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + highlighting.push({ + description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + e.stopPropagation(); + if (FormattedTextBox._highlights.indexOf(option) === -1) { + FormattedTextBox._highlights.push(option); + } else { + FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); + } + this.updateHighlights(); + }, icon: "expand-arrows-alt" + })); + funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); + funcs.push({ description: "Convert to be a template style", event: () => { if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); @@ -517,29 +536,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - const funcs: ContextMenuProps[] = []; - - //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - - const highlighting: ContextMenuProps[] = []; - ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => - highlighting.push({ - description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { - e.stopPropagation(); - if (FormattedTextBox._highlights.indexOf(option) === -1) { - FormattedTextBox._highlights.push(option); - } else { - FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); - } - this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); - funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); - + funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); this._downX = this._downY = Number.NaN; } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index a70f879ff..8da1f99b5 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -363,7 +363,7 @@ export default class RichTextMenu extends AntimodeMenu { ); } - createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { + createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>; @@ -380,24 +380,24 @@ export default class RichTextMenu extends AntimodeMenu { if (e.target.value === label && mark) { if (!self.TextView.props.isSelected()) { switch (mark.type) { - case schema.marks.pFontFamily: Doc.UserDoc().fontFamily = mark.attrs.family; break; - case schema.marks.pFontSize: Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"; break; + case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break; + case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break; } } else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); } }); } - return <select onChange={onChange} defaultValue={activeOption} key={key}>{items}</select>; + return <select onChange={onChange} value={activeOption} key={key}>{items}</select>; } createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element { const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>"; const items = options.map(({ title, label, hidden, style }) => { if (hidden) { - return <option value={label} selected={label === activeOption} title={title} key={label} style={style ? style : {}} hidden>{label}</option>; + return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>; } - return <option value={label} selected={label === activeOption} title={title} key={label} style={style ? style : {}}>{label}</option>; + return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>; }); const self = this; @@ -412,7 +412,7 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return <select defaultValue={activeOption} onChange={e => onChange(e.target.value)} key={key}>{items}</select>; + return <select value={activeOption} onChange={e => onChange(e.target.value)} key={key}>{items}</select>; } changeFontSize = (mark: Mark, view: EditorView) => { @@ -932,8 +932,8 @@ export default class RichTextMenu extends AntimodeMenu { {this.collapsed ? this.getDragger() : (null)} <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}> <div className="richTextMenu-divider" key="divider 3" />, - {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), - this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)), + this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)), <div className="richTextMenu-divider" key="divider 4" />, this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes", action((val: string) => this.activeListType = val)), this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), diff --git a/src/fields/util.ts b/src/fields/util.ts index be7736413..ea4966861 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -162,9 +162,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number) } return aclPresent ? effectiveAcl : AclEdit; } - else { - return AclEdit; - } + return AclEdit; } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index d55c2e198..f63a35e43 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -180,7 +180,14 @@ export namespace WebSocket { function barReceived(socket: SocketIO.Socket, userEmail: string) { clients[userEmail] = new Client(userEmail.toString()); - console.log(green(`user ${userEmail} has connected to the web socket`)); + const currentdate = new Date(); + const datetime = currentdate.getDate() + "/" + + (currentdate.getMonth() + 1) + "/" + + currentdate.getFullYear() + " @ " + + currentdate.getHours() + ":" + + currentdate.getMinutes() + ":" + + currentdate.getSeconds(); + console.log(green(`user ${userEmail} has connected to the web socket at: ${datetime}`)); socketMap.set(socket, userEmail); } |