import React, {useEffect} from 'react'; import { AttributeValue } from 'wdk-client/Utils/WdkModel'; /** * React Component utils */ interface AnyObject { [key: string]: any; } /** * Stateless function component decorator that prevents rerendering * when props are equal use shallow comparison. */ export function pure
(Component: React.StatelessComponent
) { return class PureWrapper extends React.PureComponent
{
static get displayName() {
return `PureWrapper(${Component.displayName || Component.name})`;
}
render() {
return (
extends React.ComponentClass
{ wrapComponent(factory: (Component: React.ComponentType
) => React.ComponentType
): void; } /** * A Component decorator that wraps the Component passed to it and returns a new * Component with a static method `wrapComponent`. Use this method to replace * the wrapped Component. See the docs for `wrapComponent` for more details. * * * Rationale * ========= * * Components in WDK should be customizable by the application. The application * should be able to replace the Component, surround the Component, or modify * the props passed to the Component. By creating a wrapper Component whose * internal Component can be replaced in-place, WDK code does not need to do * any kind of lookup, and implementors are given a nice, convenient syntax for * what will most likely be a common task: modifying a WDK Component. * * There are other approaches that would require a uniform JavaScript build * environment, which would allow the application to perform overriding at * build time (similar to how we do this with JSP tags). We aren't quite at a * point where we can do this, and it might not be desirable. * * * Usage * ===== * * // As a function * let Answer = wrappable(Answer); * * * // As an ES2016 class decorator * @wrappable * class Answer extends React.Component { * // ... * } * * // Using the static wrap method. Note that the function passed to `wrap` * // will receive the currently wrapped Component as an argument. This * // makes it possible to replace or decorate the Component, or to modify * // the props passed to the component. * Answer.wrap(function(Answer) { * return React.createClass({ * render() { * return ( *
(Component: React.ComponentType
): ComponentWrapper
{ return class Wrapper extends React.Component
{ // Forward calls for displayName and propTypes to the wrapped Component. // This is useful for debugging messages generated by React. static get displayName() { return `WdkWrapper(${Component.displayName || Component.name})`; } static get propTypes() { return Component.propTypes; } /** * Used to modify the Component being wrapped. Use it by passing a function * that returns a new Component. * * The function will receive the current Component as an argument. This * makes it possible to render the current Component in the new component, * similar to Aspect Oriented Programming techniques. * * The new Component will replace the existing Component. * * TODO Verify that factory returns a React class * * @param {function} factory A factory function returning a new React * Component. The function will receive the current Component as its sole * param. */ static wrapComponent(factory: (Component: React.ComponentType
) => React.ComponentType
) {
Component = factory(Component);
}
render() {
return (ms: number) {
return lazy (() => new Promise(resolve => setTimeout(resolve, ms)));
}
interface InstrumentOptions {
compareProps?: boolean;
}
/**
* Takes a component and returns an intrumented wrapper component
* that will log details about props, etc.
*
* This should never be used in production code!
*/
export function instrument (Component: React.ComponentClass , options: InstrumentOptions): React.ComponentClass ;
export function instrument (Component: React.StatelessComponent , options: InstrumentOptions): React.ComponentClass ;
export function instrument (Component: any, options: InstrumentOptions = {}): React.ComponentClass {
let {
compareProps = true
} = options;
let componentName = Component.displayName || Component.name || Component;
return class InstrumentWrapper extends React.Component {
shouldComponentUpdate(nextProps: P) {
if (compareProps) {
logShallowComparison(
this.props,
nextProps,
'Comparing props for ' + componentName
);
}
return true;
}
render() {
return (obj1: P, obj2: P, label: string = 'Shallow comparison') {
console.group(label);
console.log('Comparing %o and %o', obj1, obj2);
let allKeys = new Set(Object.keys(obj1).concat(Object.keys(obj2)));
for (let key of allKeys) {
let equal = obj1[key] === obj2[key];
if (!equal) {
console.log('`%s` not equal', key);
}
}
console.groupEnd();
}
/** Create a React Element using preformatted HTML */
export function safeHtml (str: string, props?: P, Component?: React.ComponentClass ): JSX.Element;
export function safeHtml (str: string, props?: P, Component?: React.StatelessComponent ): JSX.Element;
export function safeHtml (str: string, props?: P, Component?: string): JSX.Element;
export function safeHtml (str: string | null, props?: P, Component: any = 'span'): JSX.Element {
str = str ?? '';
if (str.indexOf('<') === -1) {
return (orig: P, propsToFilter: string[]) {
return Object.keys(orig).reduce((obj, key) =>
(propsToFilter.indexOf(key) !== -1 ? obj :
Object.assign(obj, { [key]: orig[key] })), {});
}
/**
* Generates HTML markup for an attribute using duck-typing
*/
export function formatAttributeValue(value?: AttributeValue): string {
if (typeof value === 'object' && value != null) {
return `${value.displayText || value.url}`;
}
return value as string;
}
/**
* Creates a React-renderable element using the provided `Component`, or 'span'
* by default.
* TODO Look up or inject custom formatters
*/
export function renderAttributeValue (value: AttributeValue, props?: P, Component = 'span') {
return safeHtml(
formatAttributeValue(value),
props,
Component
);
}
/**
* Makes a copy of current, adds value if not present, removes if present, and
* returns the copy.
* @param {Array (oldProps: P, newProps: P, propertyNameList: string[]) {
for (let i = 0; i < propertyNameList.length; i++) {
if (oldProps[propertyNameList[i]] !== newProps[propertyNameList[i]]) {
return true;
}
}
return false;
}
interface HandlerSetObject {
[key: string]: (...args: any[]) => any;
}
/**
* Bind a collection of action functions to a dispatchAction function.
*
* @param {Function} dispatchAction
* @param {Object(inputName: string, onParentChange: (s: S) => S, previousState: S) {
return (newValue: T) => {
onParentChange(Object.assign({}, previousState, { [inputName]: newValue }));
};
}
/**
* For each property in propertyNameList, examines each property in both
* oldProps and newProps to check for referential equality. If any prop differs
* returns true, else returns false.
*
* @param {Object} oldProps props object
* @param {Object} newProps another props object
* @param {Array