Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine ItemsList base component (previously LiveItemsList) #3415

Merged
merged 19 commits into from
Feb 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/Dockerfile.cypress
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM cypress/browsers:chrome67
ENV APP /usr/src/app
WORKDIR $APP

RUN npm install --no-save puppeteer@1.10.0 cypress@^3.1.5 @percy/cypress@^0.2.3 > /dev/null
RUN npm install --no-save puppeteer@1.10.0 cypress@^3.1.5 @percy/cypress@^0.2.3 atob@2.1.2 > /dev/null

COPY cypress $APP/cypress
COPY cypress.json $APP/cypress.json
Expand Down
130 changes: 130 additions & 0 deletions client/app/components/items-list/ItemsList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { omit, debounce } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { $route } from '@/services/ng';
import { clientConfig } from '@/services/auth';
import { StateStorage } from './classes/StateStorage';

export const ControllerType = PropTypes.shape({
// values of props declared by wrapped component, current route's locals (`resolve: { ... }`) and title
params: PropTypes.object.isRequired,

isLoaded: PropTypes.bool.isRequired,
isEmpty: PropTypes.bool.isRequired,

// search
searchTerm: PropTypes.string,
updateSearch: PropTypes.func.isRequired, // (searchTerm: string) => void

// tags
selectedTags: PropTypes.array.isRequired,
updateSelectedTags: PropTypes.func.isRequired, // (selectedTags: array of tags) => void

// sorting
orderByField: PropTypes.string,
orderByReverse: PropTypes.bool.isRequired,
toggleSorting: PropTypes.func.isRequired, // (orderByField: string) => void

// pagination
page: PropTypes.number.isRequired,
itemsPerPage: PropTypes.number.isRequired,
totalItemsCount: PropTypes.number.isRequired,
pageSizeOptions: PropTypes.arrayOf(PropTypes.number).isRequired,
pageItems: PropTypes.array.isRequired,
updatePagination: PropTypes.func.isRequired, // ({ page: number, itemsPerPage: number }) => void

handleError: PropTypes.func.isRequired, // (error) => void
});

export function wrap(WrappedComponent, itemsSource, stateStorage) {
class ItemsListWrapper extends React.Component {
static propTypes = {
...omit(WrappedComponent.propTypes, ['controller']),
onError: PropTypes.func,
children: PropTypes.node,
};

static defaultProps = {
...omit(WrappedComponent.defaultProps, ['controller']),
onError: (error) => {
// Allow calling chain to roll up, and then throw the error in global context
setTimeout(() => { throw error; });
},
children: null,
};

constructor(props) {
super(props);

stateStorage = stateStorage || new StateStorage();
itemsSource.setState({ ...stateStorage.getState(), validate: false });
itemsSource.getCallbackContext = () => this.state;

itemsSource.onBeforeUpdate = () => {
const state = itemsSource.getState();
stateStorage.setState(state);
this.setState(this.getState({ ...state, isLoaded: false }));
};

itemsSource.onAfterUpdate = () => {
const state = itemsSource.getState();
this.setState(this.getState({ ...state, isLoaded: true }));
};

itemsSource.onError = error => this.props.onError(error);

const initialState = this.getState({ ...itemsSource.getState(), isLoaded: false });
const { updatePagination, toggleSorting, updateSearch, updateSelectedTags, update, handleError } = itemsSource;
this.state = {
...initialState,
toggleSorting, // eslint-disable-line react/no-unused-state
updateSearch: debounce(updateSearch, 200), // eslint-disable-line react/no-unused-state
updateSelectedTags, // eslint-disable-line react/no-unused-state
updatePagination, // eslint-disable-line react/no-unused-state
update, // eslint-disable-line react/no-unused-state
handleError, // eslint-disable-line react/no-unused-state
};
}

componentDidMount() {
this.state.update();
}

// eslint-disable-next-line class-methods-use-this
getState({ isLoaded, totalCount, pageItems, ...rest }) {
const params = {
// Add some properties of current route (`$resolve`, title)
// ANGULAR_REMOVE_ME Revisit when some React router will be used
title: $route.current.title,
...omit($route.current.locals, ['$scope', '$template']),

// Add to params all props except of own ones
...omit(this.props, ['onError', 'children']),
};
return {
...rest,

params,

isLoaded,
isEmpty: !isLoaded || (totalCount === 0),
totalItemsCount: isLoaded ? totalCount : 0,
pageSizeOptions: clientConfig.pageSizeOptions,
pageItems: isLoaded ? pageItems : [],
};
}

render() {
// don't pass own props to wrapped component
const { children, onError, ...props } = this.props;
props.controller = this.state;
return <WrappedComponent {...props}>{ children }</WrappedComponent>;
}
}

// Copy static methods from `WrappedComponent`
hoistNonReactStatics(ItemsListWrapper, WrappedComponent);

return ItemsListWrapper;
}
242 changes: 0 additions & 242 deletions client/app/components/items-list/LiveItemsList.jsx

This file was deleted.

Loading