UNPKG

attributes-kit

Version:
331 lines (282 loc) 9.55 kB
import abagnale from 'abagnale/lib/abagnale'; import cloneDeep from 'lodash/cloneDeep'; import eidolon from 'eidolon'; import { EventEmitter } from 'fbemitter'; import isUndefined from 'lodash/isUndefined'; import isArray from 'lodash/isArray'; import isObject from 'lodash/isObject'; import map from 'lodash/map'; import merge from 'lodash/merge'; import minim, { JSON06Serialiser } from 'minim'; import React from 'react'; import PropTypes from 'prop-types'; import reduce from 'lodash/reduce'; import { Style } from 'radium'; import Attribute from '../Attribute/Attribute'; import Title from '../Title/Title'; import { DEFAULT_FONT_FAMILY } from '../../Constants/fonts'; import { preprocess } from '../../Modules/Preprocessor/Preprocessor'; import { preprocessSamples } from '../../Modules/SamplesPreprocessor/SamplesPreprocessor'; import defaultTheme from '../../Resources/theme'; // isMinimElement :: * -> Boolean const isMinimElement = element => ( element instanceof minim.Element || element instanceof minim.ArraySlice ); class Attributes extends React.Component { static propTypes = { namedTypes: PropTypes.bool, collapseByDefault: PropTypes.bool, dataStructures: PropTypes.oneOfType([ PropTypes.object, // minim PropTypes.array, // legacy refract array ]), element: PropTypes.object, includedProperties: PropTypes.oneOfType([ PropTypes.bool, PropTypes.string, ]), inheritedProperties: PropTypes.oneOfType([ PropTypes.bool, PropTypes.string, ]), maxInheritanceDepth: PropTypes.any, onElementLinkClick: PropTypes.func, title: PropTypes.oneOfType([ PropTypes.bool, PropTypes.string, ]), theme: PropTypes.object, }; static defaultProps = { includedProperties: 'show', inheritedProperties: 'show', }; static childContextTypes = { dereferencedDataStructures: PropTypes.array, theme: PropTypes.object, element: PropTypes.object, namedTypes: PropTypes.bool, eventEmitter: PropTypes.object, onElementLinkClick: PropTypes.func, includedProperties: PropTypes.oneOfType([ PropTypes.bool, PropTypes.string, ]), inheritedProperties: PropTypes.oneOfType([ PropTypes.bool, PropTypes.string, ]), }; constructor(props) { super(props); this.state = this.transformPropsIntoState(this.props); this.eventEmitter = new EventEmitter(); }; getChildContext() { return { dereferencedDataStructures: this.state.dereferencedDataStructures, eventEmitter: this.eventEmitter, includedProperties: this.state.includedProperties, inheritedProperties: this.state.inheritedProperties, onElementLinkClick: this.state.onElementLinkClick, theme: this.state.theme, namedTypes: this.state.namedTypes, element: this.state.element, }; }; componentWillReceiveProps(nextProps) { this.setState( this.transformPropsIntoState(nextProps) ); }; alignKeys = () => { this.eventEmitter.emit('alignKeys'); }; transformPropsIntoState(props) { let theme; // Make a deep clone of the default theme object // to prevent future mutations; then we'll merge in custom theme. theme = cloneDeep(defaultTheme); theme = merge(theme, props.theme || {}); // `dataStructures` prop is optional and is used to // resolve inheritance, references and includes/mixins, plus to // render the inheritance tree. let dataStructures = isUndefined(props.dataStructures) ? [] : props.dataStructures; let element = props.element; // serialize minim Element inputs (`element` and `dataStructures`) // to the expected refract 0.6 serialization const refract06Serializer = new JSON06Serialiser(); if (isMinimElement(element)) { element = refract06Serializer.serialise(element.content); } if (isMinimElement(dataStructures)) { dataStructures = dataStructures.map(d => refract06Serializer.serialise(d.content)); } // We have to resolve all references, otherwise we wouldn't be able to // render the element. Dereferencing turns `{ element: 'MyObject', ... }` // into `{ element: 'object', ... }`, which is something Attributes Kit // understands. // // First, let's build index of the data structure elements in the following // format—`[dataStructure.meta.id]: dataStructureElement`, where // `dataStructure.meta.id` is name of the data structure. const dataStructuresIndex = props.dataStructuresIndex || ( reduce(dataStructures, (result, dataStructure) => { if (typeof dataStructure.meta.id === 'object') { result[dataStructure.meta.id.content] = dataStructure; } else { result[dataStructure.meta.id] = dataStructure; } return result; }, {}) ); const dereferencedDataStructures = props.dereferencedDataStructures || ( map(dataStructures, (dataStructure) => eidolon.dereference( cloneDeep(dataStructure), dataStructuresIndex ) ) ); // Set default value of `inheritedProperties` and `includedProperties` options. // If a user hasn't provided the values, we default to true (= we'll render // the whole data structure including inherited and included properties). // // Options `showInherited` and `showIncluded` have been deprecated and // will be removed in the 1.0 release. let inheritedProperties = props.inheritedProperties || props.showInherited; let includedProperties = props.includedProperties || props.showIncluded; if (isUndefined(inheritedProperties)) { inheritedProperties = true; } if (isUndefined(includedProperties)) { includedProperties = true; } let title; if (props.title === 'show') { title = true; } else if (props.title === 'hide') { title = false; } else { title = true; } const maxInheritanceDepth = props.maxInheritanceDepth || undefined; // Set default value of `collapseByDefault` option. If a user hasn't // provided the value, we default to false (= render // the whole data structure expanded). let collapseByDefault = props.collapseByDefault; if (isUndefined(collapseByDefault)) { collapseByDefault = false; } let namedTypes = props.namedTypes; if (isUndefined(namedTypes)) { namedTypes = false; } // Set up a dummy handler for element link clicks. let onElementLinkClick = props.onElementLinkClick; if (isUndefined(onElementLinkClick)) { onElementLinkClick = function defaultOnElementLinkClickHandler() {}; } const originElement = this.addNestedLevels( cloneDeep(element) ); // Dereference the element. This overwrites the original // value with the normalized result. Reference information // is still available in the `meta.ref` properties. const dereferencedElement = eidolon.dereference( originElement, dataStructuresIndex ); element = abagnale.forge([dereferencedElement], { separator: '.' })[0]; preprocessSamples(element); preprocess(element); return { collapseByDefault, dereferencedDataStructures, element, includedProperties, inheritedProperties, maxInheritanceDepth, namedTypes, onElementLinkClick, theme, title, }; }; addNestedLevels(element, nestedLevel = -1) { if (!element) { return element; } if (!element.meta) { element.meta = {}; } if (element.content && element.content.value) { nestedLevel = nestedLevel + 1; } element.meta._nestedLevel = nestedLevel; if (element.content && isArray(element.content)) { element.content = map(element.content, (nestedElement) => this.addNestedLevels(nestedElement, nestedLevel) ); } else if (element.content && isObject(element.content)) { element.content.value = this.addNestedLevels( element.content.value, nestedLevel ); } return element; }; render() { if (!this.props.element) { console.error( new Error(` Please provide the ‘element’ prop (‘<Attributes element="" />’) to the Attributes Kit. `) ); return null; } const DESCRIPTION_COLOR = this.state.theme.DESCRIPTION_COLOR; return ( <div className="attributesKit"> { this.state.title && <Title element={this.state.element} /> } <Style scopeSelector=".attributesKit" rules={{ p: { marginBottom: '4px', fontFamily: DEFAULT_FONT_FAMILY, fontSize: '14px', color: DESCRIPTION_COLOR, lineHeight: '21px', fontWeight: 'regular', }, 'p:last-child': { marginBottom: '0px', }, ul: { marginLeft: '20px', }, a: { color: '#747E8E', textDecoration: 'none', borderBottom: '1px solid #DCE0E8', }, 'a:hover': { borderBottom: 'none', }, }} /> <Attribute element={this.state.element} theme={this.state.theme} collapseByDefault={this.state.collapseByDefault} /> </div> ); }; } export default Attributes;