From f456dc6b6ae99fc46c15348229f3e2330d8aa482 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 9 Jul 2017 10:27:29 -0700 Subject: [PATCH] CollectionNew scene --- frontend/components/Layout/Layout.js | 27 ++++++---- .../components/Layout/components/Modals.js | 24 --------- frontend/components/Modal/Modal.js | 36 +++++++++++++ frontend/components/Modal/index.js | 3 ++ frontend/components/modals.js | 5 -- frontend/models/Collection.js | 54 +++++++++++++++---- frontend/models/Document.js | 8 +-- .../scenes/CollectionNew/CollectionNew.js | 53 ++++++++++++++++++ frontend/scenes/CollectionNew/index.js | 3 ++ frontend/scenes/Document/Document.js | 2 +- frontend/stores/ErrorsStore.js | 4 +- frontend/stores/UiStore.js | 17 ------ 12 files changed, 164 insertions(+), 72 deletions(-) delete mode 100644 frontend/components/Layout/components/Modals.js create mode 100644 frontend/components/Modal/Modal.js create mode 100644 frontend/components/Modal/index.js delete mode 100644 frontend/components/modals.js create mode 100644 frontend/scenes/CollectionNew/CollectionNew.js create mode 100644 frontend/scenes/CollectionNew/index.js diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index a92b587a0..0fdf3d977 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -13,11 +13,12 @@ import DropdownMenu, { MenuItem } from 'components/DropdownMenu'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; import Avatar from 'components/Avatar'; +import Modal from 'components/Modal'; +import CollectionNew from 'scenes/CollectionNew'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; import SidebarLink from './components/SidebarLink'; -import Modals from './components/Modals'; import UserStore from 'stores/UserStore'; import AuthStore from 'stores/AuthStore'; @@ -39,6 +40,8 @@ type Props = { @observer class Layout extends React.Component { props: Props; + state: { createCollectionModalOpen: boolean }; + state = { createCollectionModalOpen: false }; static defaultProps = { search: true, @@ -60,8 +63,12 @@ type Props = { this.props.auth.logout(() => this.props.history.push('/')); }; - createNewCollection = () => { - this.props.ui.openModal('NewCollection'); + handleCreateCollection = () => { + this.setState({ createCollectionModalOpen: true }); + }; + + handleCloseModal = () => { + this.setState({ createCollectionModalOpen: false }); }; render() { @@ -78,12 +85,6 @@ type Props = { }, ]} /> - {this.props.ui.progressBarVisible && } @@ -122,7 +123,7 @@ type Props = { Home Starred - + Create new collection @@ -142,6 +143,12 @@ type Props = { {this.props.children} + + + ); } diff --git a/frontend/components/Layout/components/Modals.js b/frontend/components/Layout/components/Modals.js deleted file mode 100644 index 891aa5cae..000000000 --- a/frontend/components/Layout/components/Modals.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import Modal from 'react-modal'; - -class Modals extends Component { - render() { - const { name, component, onRequestClose, ...rest } = this.props; - const isOpen = !!component; - const ModalComponent = component; - - return ( - - - {isOpen && } - - ); - } -} - -export default Modals; diff --git a/frontend/components/Modal/Modal.js b/frontend/components/Modal/Modal.js new file mode 100644 index 000000000..3735386d2 --- /dev/null +++ b/frontend/components/Modal/Modal.js @@ -0,0 +1,36 @@ +// @flow +import React, { Component } from 'react'; +import styled from 'styled-components'; +import ReactModal from 'react-modal'; + +class Modal extends Component { + render() { + const { + children, + title = 'Untitled Modal', + onRequestClose, + ...rest + } = this.props; + + return ( + +
+ + {title} +
+ {children} +
+ ); + } +} + +const Header = styled.div` + text-align: center; + font-weight: semibold; +`; + +export default Modal; diff --git a/frontend/components/Modal/index.js b/frontend/components/Modal/index.js new file mode 100644 index 000000000..907593356 --- /dev/null +++ b/frontend/components/Modal/index.js @@ -0,0 +1,3 @@ +// @flow +import Modal from './Modal'; +export default Modal; diff --git a/frontend/components/modals.js b/frontend/components/modals.js deleted file mode 100644 index c409daaec..000000000 --- a/frontend/components/modals.js +++ /dev/null @@ -1,5 +0,0 @@ -// @flow -// All components wishing to be used as modals must be defined below -import NewCollection from './NewCollection'; - -export default { NewCollection }; diff --git a/frontend/models/Collection.js b/frontend/models/Collection.js index adc663c4d..bc1755cd6 100644 --- a/frontend/models/Collection.js +++ b/frontend/models/Collection.js @@ -3,12 +3,16 @@ import { extendObservable, action, computed, runInAction } from 'mobx'; import invariant from 'invariant'; import _ from 'lodash'; -import ApiClient, { client } from 'utils/ApiClient'; +import { client } from 'utils/ApiClient'; import stores from 'stores'; import ErrorsStore from 'stores/ErrorsStore'; import type { NavigationNode } from 'types'; class Collection { + isSaving: boolean = false; + hasPendingChanges: boolean = false; + errors: ErrorsStore; + createdAt: string; description: ?string; id: string; @@ -18,9 +22,6 @@ class Collection { updatedAt: string; url: string; - client: ApiClient; - errors: ErrorsStore; - /* Computed */ @computed get entryUrl(): string { @@ -29,26 +30,59 @@ class Collection { /* Actions */ - @action update = async () => { + @action fetch = async () => { try { - const res = await this.client.post('/collections.info', { id: this.id }); + const res = await client.post('/collections.info', { id: this.id }); invariant(res && res.data, 'API response should be available'); const { data } = res; - runInAction('Collection#update', () => { + runInAction('Collection#fetch', () => { this.updateData(data); }); } catch (e) { this.errors.add('Collection failed loading'); } + + return this; }; - updateData(data: Collection) { + @action save = async () => { + if (this.isSaving) return this; + this.isSaving = true; + + try { + let res; + if (this.id) { + res = await client.post('/collections.update', { + id: this.id, + name: this.name, + description: this.description, + }); + } else { + res = await client.post('/collections.create', { + name: this.name, + description: this.description, + }); + } + invariant(res && res.data, 'Data should be available'); + this.updateData({ + ...res.data, + hasPendingChanges: false, + }); + } catch (e) { + this.errors.add('Collection failed saving'); + } finally { + this.isSaving = false; + } + + return this; + }; + + updateData(data: Object = {}) { extendObservable(this, data); } - constructor(collection: Collection) { + constructor(collection: Object = {}) { this.updateData(collection); - this.client = client; this.errors = stores.errors; } } diff --git a/frontend/models/Document.js b/frontend/models/Document.js index db35d7423..37cca9905 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -30,6 +30,7 @@ class Document { starred: boolean = false; text: string = ''; title: string = 'Untitled document'; + parentDocument: ?Document; updatedAt: string; updatedBy: User; url: string; @@ -151,13 +152,14 @@ class Document { return this; }; - updateData(data: Object | Document) { + updateData(data: Object = {}) { if (data.text) data.title = parseHeader(data.text); + data.hasPendingChanges = true; extendObservable(this, data); } - constructor(document?: Object = {}) { - this.updateData(document); + constructor(data?: Object = {}) { + this.updateData(data); this.errors = stores.errors; } } diff --git a/frontend/scenes/CollectionNew/CollectionNew.js b/frontend/scenes/CollectionNew/CollectionNew.js new file mode 100644 index 000000000..e49ff931a --- /dev/null +++ b/frontend/scenes/CollectionNew/CollectionNew.js @@ -0,0 +1,53 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import Button from 'components/Button'; +import Input from 'components/Input'; +import Collection from 'models/Collection'; + +@observer class CollectionNew extends Component { + static defaultProps = { + collection: new Collection(), + }; + + handleSubmit = async (ev: SyntheticEvent) => { + ev.preventDefault(); + await this.props.collection.save(); + }; + + handleNameChange = (ev: SyntheticInputEvent) => { + this.props.collection.updateData({ name: ev.target.value }); + }; + + handleDescriptionChange = (ev: SyntheticInputEvent) => { + this.props.collection.updateData({ description: ev.target.value }); + }; + + render() { + const { collection } = this.props; + + return ( +
+ {collection.errors.errors.map(error => {error})} + + + +
+ ); + } +} + +export default CollectionNew; diff --git a/frontend/scenes/CollectionNew/index.js b/frontend/scenes/CollectionNew/index.js new file mode 100644 index 000000000..651d8d5c1 --- /dev/null +++ b/frontend/scenes/CollectionNew/index.js @@ -0,0 +1,3 @@ +// @flow +import CollectionNew from './CollectionNew'; +export default CollectionNew; diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js index d04c08c52..bc104f2e6 100644 --- a/frontend/scenes/Document/Document.js +++ b/frontend/scenes/Document/Document.js @@ -120,7 +120,7 @@ type Props = { onChange = text => { if (!this.document) return; - this.document.updateData({ text, hasPendingChanges: true }); + this.document.updateData({ text }); }; onCancel = () => { diff --git a/frontend/stores/ErrorsStore.js b/frontend/stores/ErrorsStore.js index abdc8eb60..02dc9739c 100644 --- a/frontend/stores/ErrorsStore.js +++ b/frontend/stores/ErrorsStore.js @@ -1,7 +1,7 @@ // @flow import { observable, action } from 'mobx'; -class UiStore { +class ErrorsStore { @observable errors = observable.array([]); /* Actions */ @@ -15,4 +15,4 @@ class UiStore { }; } -export default UiStore; +export default ErrorsStore; diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index 59ce9fdff..6345af8b0 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -2,14 +2,11 @@ import { observable, action, computed } from 'mobx'; import Document from 'models/Document'; import Collection from 'models/Collection'; -import modals from 'components/modals'; class UiStore { @observable activeDocument: ?Document; @observable progressBarVisible: boolean = false; @observable editMode: boolean = false; - @observable modalName: ?string; - @observable modalProps: ?Object; /* Computed */ @@ -17,10 +14,6 @@ class UiStore { return this.activeDocument ? this.activeDocument.collection : undefined; } - @computed get modalComponent(): ?ReactClass { - if (this.modalName) return modals[this.modalName]; - } - /* Actions */ @action setActiveDocument = (document: Document): void => { @@ -31,16 +24,6 @@ class UiStore { this.activeDocument = undefined; }; - @action openModal = (name: string, props?: Object) => { - this.modalName = name; - this.modalProps = props; - }; - - @action closeModal = () => { - this.modalName = undefined; - this.modalProps = undefined; - }; - @action enableEditMode() { this.editMode = true; }