Renaming atlases to collections

This commit is contained in:
Jori Lallo
2016-08-05 18:09:14 +03:00
parent cf1563eb2f
commit c753382571
14 changed files with 119 additions and 119 deletions

View File

@ -26,11 +26,11 @@ class AtlasPreview extends React.Component {
<DocumentLink document={ document } key={ document.id } />) <DocumentLink document={ document } key={ document.id } />)
}) })
: ( : (
<div className={ styles.description }>No documents. Why not <Link to={ `/atlas/${data.id}/new` }>create one</Link>?</div> <div className={ styles.description }>No documents. Why not <Link to={ `/collections/${data.id}/new` }>create one</Link>?</div>
) } ) }
</div> </div>
); );
} }
}; };
export default AtlasPreview; export default AtlasPreview;

View File

@ -13,11 +13,11 @@ class FullscreenField extends React.Component {
<div className={ styles.container }> <div className={ styles.container }>
<CenteredContent> <CenteredContent>
<div className={ styles.content }> <div className={ styles.content }>
<h2>Create a new atlas</h2> <h2>Create a new collection</h2>
<p>Atlases are collections where you, your teams or friends can share and collect information.</p> <p>Collections are spaces where you, your teams or friends can share and collect information.</p>
<div className={ styles.field }> <div className={ styles.field }>
<div className={ styles.label }>Atlas name</div> <div className={ styles.label }>Collection name</div>
<input type="text" placeholder="Meeting notes" /> <input type="text" placeholder="Meeting notes" />
</div> </div>
@ -27,7 +27,7 @@ class FullscreenField extends React.Component {
</div> </div>
<div className={ styles.field }> <div className={ styles.field }>
<button className={ styles.button }>Create atlas</button> <button className={ styles.button }>Create collection</button>
</div> </div>
</div> </div>
</CenteredContent> </CenteredContent>
@ -36,4 +36,4 @@ class FullscreenField extends React.Component {
} }
} }
export default FullscreenField; export default FullscreenField;

View File

@ -50,9 +50,9 @@ render((
<IndexRoute component={ Home } /> <IndexRoute component={ Home } />
<Route path="/dashboard" component={ Dashboard } onEnter={ requireAuth } /> <Route path="/dashboard" component={ Dashboard } onEnter={ requireAuth } />
<Route path="/atlas/:id" component={ Atlas } onEnter={ requireAuth } /> <Route path="/collections/:id" component={ Atlas } onEnter={ requireAuth } />
<Route <Route
path="/atlas/:id/new" path="/collections/:id/new"
component={ DocumentEdit } component={ DocumentEdit }
onEnter={ requireAuth } onEnter={ requireAuth }
newDocument newDocument

View File

@ -21,13 +21,13 @@ import styles from './Atlas.scss';
class Atlas extends React.Component { class Atlas extends React.Component {
componentDidMount = () => { componentDidMount = () => {
const { id } = this.props.params; const { id } = this.props.params;
store.fetchAtlas(id, data => { store.fetchCollection(id, data => {
// Forward directly to root document // Forward directly to root document
if (data.type === 'atlas') { if (data.type === 'atlas') {
browserHistory.replace(data.navigationTree.url); browserHistory.replace(data.navigationTree.url);
} }
}) });
} }
componentWillReceiveProps = (nextProps) => { componentWillReceiveProps = (nextProps) => {
@ -41,17 +41,17 @@ class Atlas extends React.Component {
onCreate = (event) => { onCreate = (event) => {
if (event) event.preventDefault(); if (event) event.preventDefault();
browserHistory.push(`/atlas/${store.atlas.id}/new`); browserHistory.push(`/collections/${store.collection.id}/new`);
} }
render() { render() {
const atlas = store.atlas; const collection = store.collection;
let actions; let actions;
let title; let title;
let titleText; let titleText;
if (atlas) { if (collection) {
actions = ( actions = (
<Flex direction="row"> <Flex direction="row">
<DropdownMenu label={ <MoreIcon /> } > <DropdownMenu label={ <MoreIcon /> } >
@ -61,8 +61,8 @@ class Atlas extends React.Component {
</DropdownMenu> </DropdownMenu>
</Flex> </Flex>
); );
title = <Title>{ atlas.name }</Title>; title = <Title>{ collection.name }</Title>;
titleText = atlas.name; titleText = collection.name;
} }
return ( return (
@ -77,15 +77,15 @@ class Atlas extends React.Component {
) : ( ) : (
<div className={ styles.container }> <div className={ styles.container }>
<div className={ styles.atlasDetails }> <div className={ styles.atlasDetails }>
<h2>{ atlas.name }</h2> <h2>{ collection.name }</h2>
<blockquote> <blockquote>
{ atlas.description } { collection.description }
</blockquote> </blockquote>
</div> </div>
<Divider /> <Divider />
<DocumentList documents={ atlas.recentDocuments } preview={ true } /> <DocumentList documents={ collection.recentDocuments } preview />
</div> </div>
) } ) }
</CenteredContent> </CenteredContent>

View File

@ -2,20 +2,20 @@ import { observable, action } from 'mobx';
import { client } from 'utils/ApiClient'; import { client } from 'utils/ApiClient';
const store = new class AtlasStore { const store = new class AtlasStore {
@observable atlas; @observable collection;
@observable isFetching = true; @observable isFetching = true;
/* Actions */ /* Actions */
@action fetchAtlas = async (id, successCallback) => { @action fetchCollection = async (id, successCallback) => {
this.isFetching = true; this.isFetching = true;
this.atlas = null; this.collection = null;
try { try {
const res = await client.get('/atlases.info', { id: id }); const res = await client.get('/collections.info', { id });
const { data } = res; const { data } = res;
this.atlas = data; this.collection = data;
successCallback(data); successCallback(data);
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error("Something went wrong");

View File

@ -24,7 +24,7 @@ class Dashboard extends React.Component {
} }
componentDidMount = () => { componentDidMount = () => {
store.fetchAtlases(this.props.user.team.id); store.fetchCollections(this.props.user.team.id);
} }
onClickNewAtlas = () => { onClickNewAtlas = () => {
@ -53,8 +53,8 @@ class Dashboard extends React.Component {
<Flex direction="column" flex> <Flex direction="column" flex>
{ store.isFetching ? ( { store.isFetching ? (
<AtlasPreviewLoading /> <AtlasPreviewLoading />
) : store.atlases && store.atlases.map((atlas) => { ) : store.collections && store.collections.map((collection) => {
return (<AtlasPreview key={ atlas.id } data={ atlas } />); return (<AtlasPreview key={ collection.id } data={ collection } />);
}) } }) }
</Flex> </Flex>
</CenteredContent> </CenteredContent>

View File

@ -2,21 +2,21 @@ import { observable, action, runInAction } from 'mobx';
import { client, cacheResponse } from 'utils/ApiClient'; import { client, cacheResponse } from 'utils/ApiClient';
const store = new class DashboardStore { const store = new class DashboardStore {
@observable atlases; @observable collections;
@observable pagination; @observable pagination;
@observable isFetching = true; @observable isFetching = true;
/* Actions */ /* Actions */
@action fetchAtlases = async (teamId) => { @action fetchCollections = async (teamId) => {
this.isFetching = true; this.isFetching = true;
try { try {
const res = await client.post('/atlases.list', { id: teamId }); const res = await client.post('/collections.list', { id: teamId });
const { data, pagination } = res; const { data, pagination } = res;
runInAction('fetchAtlases', () => { runInAction('fetchCollections', () => {
this.atlases = data; this.collections = data;
this.pagination = pagination; this.pagination = pagination;
data.forEach((collection) => cacheResponse(collection.recentDocuments)); data.forEach((collection) => cacheResponse(collection.recentDocuments));
}); });

View File

@ -47,7 +47,7 @@ class DocumentEdit extends Component {
componentDidMount = () => { componentDidMount = () => {
if (this.props.route.newDocument) { if (this.props.route.newDocument) {
this.store.atlasId = this.props.params.id; this.store.collectionId = this.props.params.id;
this.store.newDocument = true; this.store.newDocument = true;
} else if (this.props.route.newChildDocument) { } else if (this.props.route.newChildDocument) {
this.store.documentId = this.props.params.id; this.store.documentId = this.props.params.id;

View File

@ -21,7 +21,7 @@ const parseHeader = (text) => {
class DocumentEditStore { class DocumentEditStore {
@observable documentId = null; @observable documentId = null;
@observable atlasId = null; @observable collectionId = null;
@observable parentDocument; @observable parentDocument;
@observable title; @observable title;
@observable text; @observable text;
@ -63,7 +63,7 @@ class DocumentEditStore {
try { try {
const data = await client.post('/documents.create', { const data = await client.post('/documents.create', {
parentDocument: this.parentDocument && this.parentDocument.id, parentDocument: this.parentDocument && this.parentDocument.id,
atlas: this.atlasId || this.parentDocument.atlas.id, collection: this.collectionId || this.parentDocument.collection.id,
title: this.title, title: this.title,
text: this.text, text: this.text,
}, { cache: true }); }, { cache: true });

View File

@ -103,7 +103,7 @@ class DocumentScene extends React.Component {
onDelete = () => { onDelete = () => {
let msg; let msg;
if (this.store.document.atlas.type === 'atlas') { if (this.store.document.collection.type === 'atlas') {
msg = 'Are you sure you want to delete this document and all it\'s child documents (if any)?'; msg = 'Are you sure you want to delete this document and all it\'s child documents (if any)?';
} else { } else {
msg = 'Are you sure you want to delete this document?'; msg = 'Are you sure you want to delete this document?';
@ -141,8 +141,8 @@ class DocumentScene extends React.Component {
} = this.props.ui; } = this.props.ui;
const doc = this.store.document; const doc = this.store.document;
const allowDelete = doc && doc.atlas.type === 'atlas' && const allowDelete = doc && doc.collection.type === 'atlas' &&
doc.id !== doc.atlas.navigationTree.id; doc.id !== doc.collection.navigationTree.id;
let title; let title;
let titleText; let titleText;
let actions; let actions;
@ -150,7 +150,8 @@ class DocumentScene extends React.Component {
actions = ( actions = (
<div className={ styles.actions }> <div className={ styles.actions }>
<DropdownMenu label={ <MoreIcon /> }> <DropdownMenu label={ <MoreIcon /> }>
{ this.store.isAtlas && <MenuItem onClick={ this.onCreate }>New document</MenuItem> } { this.store.isCollection &&
<MenuItem onClick={ this.onCreate }>New document</MenuItem> }
<MenuItem onClick={ this.onEdit }>Edit</MenuItem> <MenuItem onClick={ this.onEdit }>Edit</MenuItem>
<MenuItem onClick={ this.onExport }>Export</MenuItem> <MenuItem onClick={ this.onExport }>Export</MenuItem>
{ allowDelete && <MenuItem onClick={ this.onDelete }>Delete</MenuItem> } { allowDelete && <MenuItem onClick={ this.onDelete }>Delete</MenuItem> }
@ -159,11 +160,11 @@ class DocumentScene extends React.Component {
); );
title = ( title = (
<span> <span>
<Link to={ `/atlas/${doc.atlas.id}` }>{ doc.atlas.name }</Link> <Link to={ `/collections/${doc.collection.id}` }>{ doc.collection.name }</Link>
{ ` / ${doc.title}` } { ` / ${doc.title}` }
</span> </span>
); );
titleText = `${doc.atlas.name} - ${doc.title}`; titleText = `${doc.collection.name} - ${doc.title}`;
} }
return ( return (
@ -179,13 +180,13 @@ class DocumentScene extends React.Component {
</CenteredContent> </CenteredContent>
) : ( ) : (
<Flex flex> <Flex flex>
{ this.store.isAtlas && ( { this.store.isCollection && (
<Flex> <Flex>
{ sidebar && ( { sidebar && (
<div className={ cx(styles.sidebar) }> <div className={ cx(styles.sidebar) }>
<Tree <Tree
paddingLeft={ 10 } paddingLeft={ 10 }
tree={ toJS(this.store.atlasTree) } tree={ toJS(this.store.collectionTree) }
onChange={ this.store.updateNavigationTree } onChange={ this.store.updateNavigationTree }
onCollapse={ this.store.onNodeCollapse } onCollapse={ this.store.onNodeCollapse }
isNodeCollapsed={ this.isNodeCollapsed } isNodeCollapsed={ this.isNodeCollapsed }

View File

@ -20,14 +20,14 @@ class DocumentSceneStore {
/* Computed */ /* Computed */
@computed get isAtlas() { @computed get isCollection() {
return this.document && return this.document &&
this.document.atlas.type === 'atlas'; this.document.collection.type === 'atlas';
} }
@computed get atlasTree() { @computed get collectionTree() {
if (!this.document || this.document.atlas.type !== 'atlas') return; if (!this.document || this.document.collection.type !== 'atlas') return;
const tree = this.document.atlas.navigationTree; const tree = this.document.collection.navigationTree;
const collapseNodes = (node) => { const collapseNodes = (node) => {
if (this.collapsedNodes.includes(node.id)) { if (this.collapsedNodes.includes(node.id)) {
@ -74,7 +74,7 @@ class DocumentSceneStore {
try { try {
await client.post('/documents.delete', { id: this.document.id }); await client.post('/documents.delete', { id: this.document.id });
browserHistory.push(`/atlas/${this.document.atlas.id}`); browserHistory.push(`/collections/${this.document.collection.id}`);
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error("Something went wrong");
} }
@ -83,20 +83,20 @@ class DocumentSceneStore {
@action updateNavigationTree = async (tree) => { @action updateNavigationTree = async (tree) => {
// Only update when tree changes // Only update when tree changes
if (_isEqual(toJS(tree), toJS(this.document.atlas.navigationTree))) { if (_isEqual(toJS(tree), toJS(this.document.collection.navigationTree))) {
return true; return true;
} }
this.updatingStructure = true; this.updatingStructure = true;
try { try {
const res = await client.post('/atlases.updateNavigationTree', { const res = await client.post('/collections.updateNavigationTree', {
id: this.document.atlas.id, id: this.document.collection.id,
tree, tree,
}); });
runInAction('updateNavigationTree', () => { runInAction('updateNavigationTree', () => {
const { data } = res; const { data } = res;
this.document.atlas = data; this.document.collection = data;
}); });
} catch (e) { } catch (e) {
console.error("Something went wrong"); console.error("Something went wrong");

View File

@ -4,12 +4,12 @@ import _orderBy from 'lodash.orderby';
import auth from './authentication'; import auth from './authentication';
import pagination from './middlewares/pagination'; import pagination from './middlewares/pagination';
import { presentAtlas } from '../presenters'; import { presentCollection } from '../presenters';
import { Atlas } from '../models'; import { Atlas } from '../models';
const router = new Router(); const router = new Router();
router.post('atlases.create', auth(), async (ctx) => { router.post('collections.create', auth(), async (ctx) => {
const { const {
name, name,
description, description,
@ -28,11 +28,11 @@ router.post('atlases.create', auth(), async (ctx) => {
}); });
ctx.body = { ctx.body = {
data: await presentAtlas(atlas, true), data: await presentCollection(atlas, true),
}; };
}); });
router.post('atlases.info', auth(), async (ctx) => { router.post('collections.info', auth(), async (ctx) => {
const { id } = ctx.body; const { id } = ctx.body;
ctx.assertPresent(id, 'id is required'); ctx.assertPresent(id, 'id is required');
@ -47,14 +47,14 @@ router.post('atlases.info', auth(), async (ctx) => {
if (!atlas) throw httpErrors.NotFound(); if (!atlas) throw httpErrors.NotFound();
ctx.body = { ctx.body = {
data: await presentAtlas(atlas, true), data: await presentCollection(atlas, true),
}; };
}); });
router.post('atlases.list', auth(), pagination(), async (ctx) => { router.post('collections.list', auth(), pagination(), async (ctx) => {
const user = ctx.state.user; const user = ctx.state.user;
const atlases = await Atlas.findAll({ const collections = await Atlas.findAll({
where: { where: {
teamId: user.teamId, teamId: user.teamId,
}, },
@ -67,8 +67,8 @@ router.post('atlases.list', auth(), pagination(), async (ctx) => {
// Atlases // Atlases
let data = []; let data = [];
await Promise.all(atlases.map(async (atlas) => { await Promise.all(collections.map(async (atlas) => {
return data.push(await presentAtlas(atlas, true)); return data.push(await presentCollection(atlas, true));
})); }));
data = _orderBy(data, ['updatedAt'], ['desc']); data = _orderBy(data, ['updatedAt'], ['desc']);
@ -79,7 +79,7 @@ router.post('atlases.list', auth(), pagination(), async (ctx) => {
}; };
}); });
router.post('atlases.updateNavigationTree', auth(), async (ctx) => { router.post('collections.updateNavigationTree', auth(), async (ctx) => {
const { id, tree } = ctx.body; const { id, tree } = ctx.body;
ctx.assertPresent(id, 'id is required'); ctx.assertPresent(id, 'id is required');
@ -96,7 +96,7 @@ router.post('atlases.updateNavigationTree', auth(), async (ctx) => {
await atlas.updateNavigationTree(tree); await atlas.updateNavigationTree(tree);
ctx.body = { ctx.body = {
data: await presentAtlas(atlas, true), data: await presentCollection(atlas, true),
}; };
}); });

View File

@ -82,19 +82,19 @@ router.post('documents.search', auth(), async (ctx) => {
router.post('documents.create', auth(), async (ctx) => { router.post('documents.create', auth(), async (ctx) => {
const { const {
atlas, collection,
title, title,
text, text,
parentDocument, parentDocument,
} = ctx.body; } = ctx.body;
ctx.assertPresent(atlas, 'atlas is required'); ctx.assertPresent(collection, 'collection is required');
ctx.assertPresent(title, 'title is required'); ctx.assertPresent(title, 'title is required');
ctx.assertPresent(text, 'text is required'); ctx.assertPresent(text, 'text is required');
const user = ctx.state.user; const user = ctx.state.user;
const ownerAtlas = await Atlas.findOne({ const ownerAtlas = await Atlas.findOne({
where: { where: {
id: atlas, id: collection,
teamId: user.teamId, teamId: user.teamId,
}, },
}); });
@ -161,9 +161,9 @@ router.post('documents.update', auth(), async (ctx) => {
await document.createRevision(); await document.createRevision();
// Update // Update
const atlas = await Atlas.findById(document.atlasId); const collection = await Atlas.findById(document.atlasId);
if (atlas.type === 'atlas') { if (collection.type === 'atlas') {
await atlas.updateNavigationTree(); await collection.updateNavigationTree();
} }
ctx.body = { ctx.body = {
@ -184,11 +184,11 @@ router.post('documents.delete', auth(), async (ctx) => {
teamId: user.teamId, teamId: user.teamId,
}, },
}); });
const atlas = await Atlas.findById(document.atlasId); const collection = await Atlas.findById(document.atlasId);
if (!document) throw httpErrors.BadRequest(); if (!document) throw httpErrors.BadRequest();
if (atlas.type === 'atlas') { if (collection.type === 'atlas') {
// Don't allow deletion of root docs // Don't allow deletion of root docs
if (!document.parentDocumentId) { if (!document.parentDocumentId) {
throw httpErrors.BadRequest('Unable to delete atlas\'s root document'); throw httpErrors.BadRequest('Unable to delete atlas\'s root document');
@ -196,8 +196,8 @@ router.post('documents.delete', auth(), async (ctx) => {
// Delete all chilren // Delete all chilren
try { try {
await atlas.deleteDocument(document); await collection.deleteDocument(document);
await atlas.save(); await collection.save();
} catch (e) { } catch (e) {
throw httpErrors.BadRequest('Error while deleting'); throw httpErrors.BadRequest('Error while deleting');
} }

View File

@ -2,7 +2,7 @@ import _orderBy from 'lodash.orderby';
import { Document, Atlas } from './models'; import { Document, Atlas } from './models';
export function presentUser(user) { export function presentUser(user) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, _reject) => {
const data = { const data = {
id: user.id, id: user.id,
name: user.name, name: user.name,
@ -14,7 +14,7 @@ export function presentUser(user) {
} }
export function presentTeam(team) { export function presentTeam(team) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, _reject) => {
resolve({ resolve({
id: team.id, id: team.id,
name: team.name, name: team.name,
@ -22,42 +22,7 @@ export function presentTeam(team) {
}); });
} }
export function presentAtlas(atlas, includeRecentDocuments=false) { export async function presentDocument(document, includeCollection = false) {
return new Promise(async (resolve, reject) => {
const data = {
id: atlas.id,
name: atlas.name,
description: atlas.description,
type: atlas.type,
}
if (atlas.type === 'atlas') {
data.navigationTree = await atlas.getStructure();
}
if (includeRecentDocuments) {
const documents = await Document.findAll({
where: {
atlasId: atlas.id,
},
limit: 10,
order: [
['updatedAt', 'DESC'],
],
});
let recentDocuments = [];
await Promise.all(documents.map(async (document) => {
recentDocuments.push(await presentDocument(document, true));
}))
data.recentDocuments = _orderBy(recentDocuments, ['updatedAt'], ['desc']);
}
resolve(data);
});
}
export async function presentDocument(document, includeAtlas=false) {
const data = { const data = {
id: document.id, id: document.id,
url: document.buildUrl(), url: document.buildUrl(),
@ -66,18 +31,17 @@ export async function presentDocument(document, includeAtlas=false) {
text: document.text, text: document.text,
html: document.html, html: document.html,
preview: document.preview, preview: document.preview,
private: document.private,
createdAt: document.createdAt, createdAt: document.createdAt,
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
atlas: document.atlasId, collection: document.atlasId,
team: document.teamId, team: document.teamId,
} };
if (includeAtlas) { if (includeCollection) {
const atlas = await Atlas.findOne({ where: { const collection = await Atlas.findOne({ where: {
id: document.atlasId, id: document.atlasId,
}}); } });
data.atlas = await presentAtlas(atlas, false); data.collection = await presentCollection(collection, false);
} }
const user = await document.getUser(); const user = await document.getUser();
@ -85,3 +49,38 @@ export async function presentDocument(document, includeAtlas=false) {
return data; return data;
} }
export function presentCollection(collection, includeRecentDocuments=false) {
return new Promise(async (resolve, _reject) => {
const data = {
id: collection.id,
name: collection.name,
description: collection.description,
type: collection.type,
};
if (collection.type === 'atlas') {
data.navigationTree = await collection.getStructure();
}
if (includeRecentDocuments) {
const documents = await Document.findAll({
where: {
atlasId: collection.id,
},
limit: 10,
order: [
['updatedAt', 'DESC'],
],
});
const recentDocuments = [];
await Promise.all(documents.map(async (document) => {
recentDocuments.push(await presentDocument(document, true));
}));
data.recentDocuments = _orderBy(recentDocuments, ['updatedAt'], ['desc']);
}
resolve(data);
});
}