stitch-ui
Version:
158 lines (143 loc) • 5.06 kB
JavaScript
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 };
}