UNPKG

8.13 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4Object.defineProperty(exports, "__esModule", {
5 value: true
6});
7exports.default = void 0;
8exports.generateComponentExamples = generateComponentExamples;
9var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10var _nanoid = require("nanoid");
11var _generatePropCombinations = require("./generatePropCombinations");
12var _react = _interopRequireDefault(require("react"));
13/*
14 * The MIT License (MIT)
15 *
16 * Copyright (c) 2015 - present Instructure, Inc.
17 *
18 * Permission is hereby granted, free of charge, to any person obtaining a copy
19 * of this software and associated documentation files (the "Software"), to deal
20 * in the Software without restriction, including without limitation the rights
21 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 * copies of the Software, and to permit persons to whom the Software is
23 * furnished to do so, subject to the following conditions:
24 *
25 * The above copyright notice and this permission notice shall be included in all
26 * copies or substantial portions of the Software.
27 *
28 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34 * SOFTWARE.
35 */
36
37/**
38 * Generates examples for the given component based on the given configuration.
39 * @param Component A React component
40 * @param config A configuration object (stored in xy.examples.jsx files in InstUI)
41 * @returns Array of examples broken into sections and pages if configured to do so.
42 * @module generateComponentExamples
43 * @private
44 *
45 */
46function generateComponentExamples(Component, config) {
47 const sectionProp = config.sectionProp,
48 excludeProps = config.excludeProps,
49 filter = config.filter;
50 const PROPS_CACHE = [];
51 const sections = [];
52 const maxExamples = config.maxExamples ? config.maxExamples : 500;
53 let exampleCount = 0;
54 let propValues = {};
55 const getParameters = page => {
56 const examples = page.examples;
57 const index = page.index;
58 let parameters = {};
59 if (typeof config.getParameters === 'function') {
60 parameters = {
61 ...config.getParameters({
62 examples,
63 index
64 })
65 };
66 }
67 return parameters;
68 };
69
70 /**
71 * Merges the auto-generated props with ones in the examples files specified
72 * by the `getComponentProps()` method; props from the example files have
73 * priority
74 */
75 const mergeComponentPropsFromConfig = props => {
76 let componentProps = props;
77 // TODO this code is so complicated because getComponentProps(props) can return
78 // different values based on its props parameter.
79 // If it would always return the same thing then we could reduce the
80 // number of combinations generated by generatePropCombinations() by
81 // getComponentProps() reducing some to 1 value, it would also remove the
82 // need of PROPS_CACHE and duplicate checks.
83 // InstUI is not using the 'props' param of getComponentProps(), but others are
84 if (typeof config.getComponentProps === 'function') {
85 componentProps = {
86 ...componentProps,
87 ...config.getComponentProps(props)
88 };
89 }
90 return componentProps;
91 };
92 const getExampleProps = props => {
93 let exampleProps = {};
94 if (typeof config.getExampleProps === 'function') {
95 exampleProps = {
96 ...config.getExampleProps(props)
97 };
98 }
99 return exampleProps;
100 };
101 const addPage = section => {
102 const page = {
103 examples: [],
104 index: section.pages.length
105 };
106 section.pages.push(page);
107 return page;
108 };
109 const addExample = (sectionName, example) => {
110 let section = sections.find(section => section.sectionName === sectionName);
111 if (!section) {
112 section = {
113 sectionName: sectionName,
114 propName: sectionProp,
115 propValue: sectionName,
116 pages: []
117 };
118 sections.push(section);
119 }
120 let page = section.pages[section.pages.length - 1];
121 let maxExamplesPerPage = config.maxExamplesPerPage;
122 if (typeof maxExamplesPerPage === 'function') {
123 maxExamplesPerPage = maxExamplesPerPage(sectionName);
124 }
125 if (!page) {
126 page = addPage(section);
127 } else if (maxExamplesPerPage && page.examples.length % maxExamplesPerPage === 0 && page.examples.length > 0) {
128 page = addPage(section);
129 }
130 page.examples.push(example);
131 };
132
133 // Serializes the given recursively, faster than JSON.stringify()
134 const fastSerialize = props => {
135 const strArr = [];
136 objToString(props, strArr);
137 return strArr.join('');
138 };
139 const objToString = (currObject, currString) => {
140 if (!currObject) {
141 return;
142 }
143 if ( /*#__PURE__*/_react.default.isValidElement(currObject)) {
144 currString.push(JSON.stringify(currObject));
145 } else if (typeof currObject === 'object') {
146 for (const _ref of Object.entries(currObject)) {
147 var _ref2 = (0, _slicedToArray2.default)(_ref, 2);
148 const key = _ref2[0];
149 const value = _ref2[1];
150 currString.push(key);
151 objToString(value, currString);
152 }
153 } else {
154 currString.push(currObject);
155 }
156 };
157 const maybeAddExample = props => {
158 const componentProps = mergeComponentPropsFromConfig(props);
159 const ignore = typeof filter === 'function' ? filter(componentProps) : false;
160 if (ignore) {
161 return;
162 }
163 const propsString = fastSerialize(componentProps);
164 if (!PROPS_CACHE.includes(propsString)) {
165 const key = (0, _nanoid.nanoid)();
166 const exampleProps = getExampleProps(props);
167 exampleCount++;
168 if (exampleCount < maxExamples) {
169 PROPS_CACHE.push(propsString);
170 let sectionName = 'Examples';
171 if (sectionProp && componentProps[sectionProp]) {
172 sectionName = componentProps[sectionProp];
173 }
174 addExample(sectionName, {
175 Component,
176 componentProps,
177 exampleProps,
178 key
179 });
180 }
181 }
182 };
183 if (isEmpty(config.propValues)) {
184 maybeAddExample({});
185 } else {
186 if (Array.isArray(excludeProps)) {
187 ;
188 Object.keys(config.propValues).forEach(propName => {
189 if (!excludeProps.includes(propName)) {
190 propValues[propName] = config.propValues[propName];
191 }
192 });
193 } else {
194 propValues = config.propValues;
195 }
196 // eslint-disable-next-line no-console
197 console.info(`Generating examples for ${Component.displayName} (${Object.keys(propValues).length} props):`, propValues);
198 // TODO reconcile the differences between these files
199 // generatePropCombinations should call getComponentProps and not do anything?
200 const combos = (0, _generatePropCombinations.generatePropCombinations)(propValues).filter(Boolean);
201 let index = 0;
202 while (index < combos.length && exampleCount < maxExamples) {
203 const combo = combos[index];
204 if (combo) {
205 maybeAddExample(combo);
206 index++;
207 }
208 }
209 }
210 if (exampleCount >= maxExamples) {
211 console.error(`Too many examples for ${Component.displayName}! Add a filter to the config.`);
212 }
213
214 // eslint-disable-next-line no-console
215 console.info(`Generated ${exampleCount} examples for ${Component.displayName}`);
216 sections.forEach(_ref3 => {
217 let pages = _ref3.pages;
218 pages.forEach(page => {
219 // eslint-disable-next-line no-param-reassign
220 page.parameters = getParameters(page);
221 });
222 });
223 return sections;
224}
225function isEmpty(obj) {
226 if (typeof obj !== 'object') return true;
227 for (const key in obj) {
228 if (Object.hasOwnProperty.call(obj, key)) return false;
229 }
230 return true;
231}
232var _default = generateComponentExamples;
233exports.default = _default;
\No newline at end of file