From d308442fef9a4c6f91d9a636c8682cdee7eff602 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 10 Aug 2018 23:03:47 -0700 Subject: [PATCH] Refactor, paginate on scroll New PaginatedDocumentList component --- app/components/DocumentList.js | 34 +++++++++ app/components/DocumentList/DocumentList.js | 37 ---------- app/components/DocumentList/index.js | 3 - app/components/PaginatedDocumentList.js | 78 +++++++++++++++++++++ app/scenes/Collection.js | 2 +- app/scenes/Dashboard.js | 61 ++++++---------- app/stores/DocumentsStore.js | 30 ++++---- server/api/documents.js | 2 +- 8 files changed, 152 insertions(+), 95 deletions(-) create mode 100644 app/components/DocumentList.js delete mode 100644 app/components/DocumentList/DocumentList.js delete mode 100644 app/components/DocumentList/index.js create mode 100644 app/components/PaginatedDocumentList.js diff --git a/app/components/DocumentList.js b/app/components/DocumentList.js new file mode 100644 index 000000000..78c67d7f4 --- /dev/null +++ b/app/components/DocumentList.js @@ -0,0 +1,34 @@ +// @flow +import * as React from 'react'; +import Document from 'models/Document'; +import DocumentPreview from 'components/DocumentPreview'; +import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; + +type Props = { + documents: Document[], + showCollection?: boolean, + limit?: number, +}; + +export default function DocumentList({ + limit, + showCollection, + documents, +}: Props) { + const items = limit ? documents.splice(0, limit) : documents; + + return ( + + {items.map(document => ( + + ))} + + ); +} diff --git a/app/components/DocumentList/DocumentList.js b/app/components/DocumentList/DocumentList.js deleted file mode 100644 index 97e53008a..000000000 --- a/app/components/DocumentList/DocumentList.js +++ /dev/null @@ -1,37 +0,0 @@ -// @flow -import * as React from 'react'; -import Document from 'models/Document'; -import DocumentPreview from 'components/DocumentPreview'; -import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; - -type Props = { - documents: Document[], - showCollection?: boolean, - limit?: number, -}; - -class DocumentList extends React.Component { - render() { - const { limit, showCollection } = this.props; - const documents = limit - ? this.props.documents.splice(0, limit) - : this.props.documents; - - return ( - - {documents.map(document => ( - - ))} - - ); - } -} - -export default DocumentList; diff --git a/app/components/DocumentList/index.js b/app/components/DocumentList/index.js deleted file mode 100644 index 35fb670b4..000000000 --- a/app/components/DocumentList/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import DocumentList from './DocumentList'; -export default DocumentList; diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.js new file mode 100644 index 000000000..1ad102e5c --- /dev/null +++ b/app/components/PaginatedDocumentList.js @@ -0,0 +1,78 @@ +// @flow +import * as React from 'react'; +import { observable, action } from 'mobx'; +import { observer } from 'mobx-react'; +import Waypoint from 'react-waypoint'; + +import { DEFAULT_PAGINATION_LIMIT } from 'stores/DocumentsStore'; +import Document from 'models/Document'; +import DocumentList from 'components/DocumentList'; +import { ListPlaceholder } from 'components/LoadingPlaceholder'; + +type Props = { + showCollection?: boolean, + documents: Document[], + fetch: (options: ?Object) => Promise<*>, + options?: Object, +}; + +@observer +class PaginatedDocumentList extends React.Component { + @observable isLoaded: boolean = false; + @observable isFetching: boolean = false; + @observable offset: number = 0; + @observable allowLoadMore: boolean = true; + + componentDidMount() { + this.fetchResults(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.fetch !== this.props.fetch) { + this.fetchResults(); + } + } + + fetchResults = async () => { + this.isFetching = true; + + const limit = DEFAULT_PAGINATION_LIMIT; + const results = await this.props.fetch({ limit, ...this.props.options }); + + if ( + results && + (results.length === 0 || results.length < DEFAULT_PAGINATION_LIMIT) + ) { + this.allowLoadMore = false; + } else { + this.offset += DEFAULT_PAGINATION_LIMIT; + } + + this.isLoaded = true; + this.isFetching = false; + }; + + @action + loadMoreResults = async () => { + // Don't paginate if there aren't more results or we’re in the middle of fetching + if (!this.allowLoadMore || this.isFetching) return; + await this.fetchResults(); + }; + + render() { + const { showCollection, documents } = this.props; + + return this.isLoaded || documents.length ? ( + + + {this.allowLoadMore && ( + + )} + + ) : ( + + ); + } +} + +export default PaginatedDocumentList; diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index 1de8ea2a5..99cd8e2da 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -61,7 +61,7 @@ class CollectionScene extends React.Component { this.collection = collection; await Promise.all([ - this.props.documents.fetchRecentlyModified({ + this.props.documents.fetchRecentlyEdited({ limit: 10, collection: id, }), diff --git a/app/scenes/Dashboard.js b/app/scenes/Dashboard.js index 0c6f67f6e..38f98a1bc 100644 --- a/app/scenes/Dashboard.js +++ b/app/scenes/Dashboard.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react'; import { Switch, Route } from 'react-router-dom'; -import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; import { NewDocumentIcon } from 'outline-icons'; @@ -10,11 +9,10 @@ import AuthStore from 'stores/AuthStore'; import NewDocumentMenu from 'menus/NewDocumentMenu'; import Actions, { Action } from 'components/Actions'; import CenteredContent from 'components/CenteredContent'; -import DocumentList from 'components/DocumentList'; import PageTitle from 'components/PageTitle'; import Tabs from 'components/Tabs'; import Tab from 'components/Tab'; -import { ListPlaceholder } from 'components/LoadingPlaceholder'; +import PaginatedDocumentList from '../components/PaginatedDocumentList'; type Props = { documents: DocumentsStore, @@ -23,41 +21,10 @@ type Props = { @observer class Dashboard extends React.Component { - @observable isLoaded: boolean = false; - - componentDidMount() { - this.loadContent(); - } - - loadContent = async () => { - const { auth } = this.props; - const user = auth.user ? auth.user.id : undefined; - - await Promise.all([ - this.props.documents.fetchRecentlyModified({ limit: 15 }), - this.props.documents.fetchRecentlyViewed({ limit: 15 }), - this.props.documents.fetchOwned({ limit: 15, user }), - ]); - this.isLoaded = true; - }; - - renderTab = (path, documents) => { - return ( - - {this.isLoaded || documents.length ? ( - - ) : ( - - )} - - ); - }; - render() { const { documents, auth } = this.props; if (!auth.user) return; - - const createdDocuments = documents.owned(auth.user.id); + const user = auth.user.id; return ( @@ -73,9 +40,27 @@ class Dashboard extends React.Component { Created by me - {this.renderTab('/dashboard/recent', documents.recentlyViewed)} - {this.renderTab('/dashboard/created', createdDocuments)} - {this.renderTab('/dashboard', documents.recentlyEdited)} + + + + + + + + + diff --git a/app/stores/DocumentsStore.js b/app/stores/DocumentsStore.js index 79c1e3b9a..eb940512b 100644 --- a/app/stores/DocumentsStore.js +++ b/app/stores/DocumentsStore.js @@ -21,8 +21,8 @@ type FetchOptions = { }; class DocumentsStore extends BaseStore { - @observable recentlyViewedIds: Array = []; - @observable recentlyEditedIds: Array = []; + @observable recentlyViewedIds: string[] = []; + @observable recentlyEditedIds: string[] = []; @observable data: Map = new ObservableMap([]); @observable isLoaded: boolean = false; @observable isFetching: boolean = false; @@ -49,7 +49,7 @@ class DocumentsStore extends BaseStore { return docs; } - owned(userId: string): Document[] { + createdByUser(userId: string): Document[] { return _.orderBy( _.filter( this.data.values(), @@ -127,10 +127,10 @@ class DocumentsStore extends BaseStore { }; @action - fetchRecentlyModified = async (options: ?PaginationParams): Promise<*> => { + fetchRecentlyEdited = async (options: ?PaginationParams): Promise<*> => { const data = await this.fetchPage('list', options); - runInAction('DocumentsStore#fetchRecentlyModified', () => { + runInAction('DocumentsStore#fetchRecentlyEdited', () => { this.recentlyEditedIds = _.map(data, 'id'); }); return data; @@ -147,23 +147,23 @@ class DocumentsStore extends BaseStore { }; @action - fetchStarred = async (options: ?PaginationParams): Promise<*> => { - await this.fetchPage('starred', options); + fetchStarred = (options: ?PaginationParams): Promise<*> => { + return this.fetchPage('starred', options); }; @action - fetchDrafts = async (options: ?PaginationParams): Promise<*> => { - await this.fetchPage('drafts', options); + fetchDrafts = (options: ?PaginationParams): Promise<*> => { + return this.fetchPage('drafts', options); }; @action - fetchPinned = async (options: ?PaginationParams): Promise<*> => { - await this.fetchPage('pinned', options); + fetchPinned = (options: ?PaginationParams): Promise<*> => { + return this.fetchPage('pinned', options); }; @action - fetchOwned = async (options: ?PaginationParams): Promise<*> => { - await this.fetchPage('list', options); + fetchOwned = (options: ?PaginationParams): Promise<*> => { + return this.fetchPage('list', options); }; @action @@ -275,11 +275,11 @@ class DocumentsStore extends BaseStore { // Re-fetch dashboard content so that we don't show deleted documents this.on('collections.delete', () => { - this.fetchRecentlyModified(); + this.fetchRecentlyEdited(); this.fetchRecentlyViewed(); }); this.on('documents.delete', () => { - this.fetchRecentlyModified(); + this.fetchRecentlyEdited(); this.fetchRecentlyViewed(); }); } diff --git a/server/api/documents.js b/server/api/documents.js index f582275b3..cccd902d3 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -19,7 +19,7 @@ router.post('documents.list', auth(), pagination(), async ctx => { let where = { teamId: ctx.state.user.teamId }; if (collection) where = { ...where, collectionId: collection }; - if (user) where = { ...where, userId: collection }; + if (user) where = { ...where, createdById: user }; const starredScope = { method: ['withStarred', ctx.state.user.id] }; const documents = await Document.scope('defaultScope', starredScope).findAll({