From c919e51ce6a2344d6eb436c73d3d6c46e0b05226 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 9 Sep 2017 16:55:02 -0700 Subject: [PATCH 1/5] Improve dropdown menus, animation :zap: --- .../components/DropdownMenu/DropdownMenu.js | 51 ++++--------------- .../DropdownMenu/DropdownMenuItem.js | 42 +++++++++++++++ frontend/components/DropdownMenu/index.js | 3 +- frontend/models/Document.js | 18 +++++++ frontend/scenes/Document/Document.js | 3 +- frontend/scenes/Document/components/Menu.js | 47 +++++------------ 6 files changed, 87 insertions(+), 77 deletions(-) create mode 100644 frontend/components/DropdownMenu/DropdownMenuItem.js diff --git a/frontend/components/DropdownMenu/DropdownMenu.js b/frontend/components/DropdownMenu/DropdownMenu.js index 20ecf1ce..953f620b 100644 --- a/frontend/components/DropdownMenu/DropdownMenu.js +++ b/frontend/components/DropdownMenu/DropdownMenu.js @@ -5,19 +5,7 @@ import { observer } from 'mobx-react'; import styled from 'styled-components'; import Flex from 'components/Flex'; import { color } from 'styles/constants'; - -type MenuItemProps = { - onClick?: Function, - children?: React.Element, -}; - -const DropdownMenuItem = ({ onClick, children }: MenuItemProps) => { - return ( - - {children} - - ); -}; +import { fadeAndScaleIn } from 'styles/animations'; type DropdownMenuProps = { label: React.Element, @@ -73,37 +61,18 @@ const MenuContainer = styled.div` `; const Menu = styled.div` + animation: ${fadeAndScaleIn} 250ms ease; + transform-origin: 75% 0; + position: absolute; right: 0; z-index: 1000; - border: 1px solid #eee; - background-color: #fff; + border: ${color.slateLight}; + background: ${color.white}; + border-radius: 2px; min-width: 160px; + overflow: hidden; + box-shadow: 0 0 0 1px rgba(0,0,0,.05), 0 4px 8px rgba(0,0,0,.08), 0 2px 4px rgba(0,0,0,.08); `; -const MenuItem = styled.div` - margin: 0; - padding: 5px 10px; - height: 32px; - - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - border-left: 2px solid transparent; - - span { - margin-top: 2px; - } - - a { - text-decoration: none; - width: 100%; - } - - &:hover { - border-left: 2px solid ${color.primary}; - } -`; - -export { DropdownMenu, DropdownMenuItem }; +export default DropdownMenu; diff --git a/frontend/components/DropdownMenu/DropdownMenuItem.js b/frontend/components/DropdownMenu/DropdownMenuItem.js new file mode 100644 index 00000000..77cf6f7e --- /dev/null +++ b/frontend/components/DropdownMenu/DropdownMenuItem.js @@ -0,0 +1,42 @@ +// @flow +import React from 'react'; +import styled from 'styled-components'; +import { color } from 'styles/constants'; + +const DropdownMenuItem = ({ + onClick, + children, +}: { + onClick?: Function, + children?: React.Element, +}) => { + return ( + + {children} + + ); +}; + +const MenuItem = styled.div` + margin: 0; + padding: 5px 10px; + height: 32px; + + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + font-size: 15px; + + a { + text-decoration: none; + width: 100%; + } + + &:hover { + color: ${color.white}; + background: ${color.primary}; + } +`; + +export default DropdownMenuItem; diff --git a/frontend/components/DropdownMenu/index.js b/frontend/components/DropdownMenu/index.js index 11e84f9c..f6b44681 100644 --- a/frontend/components/DropdownMenu/index.js +++ b/frontend/components/DropdownMenu/index.js @@ -1,2 +1,3 @@ // @flow -export { DropdownMenu, DropdownMenuItem } from './DropdownMenu'; +export { default as DropdownMenu } from './DropdownMenu'; +export { default as DropdownMenuItem } from './DropdownMenuItem'; diff --git a/frontend/models/Document.js b/frontend/models/Document.js index ea39c2f6..48b38dc2 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -79,6 +79,16 @@ class Document extends BaseModel { return !this.isEmpty && !this.isSaving; } + @computed get allowDelete(): boolean { + const collection = this.collection; + return ( + collection && + collection.type === 'atlas' && + collection.documents && + collection.documents.length > 1 + ); + } + /* Actions */ @action star = async () => { @@ -182,6 +192,14 @@ class Document extends BaseModel { return; }; + download() { + const a = window.document.createElement('a'); + a.textContent = 'download'; + a.download = `${this.title}.md`; + a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(this.text)}`; + a.click(); + } + updateData(data: Object = {}, dirty: boolean = false) { if (data.text) { const { title, emoji } = parseTitle(data.text); diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 24e48b9a..67047377 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -204,8 +204,7 @@ type Props = { /> - {document && - !isNew && + {!isNew && !isEditing && } diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/scenes/Document/components/Menu.js index 9bae6f0b..d178f42f 100644 --- a/frontend/scenes/Document/components/Menu.js +++ b/frontend/scenes/Document/components/Menu.js @@ -1,6 +1,5 @@ // @flow import React, { Component } from 'react'; -import invariant from 'invariant'; import get from 'lodash/get'; import { withRouter } from 'react-router-dom'; import { observer } from 'mobx-react'; @@ -21,7 +20,6 @@ type Props = { }; onCreateChild = () => { - invariant(this.props.document, 'Document is not available'); this.props.history.push(`${this.props.document.url}/new`); }; @@ -41,41 +39,24 @@ type Props = { }; onExport = () => { - const doc = this.props.document; - if (doc) { - const a = document.createElement('a'); - a.textContent = 'download'; - a.download = `${doc.title}.md`; - a.href = `data:text/markdown;charset=UTF-8,${encodeURIComponent(doc.text)}`; - a.click(); - } + this.props.document.download(); }; render() { - const document = get(this.props, 'document'); - if (document) { - const collection = document.collection; - const allowDelete = - collection && - collection.type === 'atlas' && - collection.documents && - collection.documents.length > 1; + const collection = this.props.document.collection; + const allowDelete = this.props.document.allowDelete; - return ( - }> - {collection && -
- - New document - -
} - Export - {allowDelete && - Delete} -
- ); - } - return null; + return ( + } top right> + {collection && + + New document + } + Export + {allowDelete && + Delete} + + ); } } From fff8e7ad41587e8a598fc03bd0dbac40069332b5 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 9 Sep 2017 22:12:59 -0700 Subject: [PATCH 2/5] 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; From 79c1cc29a060f0d927f94e41703c268b7cfe40b1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 9 Sep 2017 22:51:22 -0700 Subject: [PATCH 3/5] Split modals, document delete --- frontend/components/Layout/Layout.js | 4 +- .../components/Layout/components/Modals.js | 20 ++++++- frontend/menus/CollectionMenu.js | 4 +- frontend/menus/DocumentMenu.js | 2 +- .../CollectionDelete/CollectionDelete.js | 60 +++++++++++++++++++ frontend/scenes/CollectionDelete/index.js | 3 + .../scenes/CollectionEdit/CollectionEdit.js | 47 +-------------- .../scenes/DocumentDelete/DocumentDelete.js | 59 ++++++++++++++++++ frontend/scenes/DocumentDelete/index.js | 3 + 9 files changed, 149 insertions(+), 53 deletions(-) create mode 100644 frontend/scenes/CollectionDelete/CollectionDelete.js create mode 100644 frontend/scenes/CollectionDelete/index.js create mode 100644 frontend/scenes/DocumentDelete/DocumentDelete.js create mode 100644 frontend/scenes/DocumentDelete/index.js diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index bdb5abe9..92ec84c2 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -75,11 +75,11 @@ type Props = { } handleCreateCollection = () => { - this.props.ui.setActiveModal('create-collection'); + this.props.ui.setActiveModal('collection-new'); }; handleEditCollection = () => { - this.props.ui.setActiveModal('edit-collection'); + this.props.ui.setActiveModal('collection-edit'); }; render() { diff --git a/frontend/components/Layout/components/Modals.js b/frontend/components/Layout/components/Modals.js index 96ef4430..43e2c4da 100644 --- a/frontend/components/Layout/components/Modals.js +++ b/frontend/components/Layout/components/Modals.js @@ -5,6 +5,8 @@ import Modal from 'components/Modal'; import UiStore from 'stores/UiStore'; import CollectionNew from 'scenes/CollectionNew'; import CollectionEdit from 'scenes/CollectionEdit'; +import CollectionDelete from 'scenes/CollectionDelete'; +import DocumentDelete from 'scenes/DocumentDelete'; import KeyboardShortcuts from 'scenes/KeyboardShortcuts'; import Settings from 'scenes/Settings'; @@ -23,19 +25,33 @@ import Settings from 'scenes/Settings'; return ( + + + + + + { const { collection } = this.props; - this.props.ui.setActiveModal('edit-collection', { collection }); + this.props.ui.setActiveModal('collection-edit', { collection }); }; onDelete = () => { const { collection } = this.props; - this.props.ui.setActiveModal('delete-collection', { collection }); + this.props.ui.setActiveModal('collection-delete', { collection }); }; render() { diff --git a/frontend/menus/DocumentMenu.js b/frontend/menus/DocumentMenu.js index 1dc9d9b9..e87ebc00 100644 --- a/frontend/menus/DocumentMenu.js +++ b/frontend/menus/DocumentMenu.js @@ -25,7 +25,7 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; onDelete = () => { const { document } = this.props; - this.props.ui.setActiveModal('delete-document', { document }); + this.props.ui.setActiveModal('document-delete', { document }); }; onExport = () => { diff --git a/frontend/scenes/CollectionDelete/CollectionDelete.js b/frontend/scenes/CollectionDelete/CollectionDelete.js new file mode 100644 index 00000000..e3066cb9 --- /dev/null +++ b/frontend/scenes/CollectionDelete/CollectionDelete.js @@ -0,0 +1,60 @@ +// @flow +import React, { Component } from 'react'; +import { observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { homeUrl } from 'utils/routeHelpers'; +import Button from 'components/Button'; +import Flex from 'components/Flex'; +import HelpText from 'components/HelpText'; +import Collection from 'models/Collection'; +import CollectionsStore from 'stores/CollectionsStore'; + +type Props = { + history: Object, + collection: Collection, + collections: CollectionsStore, + onSubmit: () => void, +}; + +@observer class CollectionDelete extends Component { + props: Props; + @observable isDeleting: boolean; + + handleSubmit = async (ev: SyntheticEvent) => { + ev.preventDefault(); + this.isDeleting = true; + const success = await this.props.collection.delete(); + + if (success) { + this.props.collections.remove(this.props.collection.id); + this.props.history.push(homeUrl()); + this.props.onSubmit(); + } + + this.isDeleting = false; + }; + + render() { + const { collection } = this.props; + + return ( + +
+ + Are you sure? Deleting the + {' '} + {collection.name} + {' '} + collection is permanant and will also delete all of the documents within + it, so be careful with that. + + +
+
+ ); + } +} + +export default CollectionDelete; diff --git a/frontend/scenes/CollectionDelete/index.js b/frontend/scenes/CollectionDelete/index.js new file mode 100644 index 00000000..e86f5889 --- /dev/null +++ b/frontend/scenes/CollectionDelete/index.js @@ -0,0 +1,3 @@ +// @flow +import CollectionDelete from './CollectionDelete'; +export default CollectionDelete; diff --git a/frontend/scenes/CollectionEdit/CollectionEdit.js b/frontend/scenes/CollectionEdit/CollectionEdit.js index e3718808..249a1eb0 100644 --- a/frontend/scenes/CollectionEdit/CollectionEdit.js +++ b/frontend/scenes/CollectionEdit/CollectionEdit.js @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { observable } from 'mobx'; import { observer } from 'mobx-react'; -import { homeUrl } from 'utils/routeHelpers'; import Button from 'components/Button'; import Input from 'components/Input'; import Flex from 'components/Flex'; @@ -20,8 +19,6 @@ type Props = { @observer class CollectionEdit extends Component { props: Props; @observable name: string; - @observable isConfirming: boolean; - @observable isDeleting: boolean; @observable isSaving: boolean; componentWillMount() { @@ -46,34 +43,12 @@ type Props = { this.name = ev.target.value; }; - confirmDelete = () => { - this.isConfirming = true; - }; - - cancelDelete = () => { - this.isConfirming = false; - }; - - confirmedDelete = async (ev: SyntheticEvent) => { - ev.preventDefault(); - this.isDeleting = true; - const success = await this.props.collection.delete(); - - if (success) { - this.props.collections.remove(this.props.collection.id); - this.props.history.push(homeUrl()); - this.props.onSubmit(); - } - - this.isDeleting = false; - }; - render() { return (
- You can edit a collection name at any time, but doing so might + You can edit a collections name at any time, however doing so might confuse your team mates.
-
-
- - Deleting a collection will also delete all of the documents within - it, so be careful with that. - - {!this.isConfirming && - } - {this.isConfirming && - - - - } -
); } diff --git a/frontend/scenes/DocumentDelete/DocumentDelete.js b/frontend/scenes/DocumentDelete/DocumentDelete.js new file mode 100644 index 00000000..a37dd777 --- /dev/null +++ b/frontend/scenes/DocumentDelete/DocumentDelete.js @@ -0,0 +1,59 @@ +// @flow +import React, { Component } from 'react'; +import { observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { homeUrl } from 'utils/routeHelpers'; +import Button from 'components/Button'; +import Flex from 'components/Flex'; +import HelpText from 'components/HelpText'; +import Document from 'models/Document'; +import DocumentsStore from 'stores/DocumentsStore'; + +type Props = { + history: Object, + document: Document, + documents: DocumentsStore, + onSubmit: () => void, +}; + +@observer class DocumentDelete extends Component { + props: Props; + @observable isDeleting: boolean; + + handleSubmit = async (ev: SyntheticEvent) => { + ev.preventDefault(); + this.isDeleting = true; + const success = await this.props.document.delete(); + + if (success) { + this.props.documents.remove(this.props.document.id); + this.props.history.push(homeUrl()); + this.props.onSubmit(); + } + + this.isDeleting = false; + }; + + render() { + const { document } = this.props; + + return ( + +
+ + Are you sure? Deleting the + {' '} + {document.title} + {' '} + document is permanant and will also delete all of its history. + + +
+
+ ); + } +} + +export default DocumentDelete; diff --git a/frontend/scenes/DocumentDelete/index.js b/frontend/scenes/DocumentDelete/index.js new file mode 100644 index 00000000..90e2e111 --- /dev/null +++ b/frontend/scenes/DocumentDelete/index.js @@ -0,0 +1,3 @@ +// @flow +import DocumentDelete from './DocumentDelete'; +export default DocumentDelete; From 4cec8e69edcb5085421b7c8b574e156aec074a53 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 10 Sep 2017 22:27:23 -0700 Subject: [PATCH 4/5] WIP --- .../components/Layout/components/Modals.js | 58 ++++++++----------- .../scenes/CollectionEdit/CollectionEdit.js | 2 +- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/frontend/components/Layout/components/Modals.js b/frontend/components/Layout/components/Modals.js index 43e2c4da..37193b8b 100644 --- a/frontend/components/Layout/components/Modals.js +++ b/frontend/components/Layout/components/Modals.js @@ -1,7 +1,7 @@ // @flow import React, { Component } from 'react'; import { observer } from 'mobx-react'; -import Modal from 'components/Modal'; +import BaseModal from 'components/Modal'; import UiStore from 'stores/UiStore'; import CollectionNew from 'scenes/CollectionNew'; import CollectionEdit from 'scenes/CollectionEdit'; @@ -22,48 +22,36 @@ import Settings from 'scenes/Settings'; render() { const { activeModalName, activeModalProps } = this.props.ui; + const Modal = ({ name, children, ...rest }) => { + return ( + + {React.cloneElement(children, activeModalProps)} + + ); + }; + return ( - - + + - - + + - - + + - - + + - + - + diff --git a/frontend/scenes/CollectionEdit/CollectionEdit.js b/frontend/scenes/CollectionEdit/CollectionEdit.js index 249a1eb0..3054311c 100644 --- a/frontend/scenes/CollectionEdit/CollectionEdit.js +++ b/frontend/scenes/CollectionEdit/CollectionEdit.js @@ -48,7 +48,7 @@ type Props = {
- You can edit a collections name at any time, however doing so might + You can edit a collection's name at any time, however doing so might confuse your team mates. Date: Mon, 11 Sep 2017 23:41:12 -0700 Subject: [PATCH 5/5] Connecting modals where needed --- frontend/components/Layout/Layout.js | 2 +- frontend/models/Collection.js | 10 ++++++---- frontend/models/Document.js | 3 ++- frontend/scenes/CollectionDelete/CollectionDelete.js | 5 +++-- frontend/scenes/CollectionEdit/CollectionEdit.js | 7 +++---- frontend/scenes/CollectionNew/CollectionNew.js | 5 +++-- frontend/scenes/DocumentDelete/DocumentDelete.js | 5 +++-- frontend/utils/ApiClient.js | 4 +++- yarn.lock | 6 +----- 9 files changed, 25 insertions(+), 22 deletions(-) diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 92ec84c2..af08b23a 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -115,7 +115,7 @@ type Props = { } /> - + diff --git a/frontend/models/Collection.js b/frontend/models/Collection.js index da302b8f..d19b76ab 100644 --- a/frontend/models/Collection.js +++ b/frontend/models/Collection.js @@ -88,13 +88,15 @@ class Collection extends BaseModel { @action delete = async () => { try { - const res = await client.post('/collections.delete', { id: this.id }); - invariant(res && res.data, 'Data should be available'); - const { data } = res; - return data.success; + await client.post('/collections.delete', { id: this.id }); + this.emit('collections.delete', { + id: this.id, + }); + return true; } catch (e) { this.errors.add('Collection failed to delete'); } + return false; }; updateData(data: Object = {}) { diff --git a/frontend/models/Document.js b/frontend/models/Document.js index 48b38dc2..7f0aabc4 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -186,10 +186,11 @@ class Document extends BaseModel { id: this.id, collectionId: this.collection.id, }); + return true; } catch (e) { this.errors.add('Error while deleting the document'); } - return; + return false; }; download() { diff --git a/frontend/scenes/CollectionDelete/CollectionDelete.js b/frontend/scenes/CollectionDelete/CollectionDelete.js index e3066cb9..31573e90 100644 --- a/frontend/scenes/CollectionDelete/CollectionDelete.js +++ b/frontend/scenes/CollectionDelete/CollectionDelete.js @@ -1,7 +1,8 @@ // @flow import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; import { observable } from 'mobx'; -import { observer } from 'mobx-react'; +import { inject, observer } from 'mobx-react'; import { homeUrl } from 'utils/routeHelpers'; import Button from 'components/Button'; import Flex from 'components/Flex'; @@ -57,4 +58,4 @@ type Props = { } } -export default CollectionDelete; +export default inject('collections')(withRouter(CollectionDelete)); diff --git a/frontend/scenes/CollectionEdit/CollectionEdit.js b/frontend/scenes/CollectionEdit/CollectionEdit.js index 3054311c..8cb2401e 100644 --- a/frontend/scenes/CollectionEdit/CollectionEdit.js +++ b/frontend/scenes/CollectionEdit/CollectionEdit.js @@ -1,18 +1,17 @@ // @flow import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; import { observable } from 'mobx'; -import { observer } from 'mobx-react'; +import { inject, observer } from 'mobx-react'; import Button from 'components/Button'; import Input from 'components/Input'; import Flex from 'components/Flex'; import HelpText from 'components/HelpText'; import Collection from 'models/Collection'; -import CollectionsStore from 'stores/CollectionsStore'; type Props = { history: Object, collection: Collection, - collections: CollectionsStore, onSubmit: () => void, }; @@ -71,4 +70,4 @@ type Props = { } } -export default CollectionEdit; +export default inject('collections')(withRouter(CollectionEdit)); diff --git a/frontend/scenes/CollectionNew/CollectionNew.js b/frontend/scenes/CollectionNew/CollectionNew.js index 4b9f7819..e52d28ee 100644 --- a/frontend/scenes/CollectionNew/CollectionNew.js +++ b/frontend/scenes/CollectionNew/CollectionNew.js @@ -1,7 +1,8 @@ // @flow import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; import { observable } from 'mobx'; -import { observer } from 'mobx-react'; +import { inject, observer } from 'mobx-react'; import Button from 'components/Button'; import Input from 'components/Input'; import HelpText from 'components/HelpText'; @@ -69,4 +70,4 @@ type Props = { } } -export default CollectionNew; +export default inject('collections')(withRouter(CollectionNew)); diff --git a/frontend/scenes/DocumentDelete/DocumentDelete.js b/frontend/scenes/DocumentDelete/DocumentDelete.js index a37dd777..0a8799c7 100644 --- a/frontend/scenes/DocumentDelete/DocumentDelete.js +++ b/frontend/scenes/DocumentDelete/DocumentDelete.js @@ -1,7 +1,8 @@ // @flow import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; import { observable } from 'mobx'; -import { observer } from 'mobx-react'; +import { inject, observer } from 'mobx-react'; import { homeUrl } from 'utils/routeHelpers'; import Button from 'components/Button'; import Flex from 'components/Flex'; @@ -56,4 +57,4 @@ type Props = { } } -export default DocumentDelete; +export default inject('documents')(withRouter(DocumentDelete)); diff --git a/frontend/utils/ApiClient.js b/frontend/utils/ApiClient.js index aff280e9..3c7ff2be 100644 --- a/frontend/utils/ApiClient.js +++ b/frontend/utils/ApiClient.js @@ -65,7 +65,9 @@ class ApiClient { // Handle 401, log out user if (response.status === 401) { - return stores.auth.logout(() => (window.location = '/')); + stores.auth.logout(); + window.location = '/'; + return; } // Handle failed responses diff --git a/yarn.lock b/yarn.lock index f446a475..a74ca247 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2476,11 +2476,7 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -electron-to-chromium@^1.2.7: - version "1.3.21" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.21.tgz#a967ebdcfe8ed0083fc244d1894022a8e8113ea2" - -electron-to-chromium@^1.3.18: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18: version "1.3.20" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.20.tgz#2eedd5ccbae7ddc557f68ad1fce9c172e915e4e5"