From aa0ddd94bfc97b00ff2c2f807a7a6d88cbfa278c Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Thu, 15 Jun 2017 20:39:08 -0700 Subject: [PATCH] Sidebar work --- frontend/components/Layout/Layout.js | 150 +++++++++--------- .../SidebarCollection/SidebarCollection.js | 39 +++++ .../components/SidebarCollection/index.js | 3 + .../SidebarCollectionList.js | 36 +++++ .../components/SidebarCollectionList/index.js | 3 + .../components/SidebarLink/SidebarLink.js | 26 +++ .../Layout/components/SidebarLink/index.js | 3 + frontend/components/Sidebar/Sidebar.js | 85 ---------- frontend/components/Sidebar/Sidebar.scss | 56 ------- frontend/components/Sidebar/SidebarStore.js | 14 -- .../Sidebar/components/Separator/Separator.js | 16 -- .../components/Separator/Separator.scss | 6 - .../Sidebar/components/Separator/index.js | 3 - frontend/components/Sidebar/index.js | 3 - frontend/scenes/Collection/Collection.js | 38 ++--- frontend/scenes/Collection/CollectionStore.js | 29 ++-- frontend/scenes/Document/Document.js | 20 ++- frontend/stores/CollectionsStore.js | 3 +- frontend/stores/UiStore.js | 30 +--- frontend/styles/base.scss | 10 +- server/static/index.html | 45 ++---- 21 files changed, 250 insertions(+), 368 deletions(-) create mode 100644 frontend/components/Layout/components/SidebarCollection/SidebarCollection.js create mode 100644 frontend/components/Layout/components/SidebarCollection/index.js create mode 100644 frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js create mode 100644 frontend/components/Layout/components/SidebarCollectionList/index.js create mode 100644 frontend/components/Layout/components/SidebarLink/SidebarLink.js create mode 100644 frontend/components/Layout/components/SidebarLink/index.js delete mode 100644 frontend/components/Sidebar/Sidebar.js delete mode 100644 frontend/components/Sidebar/Sidebar.scss delete mode 100644 frontend/components/Sidebar/SidebarStore.js delete mode 100644 frontend/components/Sidebar/components/Separator/Separator.js delete mode 100644 frontend/components/Sidebar/components/Separator/Separator.scss delete mode 100644 frontend/components/Sidebar/components/Separator/index.js delete mode 100644 frontend/components/Sidebar/index.js diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index c59363bc..9af95ec4 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -6,23 +6,31 @@ import styled from 'styled-components'; import { observer, inject } from 'mobx-react'; import _ from 'lodash'; import keydown from 'react-keydown'; -import searchIcon from 'assets/icons/search.svg'; import { Flex } from 'reflexbox'; -import { textColor, headerHeight } from 'styles/constants.scss'; +import { textColor } from 'styles/constants.scss'; import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import LoadingIndicator from 'components/LoadingIndicator'; + +import SidebarCollection from './components/SidebarCollection'; +import SidebarCollectionList from './components/SidebarCollectionList'; +import SidebarLink from './components/SidebarLink'; + import UserStore from 'stores/UserStore'; import AuthStore from 'stores/AuthStore'; +import UiStore from 'stores/UiStore'; +import CollectionsStore from 'stores/CollectionsStore'; type Props = { history: Object, + collections: CollectionsStore, children?: ?React.Element, actions?: ?React.Element, title?: ?React.Element, loading?: boolean, user: UserStore, auth: AuthStore, + ui: UiStore, search: ?boolean, notifications?: React.Element, }; @@ -36,13 +44,13 @@ type Props = { @keydown(['/', 't']) search() { - if (this.props.auth.isAuthenticated) + if (this.props.auth.authenticated) _.defer(() => this.props.history.push('/search')); } @keydown(['d']) dashboard() { - if (this.props.auth.isAuthenticated) + if (this.props.auth.authenticated) _.defer(() => this.props.history.push('/')); } @@ -51,7 +59,7 @@ type Props = { }; render() { - const { auth, user } = this.props; + const { user, auth, ui, collections } = this.props; return ( @@ -69,48 +77,52 @@ type Props = { {this.props.notifications} -
- - Atlas - - - - - {this.props.actions} - - {auth.authenticated && - user && - - {this.props.search && - - - - - - - } - }> - - Settings - - - - Keyboard shortcuts - - - - API - - Logout - - } - - -
+ + {auth.authenticated && + user && + +
+ + Atlas + + }> + + Settings + + + + Keyboard shortcuts + + + + API + + Logout + +
- - {this.props.children} - + + + Search + + + Dashboard + Starred + + + {ui.activeCollection + ? + : } + + +
} + + + {this.props.children} + +
); } @@ -122,24 +134,8 @@ const Container = styled(Flex)` height: 100%; `; -const Header = styled(Flex)` - position: absolute; - top: 0; - left: 0; - right: 0; - justify-content: space-between; - align-items: center; - - padding: 0 20px; - - z-index: 1; - height: ${headerHeight}; - - font-size: 14px; - line-height: 1; -`; - const LogoLink = styled(Link)` + margin-top: 5px; font-family: 'Atlas Grotesk'; font-weight: bold; color: ${textColor}; @@ -147,16 +143,6 @@ const LogoLink = styled(Link)` font-size: 16px; `; -const Search = styled(Flex)` - margin: 0 5px; - padding: 15px 5px 0 5px; - cursor: pointer; -`; - -const SearchIcon = styled.img` - height: 20px; -`; - const Avatar = styled.img` width: 24px; height: 24px; @@ -172,4 +158,20 @@ const Content = styled(Flex)` overflow: scroll; `; -export default withRouter(inject('user', 'auth')(Layout)); +const Sidebar = styled(Flex)` + width: 250px; + padding: 10px 20px; + background: rgba(250, 251, 252, 0.71); + border-right: 1px solid #eceff3; +`; + +const Header = styled(Flex)` + margin-bottom: 20px; +`; + +const LinkSection = styled(Flex)` + margin-bottom: 20px; + flex-direction: column; +`; + +export default withRouter(inject('user', 'auth', 'ui', 'collections')(Layout)); diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js new file mode 100644 index 00000000..de6a1244 --- /dev/null +++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js @@ -0,0 +1,39 @@ +// @flow +import React from 'react'; +import { observer } from 'mobx-react'; +import { Flex } from 'reflexbox'; +import styled from 'styled-components'; + +import SidebarLink from '../SidebarLink'; + +import Collection from 'models/Collection'; + +type Props = { + collection: Collection, +}; + +const SidebarCollection = ({ collection }: Props) => { + if (collection) { + return ( + +
{collection.name}
+ {collection.documents.map(document => ( + + {document.title} + + ))} +
+ ); + } + return null; +}; + +const Header = styled(Flex)` + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + color: #9FA6AB; + letter-spacing: 0.04em; +`; + +export default observer(SidebarCollection); diff --git a/frontend/components/Layout/components/SidebarCollection/index.js b/frontend/components/Layout/components/SidebarCollection/index.js new file mode 100644 index 00000000..80b2a046 --- /dev/null +++ b/frontend/components/Layout/components/SidebarCollection/index.js @@ -0,0 +1,3 @@ +// @flow +import SidebarCollection from './SidebarCollection'; +export default SidebarCollection; diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js new file mode 100644 index 00000000..0115ec66 --- /dev/null +++ b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js @@ -0,0 +1,36 @@ +// @flow +import React from 'react'; +import { observer, inject } from 'mobx-react'; +import { Flex } from 'reflexbox'; +import styled from 'styled-components'; + +import SidebarLink from '../SidebarLink'; + +import CollectionsStore from 'stores/CollectionsStore'; + +type Props = { + collections: CollectionsStore, +}; + +const SidebarCollectionList = observer(({ collections }: Props) => { + return ( + +
Collections
+ {collections.data.map(collection => ( + + {collection.name} + + ))} +
+ ); +}); + +const Header = styled(Flex)` + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + color: #9FA6AB; + letter-spacing: 0.04em; +`; + +export default inject('collections')(SidebarCollectionList); diff --git a/frontend/components/Layout/components/SidebarCollectionList/index.js b/frontend/components/Layout/components/SidebarCollectionList/index.js new file mode 100644 index 00000000..ed4ba40c --- /dev/null +++ b/frontend/components/Layout/components/SidebarCollectionList/index.js @@ -0,0 +1,3 @@ +// @flow +import SidebarCollectionList from './SidebarCollectionList'; +export default SidebarCollectionList; diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js new file mode 100644 index 00000000..73543635 --- /dev/null +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -0,0 +1,26 @@ +// @flow +import React from 'react'; +import { observer } from 'mobx-react'; +import { NavLink } from 'react-router-dom'; +import { Flex } from 'reflexbox'; +import styled from 'styled-components'; + +const activeStyle = { + color: '#000000', +}; + +const SidebarLink = observer(props => ( + + + +)); + +const LinkContainer = styled(Flex)` + padding: 5px 0; + + a { + color: #848484; + } +`; + +export default SidebarLink; diff --git a/frontend/components/Layout/components/SidebarLink/index.js b/frontend/components/Layout/components/SidebarLink/index.js new file mode 100644 index 00000000..eabfd77f --- /dev/null +++ b/frontend/components/Layout/components/SidebarLink/index.js @@ -0,0 +1,3 @@ +// @flow +import SidebarLink from './SidebarLink'; +export default SidebarLink; diff --git a/frontend/components/Sidebar/Sidebar.js b/frontend/components/Sidebar/Sidebar.js deleted file mode 100644 index d4a9f58b..00000000 --- a/frontend/components/Sidebar/Sidebar.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow -import React from 'react'; -import { observer } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; - -import { Flex } from 'reflexbox'; -import Tree from 'components/Tree'; -import Separator from './components/Separator'; - -import styles from './Sidebar.scss'; -import classNames from 'classnames/bind'; -const cx = classNames.bind(styles); - -import SidebarStore from './SidebarStore'; - -type Props = { - open?: boolean, - onToggle: Function, - navigationTree: Object, - onNavigationUpdate: Function, - onNodeCollapse: Function, - history: Object, -}; - -@observer class Sidebar extends React.Component { - props: Props; - store: SidebarStore; - - constructor(props: Props) { - super(props); - - this.store = new SidebarStore(); - } - - toggleEdit = (e: MouseEvent) => { - e.preventDefault(); - this.store.toggleEdit(); - }; - - render() { - return ( - - {this.props.open && - - - - - - {this.store.isEditing && - - Drag & drop to organize   - } - - {!this.store.isEditing ? 'Organize documents' : 'Done'} - - - } -
- Menu -
-
- ); - } -} - -export default withRouter(Sidebar); diff --git a/frontend/components/Sidebar/Sidebar.scss b/frontend/components/Sidebar/Sidebar.scss deleted file mode 100644 index dc46e94e..00000000 --- a/frontend/components/Sidebar/Sidebar.scss +++ /dev/null @@ -1,56 +0,0 @@ -@import '~styles/constants.scss'; - -.sidebar { - position: relative; - width: 250px; - border-right: 1px solid #eee; - font-size: 13px; -} - -.sidebarToggle { - display: flex; - position: relative; - width: 60px; - cursor: pointer; - justify-content: center; - - &:hover { - background-color: #f5f5f5; - - .menuIcon { - opacity: 1; - } - } -} - -.menuIcon { - margin-top: 18px; - height: 28px; - opacity: 0.15; -} - -.content { - padding: 20px 20px 20px 5px; -} - -.tree { - -} - -.actions { - position: absolute; - bottom: 0; - left: 0; - right: 0; - background-color: #fff; - padding: 10px 20px; - height: 40px; -} - -.action { - color: $gray; - - &.active { - color: $textColor; - } -} diff --git a/frontend/components/Sidebar/SidebarStore.js b/frontend/components/Sidebar/SidebarStore.js deleted file mode 100644 index b57621c5..00000000 --- a/frontend/components/Sidebar/SidebarStore.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { observable, action } from 'mobx'; - -class SidebarStore { - @observable isEditing = false; - - /* Actions */ - - @action toggleEdit = () => { - this.isEditing = !this.isEditing; - }; -} - -export default SidebarStore; diff --git a/frontend/components/Sidebar/components/Separator/Separator.js b/frontend/components/Sidebar/components/Separator/Separator.js deleted file mode 100644 index 8cc898a7..00000000 --- a/frontend/components/Sidebar/components/Separator/Separator.js +++ /dev/null @@ -1,16 +0,0 @@ -// @flow -import React from 'react'; - -import styles from './Separator.scss'; - -class Separator extends React.Component { - render() { - return ( - - ยท - - ); - } -} - -export default Separator; diff --git a/frontend/components/Sidebar/components/Separator/Separator.scss b/frontend/components/Sidebar/components/Separator/Separator.scss deleted file mode 100644 index 7aa44d11..00000000 --- a/frontend/components/Sidebar/components/Separator/Separator.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '~styles/constants.scss'; - -.separator { - padding: 0 10px; - color: $lightGray; -} diff --git a/frontend/components/Sidebar/components/Separator/index.js b/frontend/components/Sidebar/components/Separator/index.js deleted file mode 100644 index 4b828564..00000000 --- a/frontend/components/Sidebar/components/Separator/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Separator from './Separator'; -export default Separator; diff --git a/frontend/components/Sidebar/index.js b/frontend/components/Sidebar/index.js deleted file mode 100644 index c1b441c2..00000000 --- a/frontend/components/Sidebar/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Sidebar from './Sidebar'; -export default Sidebar; diff --git a/frontend/scenes/Collection/Collection.js b/frontend/scenes/Collection/Collection.js index 96210a58..7ac85440 100644 --- a/frontend/scenes/Collection/Collection.js +++ b/frontend/scenes/Collection/Collection.js @@ -3,9 +3,9 @@ import React from 'react'; import { observer, inject } from 'mobx-react'; import { Redirect } from 'react-router'; import _ from 'lodash'; -import { notFoundUrl } from 'utils/routeHelpers'; import CollectionsStore from 'stores/CollectionsStore'; +import CollectionStore from './CollectionStore'; import Layout from 'components/Layout'; import CenteredContent from 'components/CenteredContent'; @@ -16,48 +16,28 @@ type Props = { match: Object, }; -type State = { - redirectUrl: ?string, -}; - @observer class Collection extends React.Component { props: Props; - state: State; + store: CollectionStore; constructor(props) { super(props); - this.state = { - redirectUrl: null, - }; + this.store = new CollectionStore(); } componentDidMount = () => { const { id } = this.props.match.params; - this.props.collections - .getById(id) - .then(collection => { - if (collection.type !== 'atlas') - throw new Error('TODO code up non-atlas collections'); - - this.setState({ - redirectUrl: collection.documents[0].url, - }); - }) - .catch(() => { - this.setState({ - redirectUrl: notFoundUrl(), - }); - }); + this.store.fetchCollection(id); }; render() { return ( - {this.state.redirectUrl && } - - - - + {this.store.redirectUrl + ? + : + + } ); } diff --git a/frontend/scenes/Collection/CollectionStore.js b/frontend/scenes/Collection/CollectionStore.js index 8aa446e8..16569b8e 100644 --- a/frontend/scenes/Collection/CollectionStore.js +++ b/frontend/scenes/Collection/CollectionStore.js @@ -1,37 +1,30 @@ // @flow -import { observable, action, computed } from 'mobx'; +import { observable, action } from 'mobx'; import invariant from 'invariant'; import { client } from 'utils/ApiClient'; -import Collection from 'models/Collection'; - -const store = new class AtlasStore { - @observable collection: ?(Collection & { recentDocuments?: Object[] }); +import { notFoundUrl } from 'utils/routeHelpers'; +class CollectionStore { + @observable redirectUrl: ?string; @observable isFetching = true; - /* Computed */ - - @computed get isLoaded(): boolean { - return !this.isFetching && !!this.collection; - } - /* Actions */ - @action fetchCollection = async (id: string, successCallback: Function) => { + @action fetchCollection = async (id: string) => { this.isFetching = true; - this.collection = null; try { const res = await client.get('/collections.info', { id }); invariant(res && res.data, 'Data should be available'); const { data } = res; - this.collection = new Collection(data); - successCallback(data); + + if (data.type === 'atlas') this.redirectUrl = data.documents[0].url; + else throw new Error('TODO code up non-atlas collections'); } catch (e) { - console.error('Something went wrong'); + this.redirectUrl = notFoundUrl(); } this.isFetching = false; }; -}(); +} -export default store; +export default CollectionStore; diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index 2e1c6fc0..256e2142 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -2,10 +2,12 @@ import React, { Component } from 'react'; import get from 'lodash/get'; import styled from 'styled-components'; -import { observer } from 'mobx-react'; +import { observer, inject } from 'mobx-react'; import { withRouter, Prompt } from 'react-router'; import { Flex } from 'reflexbox'; +import UiStore from 'stores/UiStore'; + import DocumentStore from './DocumentStore'; import Breadcrumbs from './components/Breadcrumbs'; import Menu from './components/Menu'; @@ -26,6 +28,7 @@ type Props = { history: Object, keydown: Object, newChildDocument?: boolean, + ui: UiStore, }; @observer class Document extends Component { @@ -34,10 +37,13 @@ type Props = { constructor(props: Props) { super(props); - this.store = new DocumentStore({ history: this.props.history }); + this.store = new DocumentStore({ + history: this.props.history, + ui: props.ui, + }); } - componentDidMount = () => { + componentDidMount() { if (this.props.newDocument) { this.store.collectionId = this.props.match.params.id; this.store.newDocument = true; @@ -53,7 +59,11 @@ type Props = { this.store.newDocument = false; this.store.fetchDocument(); } - }; + } + + componentWillUnmout() { + this.props.ui.clearActiveCollection(); + } onEdit = () => { const url = `${this.store.document.url}/edit`; @@ -163,4 +173,4 @@ const Container = styled.div` width: 50em; `; -export default withRouter(Document); +export default withRouter(inject('ui')(Document)); diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 745684a6..2f9d2daf 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -38,8 +38,7 @@ class CollectionsStore { } }; - @action getById = async (id: string): Promise => { - if (!this.isLoaded) await this.fetch(); + getById = (id: string): Collection => { return _.find(this.data, { id }); }; diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index 80c95352..e5c9067f 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -1,34 +1,18 @@ // @flow -import { observable, action, computed, autorunAsync } from 'mobx'; - -const UI_STORE = 'UI_STORE'; +import { observable, action } from 'mobx'; class UiStore { - @observable sidebar: boolean = false; - - /* Computed */ - - @computed get asJson(): string { - return JSON.stringify({ - sidebar: this.sidebar, - }); - } + @observable activeCollection: ?string; /* Actions */ - @action toggleSidebar = (): void => { - this.sidebar = !this.sidebar; + @action setActiveCollection = (id: string): void => { + this.activeCollection = id; }; - constructor() { - // Rehydrate - const data = JSON.parse(localStorage.getItem(UI_STORE) || '{}'); - this.sidebar = data.sidebar; - - autorunAsync(() => { - localStorage.setItem(UI_STORE, this.asJson); - }); - } + @action clearActiveCollection = (): void => { + this.activeCollection = null; + }; } export default UiStore; diff --git a/frontend/styles/base.scss b/frontend/styles/base.scss index 17be0a19..c7bd8153 100644 --- a/frontend/styles/base.scss +++ b/frontend/styles/base.scss @@ -20,11 +20,11 @@ html, body, .viewport { } body { - font-family: -apple-system, 'Helvetica Neue', Helvetica, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; font-size: 15px; line-height: 1.5; margin: 0; - color: $textColor; + color: #617180; background-color: #fff; display: flex; position: absolute; @@ -32,6 +32,10 @@ body { right: 0; bottom: 0; left: 0; + + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } img { max-width: 100%; @@ -41,7 +45,7 @@ svg { max-height: 100%; } a { - color: $actionColor; + color: #005AA6; text-decoration: none; cursor: pointer; } diff --git a/server/static/index.html b/server/static/index.html index e732e8ce..21c5637e 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -1,36 +1,19 @@ - - Atlas - + - .container { - display: flex; - flex; - } + + - .header { - display: flex; - flex: 1; - height: 42px; - border-bottom: 1px solid #eee; - } - - - -
-
-
-
-
- - + \ No newline at end of file