GitLab backend built with cursor API (#1343)

This commit is contained in:
Benaiah Mischenko
2018-06-11 19:03:43 -07:00
committed by Shawn Erquhart
parent 1f94e3123d
commit b65f68efd4
29 changed files with 1364 additions and 280 deletions

View File

@ -11,7 +11,9 @@ const Entries = ({
page,
onPaginate,
isFetching,
viewStyle
viewStyle,
cursor,
handleCursorActions,
}) => {
const loadingMessages = [
'Loading Entries',
@ -25,9 +27,9 @@ const Entries = ({
collections={collections}
entries={entries}
publicFolder={publicFolder}
page={page}
onPaginate={onPaginate}
viewStyle={viewStyle}
cursor={cursor}
handleCursorActions={handleCursorActions}
/>
);
}
@ -46,6 +48,8 @@ Entries.propTypes = {
page: PropTypes.number,
isFetching: PropTypes.bool,
viewStyle: PropTypes.string,
cursor: PropTypes.any.isRequired,
handleCursorActions: PropTypes.func.isRequired,
};
export default Entries;

View File

@ -2,18 +2,26 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { loadEntries as actionLoadEntries } from 'Actions/entries';
import { partial } from 'lodash';
import {
loadEntries as actionLoadEntries,
traverseCollectionCursor as actionTraverseCollectionCursor,
} from 'Actions/entries';
import { selectEntries } from 'Reducers';
import { selectCollectionEntriesCursor } from 'Reducers/cursors';
import Cursor from 'ValueObjects/Cursor';
import Entries from './Entries';
class EntriesCollection extends React.Component {
static propTypes = {
collection: ImmutablePropTypes.map.isRequired,
publicFolder: PropTypes.string.isRequired,
page: PropTypes.number,
entries: ImmutablePropTypes.list,
isFetching: PropTypes.bool.isRequired,
viewStyle: PropTypes.string,
cursor: PropTypes.object.isRequired,
loadEntries: PropTypes.func.isRequired,
traverseCollectionCursor: PropTypes.func.isRequired,
};
componentDidMount() {
@ -30,31 +38,31 @@ class EntriesCollection extends React.Component {
}
}
handleLoadMore = page => {
const { collection, loadEntries } = this.props;
loadEntries(collection, page);
}
handleCursorActions = (cursor, action) => {
const { collection, traverseCollectionCursor } = this.props;
traverseCollectionCursor(collection, action);
};
render () {
const { collection, entries, publicFolder, page, isFetching, viewStyle } = this.props;
const { collection, entries, publicFolder, isFetching, viewStyle, cursor } = this.props;
return (
<Entries
collections={collection}
entries={entries}
publicFolder={publicFolder}
page={page}
onPaginate={this.handleLoadMore}
isFetching={isFetching}
collectionName={collection.get('label')}
viewStyle={viewStyle}
cursor={cursor}
handleCursorActions={partial(this.handleCursorActions, cursor)}
/>
);
}
}
function mapStateToProps(state, ownProps) {
const { name, collection, viewStyle } = ownProps;
const { collection, viewStyle } = ownProps;
const { config } = state;
const publicFolder = config.get('public_folder');
const page = state.entries.getIn(['pages', collection.get('name'), 'page']);
@ -62,11 +70,15 @@ function mapStateToProps(state, ownProps) {
const entries = selectEntries(state, collection.get('name'));
const isFetching = state.entries.getIn(['pages', collection.get('name'), 'isFetching'], false);
return { publicFolder, collection, page, entries, isFetching, viewStyle };
const rawCursor = selectCollectionEntriesCursor(state.cursors, collection.get("name"));
const cursor = Cursor.create(rawCursor).clearData();
return { publicFolder, collection, page, entries, isFetching, viewStyle, cursor };
}
const mapDispatchToProps = {
loadEntries: actionLoadEntries,
traverseCollectionCursor: actionTraverseCollectionCursor,
};
export default connect(mapStateToProps, mapDispatchToProps)(EntriesCollection);

View File

@ -7,6 +7,7 @@ import {
searchEntries as actionSearchEntries,
clearSearch as actionClearSearch
} from 'Actions/search';
import Cursor from 'ValueObjects/Cursor';
import Entries from './Entries';
class EntriesSearch extends React.Component {
@ -36,15 +37,27 @@ class EntriesSearch extends React.Component {
this.props.clearSearch();
}
handleLoadMore = (page) => {
const { searchTerm, searchEntries } = this.props;
if (!isNaN(page)) searchEntries(searchTerm, page);
getCursor = () => {
const { page } = this.props;
return Cursor.create({
actions: isNaN(page) ? [] : ["append_next"],
});
};
handleCursorActions = (action) => {
const { page, searchTerm, searchEntries } = this.props;
if (action === "append_next") {
const nextPage = page + 1;
searchEntries(searchTerm, nextPage);
}
};
render () {
const { collections, entries, publicFolder, page, isFetching } = this.props;
return (
<Entries
cursor={this.getCursor()}
handleCursorActions={this.handleCursorActions}
collections={collections}
entries={entries}
publicFolder={publicFolder}
@ -59,8 +72,8 @@ class EntriesSearch extends React.Component {
function mapStateToProps(state, ownProps) {
const { searchTerm } = ownProps;
const collections = ownProps.collections.toIndexedSeq();
const isFetching = state.entries.getIn(['search', 'isFetching']);
const page = state.entries.getIn(['search', 'page']);
const isFetching = state.search.get('isFetching');
const page = state.search.get('page');
const entries = selectSearchedEntries(state);
const publicFolder = state.config.get('public_folder');

View File

@ -5,6 +5,7 @@ import Waypoint from 'react-waypoint';
import { Map } from 'immutable';
import { selectFields, selectInferedField } from 'Reducers/collections';
import EntryCard from './EntryCard';
import Cursor from 'ValueObjects/Cursor';
export default class EntryListing extends React.Component {
static propTypes = {
@ -14,13 +15,14 @@ export default class EntryListing extends React.Component {
ImmutablePropTypes.iterable,
]).isRequired,
entries: ImmutablePropTypes.list,
onPaginate: PropTypes.func.isRequired,
page: PropTypes.number,
viewStyle: PropTypes.string,
};
handleLoadMore = () => {
this.props.onPaginate(this.props.page + 1);
const { cursor, handleCursorActions } = this.props;
if (Cursor.create(cursor).actions.has("append_next")) {
handleCursorActions("append_next");
}
};
inferFields = collection => {
@ -48,12 +50,12 @@ export default class EntryListing extends React.Component {
const collectionLabel = collection.get('label');
const inferedFields = this.inferFields(collection);
const entryCardProps = { collection, entry, inferedFields, publicFolder, key: idx, collectionLabel };
return <EntryCard {...entryCardProps}/>;
return <EntryCard {...entryCardProps} />;
});
};
render() {
const { collections, entries, publicFolder } = this.props;
const { collections } = this.props;
return (
<div>

View File

@ -1,35 +1,36 @@
import PropTypes from 'prop-types';
import React from 'react';
const ErrorComponent = () => {
const issueUrl = "https://github.com/netlify/netlify-cms/issues/new";
return (
<div className="nc-errorBoundary">
<h1 className="nc-errorBoundary-heading">Sorry!</h1>
<p>
<span>There's been an error - please </span>
<a href={issueUrl} target="_blank" className="nc-errorBoundary-link">report it</a>!
</p>
</div>
);
const DefaultErrorComponent = () => {
};
export class ErrorBoundary extends React.Component {
static propTypes = {
render: PropTypes.element,
};
const ISSUE_URL = "https://github.com/netlify/netlify-cms/issues/new";
export class ErrorBoundary extends React.Component {
state = {
hasError: false,
errorMessage: '',
};
componentDidCatch(error) {
console.error(error);
this.setState({ hasError: true });
this.setState({ hasError: true, errorMessage: error.toString() });
}
render() {
const errorComponent = this.props.errorComponent || <ErrorComponent/>;
return this.state.hasError ? errorComponent : this.props.children;
const { hasError, errorMessage } = this.state;
if (!hasError) {
return this.props.children;
}
return (
<div className="nc-errorBoundary">
<h1 className="nc-errorBoundary-heading">Sorry!</h1>
<p>
<span>There's been an error - please </span>
<a href={ISSUE_URL} target="_blank" className="nc-errorBoundary-link">report it</a>!
</p>
<p>{errorMessage}</p>
</div>
);
}
}

View File

@ -13,6 +13,7 @@ import iconDragHandle from './drag-handle.svg';
import iconEye from './eye.svg';
import iconFolder from './folder.svg';
import iconGithub from './github.svg';
import iconGitlab from './gitlab.svg';
import iconGrid from './grid.svg';
import iconH1 from './h1.svg';
import iconH2 from './h2.svg';
@ -55,6 +56,7 @@ const images = {
'eye': iconEye,
'folder': iconFolder,
'github': iconGithub,
'gitlab': iconGitlab,
'grid': iconGrid,
'h1': iconH1,
'h2': iconH2,

View File

@ -0,0 +1 @@
<svg width="26" height="26" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M22.616 14.971L21.52 11.5l-2.173-6.882a.37.37 0 0 0-.71 0l-2.172 6.882H9.252L7.079 4.617a.37.37 0 0 0-.71 0l-2.172 6.882L3.1 14.971c-.1.317.01.664.27.86l9.487 7.094 9.487-7.094a.781.781 0 0 0 .27-.86" fill="#FC6D26"/><path d="M12.858 22.925L16.465 11.5H9.251z" fill="#E24329"/><path d="M12.858 22.925L9.251 11.5H4.197z" fill="#FC6D26"/><path d="M4.197 11.499L3.1 14.971c-.1.317.01.664.27.86l9.487 7.094L4.197 11.5z" fill="#FCA326"/><path d="M4.197 11.499H9.25L7.08 4.617a.37.37 0 0 0-.71 0l-2.172 6.882z" fill="#E24329"/><path d="M12.858 22.925L16.465 11.5h5.055z" fill="#FC6D26"/><path d="M21.52 11.499l1.096 3.472c.1.317-.01.664-.271.86l-9.487 7.094L21.52 11.5z" fill="#FCA326"/><path d="M21.52 11.499h-5.055l2.172-6.882a.37.37 0 0 1 .71 0l2.173 6.882z" fill="#E24329"/></g></svg>

After

Width:  |  Height:  |  Size: 889 B