diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js
index a92b587a..0fdf3d97 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 891aa5ca..00000000
--- 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 00000000..3735386d
--- /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 (
+
+
+ {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 00000000..90759335
--- /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 c409daae..00000000
--- 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 adc663c4..bc1755cd 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 db35d742..37cca990 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 00000000..e49ff931
--- /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 (
+
+ );
+ }
+}
+
+export default CollectionNew;
diff --git a/frontend/scenes/CollectionNew/index.js b/frontend/scenes/CollectionNew/index.js
new file mode 100644
index 00000000..651d8d5c
--- /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 d04c08c5..bc104f2e 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 abdc8eb6..02dc9739 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 59ce9fdf..6345af8b 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;
}