CollectionNew scene

This commit is contained in:
Tom Moor
2017-07-09 10:27:29 -07:00
parent 98da54d82a
commit f456dc6b6a
12 changed files with 164 additions and 72 deletions

View File

@ -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 = {
},
]}
/>
<Modals
name={this.props.ui.modalName}
component={this.props.ui.modalComponent}
onRequestClose={this.props.ui.closeModal}
{...this.props.ui.modalProps}
/>
{this.props.ui.progressBarVisible && <LoadingIndicatorBar />}
@ -122,7 +123,7 @@ type Props = {
<SidebarLink to="/dashboard">Home</SidebarLink>
<SidebarLink to="/starred">Starred</SidebarLink>
</LinkSection>
<a onClick={this.createNewCollection}>
<a onClick={this.handleCreateCollection}>
Create new collection
</a>
<LinkSection>
@ -142,6 +143,12 @@ type Props = {
{this.props.children}
</Content>
</Flex>
<Modal
isOpen={this.state.createCollectionModalOpen}
onRequestClose={this.handleCloseModal}
>
<CollectionNew />
</Modal>
</Container>
);
}

View File

@ -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 (
<Modal
isOpen={isOpen}
contentLabel={name}
onRequestClose={onRequestClose}
>
<button onClick={onRequestClose}>Close</button>
{isOpen && <ModalComponent {...rest} />}
</Modal>
);
}
}
export default Modals;

View File

@ -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 (
<ReactModal
contentLabel={title}
onRequestClose={onRequestClose}
{...rest}
>
<Header>
<button onClick={onRequestClose}>Close</button>
{title}
</Header>
{children}
</ReactModal>
);
}
}
const Header = styled.div`
text-align: center;
font-weight: semibold;
`;
export default Modal;

View File

@ -0,0 +1,3 @@
// @flow
import Modal from './Modal';
export default Modal;

View File

@ -1,5 +0,0 @@
// @flow
// All components wishing to be used as modals must be defined below
import NewCollection from './NewCollection';
export default { NewCollection };

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 (
<form onSubmit={this.handleSubmit}>
{collection.errors.errors.map(error => <span>{error}</span>)}
<Input
type="text"
placeholder="Name"
onChange={this.handleNameChange}
value={collection.name}
autoFocus
/>
<Input
type="textarea"
placeholder="Description (optional)"
onChange={this.handleDescriptionChange}
value={collection.description}
/>
<Button type="submit" disabled={collection.isSaving}>
{collection.isSaving ? 'Creating…' : 'Create'}
</Button>
</form>
);
}
}
export default CollectionNew;

View File

@ -0,0 +1,3 @@
// @flow
import CollectionNew from './CollectionNew';
export default CollectionNew;

View File

@ -120,7 +120,7 @@ type Props = {
onChange = text => {
if (!this.document) return;
this.document.updateData({ text, hasPendingChanges: true });
this.document.updateData({ text });
};
onCancel = () => {

View File

@ -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;

View File

@ -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<any> {
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;
}