From fff8e7ad41587e8a598fc03bd0dbac40069332b5 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 9 Sep 2017 22:12:59 -0700 Subject: [PATCH] Dropdown menu refactors --- .../components/DropdownMenu/DropdownMenu.js | 21 +++- .../DropdownMenu/DropdownMenuItem.js | 1 + frontend/components/Layout/Layout.js | 104 +++--------------- .../components/Layout/components/Modals.js | 58 ++++++++++ frontend/components/Scrollable/Scrollable.js | 1 - frontend/menus/AccountMenu.js | 53 +++++++++ frontend/menus/CollectionMenu.js | 43 ++++++++ .../Menu.js => menus/DocumentMenu.js} | 42 +++---- frontend/models/Collection.js | 4 + frontend/scenes/Document/Document.js | 10 +- frontend/stores/AuthStore.js | 3 +- frontend/stores/UiStore.js | 11 ++ 12 files changed, 221 insertions(+), 130 deletions(-) create mode 100644 frontend/components/Layout/components/Modals.js create mode 100644 frontend/menus/AccountMenu.js create mode 100644 frontend/menus/CollectionMenu.js rename frontend/{scenes/Document/components/Menu.js => menus/DocumentMenu.js} (52%) diff --git a/frontend/components/DropdownMenu/DropdownMenu.js b/frontend/components/DropdownMenu/DropdownMenu.js index 953f620b..d36da02c 100644 --- a/frontend/components/DropdownMenu/DropdownMenu.js +++ b/frontend/components/DropdownMenu/DropdownMenu.js @@ -2,6 +2,7 @@ import React from 'react'; import { observable } from 'mobx'; import { observer } from 'mobx-react'; +import keydown from 'react-keydown'; import styled from 'styled-components'; import Flex from 'components/Flex'; import { color } from 'styles/constants'; @@ -15,22 +16,30 @@ type DropdownMenuProps = { @observer class DropdownMenu extends React.Component { props: DropdownMenuProps; - @observable menuOpen: boolean = false; + @observable open: boolean = false; handleClick = () => { - this.menuOpen = !this.menuOpen; + this.open = !this.open; + }; + + @keydown('esc') + handleEscape() { + this.open = false; + } + + handleClickOutside = (ev: SyntheticEvent) => { + ev.stopPropagation(); + this.open = false; }; render() { return ( - {this.menuOpen && } - + {this.open && } - - {this.menuOpen && + {this.open && {this.props.children} } diff --git a/frontend/components/DropdownMenu/DropdownMenuItem.js b/frontend/components/DropdownMenu/DropdownMenuItem.js index 77cf6f7e..113dc091 100644 --- a/frontend/components/DropdownMenu/DropdownMenuItem.js +++ b/frontend/components/DropdownMenu/DropdownMenuItem.js @@ -22,6 +22,7 @@ const MenuItem = styled.div` padding: 5px 10px; height: 32px; + color: ${color.slateDark}; display: flex; justify-content: space-between; align-items: center; diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 48592645..bdb5abe9 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -1,31 +1,26 @@ // @flow import React from 'react'; -import { Link, withRouter } from 'react-router-dom'; +import { withRouter } from 'react-router-dom'; import Helmet from 'react-helmet'; import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; -import { observable } from 'mobx'; -import _ from 'lodash'; import keydown from 'react-keydown'; import Flex from 'components/Flex'; import { color, layout } from 'styles/constants'; import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers'; -import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; import Avatar from 'components/Avatar'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; -import Modal from 'components/Modal'; import Icon from 'components/Icon'; -import CollectionNew from 'scenes/CollectionNew'; -import CollectionEdit from 'scenes/CollectionEdit'; -import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; -import Settings from 'scenes/Settings'; +import CollectionMenu from 'menus/CollectionMenu'; +import AccountMenu from 'menus/AccountMenu'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; import SidebarLink from './components/SidebarLink'; import HeaderBlock from './components/HeaderBlock'; +import Modals from './components/Modals'; import AuthStore from 'stores/AuthStore'; import UiStore from 'stores/UiStore'; @@ -52,8 +47,6 @@ type Props = { search: true, }; - @observable modal = null; - @keydown(['/', 't']) goToSearch(ev) { ev.preventDefault(); @@ -76,37 +69,21 @@ type Props = { this.props.history.push(documentEditUrl(activeDocument)); } - handleLogout = () => { - this.props.auth.logout(() => this.props.history.push('/')); - }; - @keydown('shift+/') goToOpenKeyboardShortcuts() { - this.modal = 'keyboard-shortcuts'; + this.props.ui.setActiveModal('keyboard-shortcuts'); } - handleOpenKeyboardShortcuts = () => { - this.goToOpenKeyboardShortcuts(); - }; - - handleOpenSettings = () => { - this.modal = 'settings'; - }; - handleCreateCollection = () => { - this.modal = 'create-collection'; + this.props.ui.setActiveModal('create-collection'); }; handleEditCollection = () => { - this.modal = 'edit-collection'; - }; - - handleCloseModal = () => { - this.modal = null; + this.props.ui.setActiveModal('edit-collection'); }; render() { - const { auth, documents, collections, history, ui } = this.props; + const { auth, documents, collections, ui } = this.props; const { user, team } = auth; return ( @@ -130,27 +107,13 @@ type Props = { user && team && - } - > - - Settings - - - Keyboard shortcuts - - - API - - - Logout - - + /> @@ -167,8 +130,8 @@ type Props = { {collections.active - ? - + ? + : @@ -189,44 +152,7 @@ type Props = { {this.props.children} - - - - - {collections.active && - } - - - - - - - + ); } @@ -255,10 +181,6 @@ const Content = styled(Flex)` transition: margin-left 200ms ease-in-out; `; -const MenuLink = styled(Link)` - color: ${color.text}; -`; - const Sidebar = styled(Flex)` position: fixed; top: 0; diff --git a/frontend/components/Layout/components/Modals.js b/frontend/components/Layout/components/Modals.js new file mode 100644 index 00000000..96ef4430 --- /dev/null +++ b/frontend/components/Layout/components/Modals.js @@ -0,0 +1,58 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import Modal from 'components/Modal'; +import UiStore from 'stores/UiStore'; +import CollectionNew from 'scenes/CollectionNew'; +import CollectionEdit from 'scenes/CollectionEdit'; +import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; +import Settings from 'scenes/Settings'; + +@observer class Modals extends Component { + props: { + ui: UiStore, + }; + + handleClose = () => { + this.props.ui.clearActiveModal(); + }; + + render() { + const { activeModalName, activeModalProps } = this.props.ui; + + return ( + + + + + + + + + + + + + + + ); + } +} + +export default Modals; diff --git a/frontend/components/Scrollable/Scrollable.js b/frontend/components/Scrollable/Scrollable.js index 050af487..526cc29e 100644 --- a/frontend/components/Scrollable/Scrollable.js +++ b/frontend/components/Scrollable/Scrollable.js @@ -6,7 +6,6 @@ const Scroll = styled.div` height: 100%; overflow-y: auto; overflow-x: hidden; - transform: translateZ(0); -webkit-overflow-scrolling: touch; `; diff --git a/frontend/menus/AccountMenu.js b/frontend/menus/AccountMenu.js new file mode 100644 index 00000000..77ca6cb2 --- /dev/null +++ b/frontend/menus/AccountMenu.js @@ -0,0 +1,53 @@ +// @flow +import React, { Component } from 'react'; +import { Link, withRouter } from 'react-router-dom'; +import { inject, observer } from 'mobx-react'; +import UiStore from 'stores/UiStore'; +import AuthStore from 'stores/AuthStore'; +import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; + +@observer class AccountMenu extends Component { + props: { + label?: React$Element, + history: Object, + ui: UiStore, + auth: AuthStore, + }; + + handleOpenKeyboardShortcuts = () => { + this.props.ui.setActiveModal('keyboard-shortcuts'); + }; + + handleOpenSettings = () => { + this.props.ui.setActiveModal('settings'); + }; + + handleLogout = () => { + this.props.auth.logout(); + this.props.history.push('/'); + }; + + render() { + return ( + + + Settings + + + Keyboard shortcuts + + + API documentation + + + Logout + + + ); + } +} + +export default withRouter(inject('ui', 'auth')(AccountMenu)); diff --git a/frontend/menus/CollectionMenu.js b/frontend/menus/CollectionMenu.js new file mode 100644 index 00000000..eed089c4 --- /dev/null +++ b/frontend/menus/CollectionMenu.js @@ -0,0 +1,43 @@ +// @flow +import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; +import { inject, observer } from 'mobx-react'; +import Collection from 'models/Collection'; +import UiStore from 'stores/UiStore'; +import Icon from 'components/Icon'; +import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; + +@observer class CollectionMenu extends Component { + props: { + label?: React$Element, + history: Object, + ui: UiStore, + collection: Collection, + }; + + onEdit = () => { + const { collection } = this.props; + this.props.ui.setActiveModal('edit-collection', { collection }); + }; + + onDelete = () => { + const { collection } = this.props; + this.props.ui.setActiveModal('delete-collection', { collection }); + }; + + render() { + const { collection, label } = this.props; + const { allowDelete } = collection; + + return ( + }> + {collection && + Edit} + {allowDelete && + Delete} + + ); + } +} + +export default withRouter(inject('ui')(CollectionMenu)); diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/menus/DocumentMenu.js similarity index 52% rename from frontend/scenes/Document/components/Menu.js rename to frontend/menus/DocumentMenu.js index d178f42f..1dc9d9b9 100644 --- a/frontend/scenes/Document/components/Menu.js +++ b/frontend/menus/DocumentMenu.js @@ -1,19 +1,19 @@ // @flow import React, { Component } from 'react'; -import get from 'lodash/get'; import { withRouter } from 'react-router-dom'; -import { observer } from 'mobx-react'; +import { inject, observer } from 'mobx-react'; import Document from 'models/Document'; +import UiStore from 'stores/UiStore'; import Icon from 'components/Icon'; import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; -type Props = { - history: Object, - document: Document, -}; - -@observer class Menu extends Component { - props: Props; +@observer class DocumentMenu extends Component { + props: { + ui: UiStore, + label?: React$Element, + history: Object, + document: Document, + }; onCreateDocument = () => { this.props.history.push(`${this.props.document.collection.url}/new`); @@ -23,19 +23,9 @@ type Props = { this.props.history.push(`${this.props.document.url}/new`); }; - onDelete = async () => { - let msg; - if (get(this.props, 'document.collection.type') === 'atlas') { - msg = - "Are you sure you want to delete this document and all it's child documents (if any)?"; - } else { - msg = 'Are you sure you want to delete this document?'; - } - - if (confirm(msg)) { - await this.props.document.delete(); - this.props.history.push(this.props.document.collection.url); - } + onDelete = () => { + const { document } = this.props; + this.props.ui.setActiveModal('delete-document', { document }); }; onExport = () => { @@ -43,11 +33,11 @@ type Props = { }; render() { - const collection = this.props.document.collection; - const allowDelete = this.props.document.allowDelete; + const { document, label } = this.props; + const { collection, allowDelete } = document; return ( - } top right> + }> {collection && New document @@ -60,4 +50,4 @@ type Props = { } } -export default withRouter(Menu); +export default withRouter(inject('ui')(DocumentMenu)); diff --git a/frontend/models/Collection.js b/frontend/models/Collection.js index 13d8e5e8..da302b8f 100644 --- a/frontend/models/Collection.js +++ b/frontend/models/Collection.js @@ -32,6 +32,10 @@ class Collection extends BaseModel { : this.url; } + @computed get allowDelete(): boolean { + return true; + } + /* Actions */ @action fetch = async () => { diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 67047377..8a338eb3 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -10,7 +10,7 @@ import { color, layout } from 'styles/constants'; import Document from 'models/Document'; import UiStore from 'stores/UiStore'; import DocumentsStore from 'stores/DocumentsStore'; -import Menu from './components/Menu'; +import DocumentMenu from 'menus/DocumentMenu'; import SaveAction from './components/SaveAction'; import LoadingPlaceholder from 'components/LoadingPlaceholder'; import Editor from 'components/Editor'; @@ -222,10 +222,12 @@ type Props = { Edit } + {isEditing && + + Cancel + } - {isEditing - ? Cancel - : } + {!isEditing && } diff --git a/frontend/stores/AuthStore.js b/frontend/stores/AuthStore.js index f5b77ecb..c0c7800e 100644 --- a/frontend/stores/AuthStore.js +++ b/frontend/stores/AuthStore.js @@ -30,10 +30,9 @@ class AuthStore { /* Actions */ - @action logout = (cb: Function) => { + @action logout = () => { this.user = null; this.token = null; - cb(); }; @action getOauthState = () => { diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index e8db5c5e..5d249619 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -3,12 +3,23 @@ import { observable, action } from 'mobx'; import Document from 'models/Document'; class UiStore { + @observable activeModalName: ?string; + @observable activeModalProps: ?Object; @observable activeDocumentId: ?string; @observable activeCollectionId: ?string; @observable progressBarVisible: boolean = false; @observable editMode: boolean = false; /* Actions */ + @action setActiveModal = (name: string, props: ?Object): void => { + this.activeModalName = name; + this.activeModalProps = props; + }; + + @action clearActiveModal = (): void => { + this.activeModalName = undefined; + this.activeModalProps = undefined; + }; @action setActiveDocument = (document: Document): void => { this.activeDocumentId = document.id;