UNPKG

stitch-ui

Version:

158 lines (143 loc) 5.06 kB
import React from "react"; // eslint-disable-line no-unused-vars import { createAction } from "redux-act"; import { Map, Record } from "immutable"; import { PropTypes } from "prop-types"; import classNames from "classnames"; export const toJSON = x => JSON.stringify(x, null, 2); export const makeAsyncActions = name => ({ req: createAction(`${name}(request)`), rcv: createAction(`${name}(receive)`), fail: createAction(`${name}(fail)`) }); export const pluralize = (count, singular, plural) => count === 1 ? singular : plural; /** * Creates a simple stateless functional component that activates a given set of CSS classes * when certain corresponding props are specified. * * @param {Object} propsMapping - A mapping of prop names to CSS class names. * @param {string} displayName - The display name for the React component * @param {string=} className - A classname that is *always* applied to instances of the component. */ export const basicComponent = (propsMapping, displayName, className = "") => { /* eslint-disable react/prop-types */ const result = props => { const componentClassName = classNames( className, props.className, Object.keys(propsMapping).reduce( (obj, k) => ({ ...obj, [propsMapping[k]]: props[k] }), {} ) ); const newProps = { ...props, className: componentClassName }; Object.keys(propsMapping).forEach(k => delete newProps[k]); return <div {...newProps} />; }; /* eslint-enable react/prop-types */ result.displayName = displayName; return result; }; /* Given an object containing async actions {req, rcv, fail}, * returns a function which applies its args to the given apiMethod using the client * currently in the redux store. It executes the API request, and dispatches the individual * async actions appopriately: * req: when request is started * rcv: when a successful result is received * fail: when the request has failed */ export const asyncActionExecutor = (asyncAction, apiMethod, rethrow) => ( ...args ) => (dispatch, getState) => { dispatch(asyncAction.req([...args])); const client = getState().base.client; return apiMethod(client, ...args)() .then(payload => { dispatch(asyncAction.rcv({ payload, reqArgs: [...args] })); return payload; }) .catch(e => { if (e.code === "Unauthorized") { const history = getState().router.history; history.push("/login"); return Promise.resolve(); } // TODO this is slightly ugly. Consider always rethrowing. // Always rethrow if the error did not come from the API if (e.name !== "StitchError") { return Promise.reject(e); } dispatch( asyncAction.fail({ error: e.message, reqArgs: [...args], rawError: e }) ); if (rethrow) { throw e; } return Promise.resolve(); }); }; // Returns an event handler function which calls the given argument (a function) // if the event is an "enter" keypress. // The optional second argument is a function that will get called if the // user presses Escape. export const enterPressedCaller = (func, escFunc) => e => { if (e.key === "Enter") { func(); } if (typeof escFunc === "function" && e.key === "Escape") { escFunc(); } }; // If a 401 error is caught (user session is logged out or invalid) force // them back to the login page. // Any other errors just get logged to console. export const handleError = (e, target) => { if (e.name === "StitchError") { if ( e.message === "Must auth first" || (e.response && e.response.status === 401) ) { target.context.router.history.push("/login"); return; } target.setState({ error: e.message, loading: false }); return; } console.error(e); // eslint-disable-line no-console }; export const randomAlphaNumericString = length => { const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let str = ""; for (let i = 0; i < length; i += 1) { const pos = Math.floor(Math.random() * alpha.length); str += alpha.substring(pos, pos + 1); } return str; }; export function appLocation(groupId, id) { return `/groups/${groupId}/apps/${id}`; } export const EDIT_TYPE_LOCAL = "local"; export const EDIT_TYPE_REMOTE = "remote"; export const EDIT_TYPE_STATUS = "status"; /** * Creates a Record and corresponding PropType shape for use * in editing a type that has local and remote state while * tracking UI state. * @param Type - The type that the edit type should represent. * @returns {Object} - An object containing the record and prop type. */ export function createEditType(Type) { const record = Record({ [EDIT_TYPE_LOCAL]: new Type(), [EDIT_TYPE_REMOTE]: new Type(), [EDIT_TYPE_STATUS]: new Map() }); const propType = PropTypes.shape({ [EDIT_TYPE_LOCAL]: PropTypes.instanceOf(Type), [EDIT_TYPE_REMOTE]: PropTypes.instanceOf(Type), [EDIT_TYPE_STATUS]: PropTypes.instanceOf(Map) }); return { record, propType }; }