mirror of
https://github.com/wassname/talk.git
synced 2026-07-11 10:12:48 +08:00
203 lines
6.5 KiB
JavaScript
203 lines
6.5 KiB
JavaScript
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { connect } from 'react-redux';
|
|
import isEqual from 'lodash/isEqual';
|
|
import { getShallowChanges } from 'coral-framework/utils';
|
|
import { compose } from 'recompose';
|
|
import hoistStatics from 'recompose/hoistStatics';
|
|
import isFunction from 'lodash/isFunction';
|
|
|
|
function resolvePrimitiveOrFunction(primitiveOrFunction, props) {
|
|
if (isFunction(primitiveOrFunction)) {
|
|
return primitiveOrFunction(props);
|
|
}
|
|
return primitiveOrFunction;
|
|
}
|
|
|
|
const createHOC = ({
|
|
slot,
|
|
defaultComponent = null,
|
|
passthroughPropName = 'passthrough',
|
|
size = null,
|
|
propName = 'slotElements',
|
|
}) =>
|
|
hoistStatics(WrappedComponent => {
|
|
return class withSlotElements extends React.Component {
|
|
static contextTypes = {
|
|
plugins: PropTypes.object,
|
|
};
|
|
|
|
static propTypes = {
|
|
reduxState: PropTypes.object,
|
|
};
|
|
|
|
getSlots(props = this.props) {
|
|
const tmp = resolvePrimitiveOrFunction(slot, props);
|
|
if (Array.isArray(tmp)) {
|
|
return tmp;
|
|
}
|
|
return [tmp];
|
|
}
|
|
|
|
getDefaultComponents(props = this.props, fill = 1) {
|
|
const tmp = resolvePrimitiveOrFunction(defaultComponent, props);
|
|
if (Array.isArray(tmp)) {
|
|
return tmp;
|
|
}
|
|
return new Array(fill).fill(tmp);
|
|
}
|
|
|
|
getSizes(props = this.props, fill = 1) {
|
|
const tmp = resolvePrimitiveOrFunction(size, props);
|
|
if (Array.isArray(tmp)) {
|
|
return tmp;
|
|
}
|
|
return new Array(fill).fill(tmp);
|
|
}
|
|
|
|
getPassthrough(props = this.props) {
|
|
return passthroughPropName ? props[passthroughPropName] : null;
|
|
}
|
|
|
|
shouldComponentUpdate(next) {
|
|
// Prevent Slot from rerendering when only reduxState has changed and
|
|
// it does not result in a change of slot children.
|
|
const changes = getShallowChanges(this.props, next);
|
|
|
|
// Handle special `passthrough` props.
|
|
if (passthroughPropName) {
|
|
const passthroughIndex = changes.indexOf(passthroughPropName);
|
|
if (passthroughIndex !== -1) {
|
|
if (!this.props[passthroughPropName] || next[passthroughPropName]) {
|
|
return true;
|
|
}
|
|
if (
|
|
getShallowChanges(
|
|
this.props[passthroughPropName],
|
|
next[passthroughPropName]
|
|
).lenght === 0
|
|
) {
|
|
changes.splice(passthroughIndex, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changes.length === 1 && changes[0] === 'reduxState') {
|
|
// If config changed, we'll have to rerender everything.
|
|
// Should only happen during development as this is
|
|
// usually static.
|
|
if (this.props.reduxState.config !== next.reduxState.config) {
|
|
return true;
|
|
}
|
|
|
|
const prevChildrenKeys = this.getSlotElements(this.props).map(
|
|
child => child.key
|
|
);
|
|
const nextChildrenKeys = this.getSlotElements(next).map(
|
|
child => child.key
|
|
);
|
|
return !isEqual(prevChildrenKeys, nextChildrenKeys);
|
|
}
|
|
|
|
// Prevent Slot from rerendering when no props has shallowly changed.
|
|
return changes.length !== 0;
|
|
}
|
|
|
|
/**
|
|
* Returns slot elements for configured slots. If only one slot is given
|
|
* slot elements are returned directly. If more than one slot is specified
|
|
* returns an array of slot elements.
|
|
*/
|
|
getSlotElements(props = this.props) {
|
|
const { plugins } = this.context;
|
|
const slots = this.getSlots(props);
|
|
const sizes = this.getSizes(props, slots.length);
|
|
const defaultComponents = this.getDefaultComponents(
|
|
props,
|
|
slots.length
|
|
);
|
|
const slotPassthrough = this.getPassthrough(props);
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (slotPassthrough.data && slotPassthrough.data.refetch) {
|
|
/* eslint-disable no-console */
|
|
console.warn(
|
|
'Slots no longer need `data` property.',
|
|
'Plugins can use the new HOCs `withRefetch`,',
|
|
'`withFetchMore`, `withSubscribeToMore` and `withVariables` instead.',
|
|
'Affected slots: ',
|
|
slots
|
|
);
|
|
/* eslint-enable no-console */
|
|
}
|
|
}
|
|
|
|
const elements = [];
|
|
slots.forEach((s, i) => {
|
|
const DefaultComponent = defaultComponents[i];
|
|
const size = sizes[i];
|
|
const slotElements = plugins.getSlotElements(
|
|
s,
|
|
props.reduxState,
|
|
slotPassthrough,
|
|
{ size }
|
|
);
|
|
|
|
if (slotElements.length === 0 && DefaultComponent) {
|
|
const p = plugins.getSlotComponentProps(
|
|
DefaultComponent,
|
|
props.reduxState,
|
|
slotPassthrough
|
|
);
|
|
slotElements.push(<DefaultComponent key="default" {...p} />);
|
|
}
|
|
|
|
elements.push(slotElements);
|
|
});
|
|
|
|
if (elements.length === 1) {
|
|
return elements[0];
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
render() {
|
|
const { reduxState: _a, ...rest } = this.props;
|
|
const slotElements = this.getSlotElements();
|
|
const props = {
|
|
[propName]: slotElements,
|
|
...rest,
|
|
};
|
|
return <WrappedComponent {...props} />;
|
|
}
|
|
};
|
|
});
|
|
|
|
const mapStateToProps = state => ({
|
|
reduxState: state,
|
|
});
|
|
|
|
/**
|
|
* Exports a HOC that provides a property `slotElements`.
|
|
* @param {Object} [options] configuration
|
|
* @param {string|array} [options.slot] Slot name or Array of Slot Names
|
|
* @param {element|array} [options.defaultComponent] Default Components or Array of such
|
|
* @param {number|array} [options.size] Slot size or an Array of slot size
|
|
* @param {string} [options.passthroughPropName] The property to find the passthrough prop
|
|
* @param {string} [options.propName] New property name, defaults to `slotElements`
|
|
*
|
|
* @return {func} Returns a HOC that per default provides the property `slotElements` with an
|
|
* Array of Slot Elements or in case of multiple slots, an Array of Slot Element
|
|
* Arrays.
|
|
*
|
|
* Example:
|
|
* withSlotElements({
|
|
* slot: 'awesomeSlot',
|
|
* size: 1,
|
|
* })(MyComponent);
|
|
*/
|
|
export default settings => {
|
|
return compose(connect(mapStateToProps, null), createHOC(settings));
|
|
};
|