This repository was archived by the owner on Jun 4, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathgenerate-components.js
218 lines (178 loc) · 5.4 KB
/
generate-components.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
'use strict';
/**
* Generates React components from a newline-separated list of HTML elements.
*/
const fs = require('fs');
const path = require('path');
const srcPath = '../src/components';
const attributesPath = './data/attributes.json';
const PROP_TYPES = {
_default: 'string',
style: 'object'
};
function bail(message) {
console.error('Error: ' + message);
process.exit(1);
}
function upperCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function nameComponent(elementName) {
const reservedWords = {
'object': 'ObjectEl',
'map': 'MapEl'
};
return reservedWords[elementName] || upperCase(elementName);
}
function generatePropTypes(element, attributes) {
const elements = attributes.elements;
// Always add the list of global attributes.
const supportedAttributes = elements[element] ?
elements[element].concat(elements.Globalattribute) :
elements.Globalattribute;
return `
/**
* The ID of this component, used to identify dash components
* in callbacks. The ID needs to be unique across all of the
* components in an app.
*/
'id': PropTypes.string,
/**
* The children of this component
*/
'children': PropTypes.node,
/**
* An integer that represents the number of times
* that this element has been clicked on.
*/
'n_clicks': PropTypes.number,
/**
* An integer that represents the time (in ms since 1970)
* at which n_clicks changed. This can be used to tell
* which button was changed most recently.
*/
'n_clicks_timestamp': PropTypes.number,
/**
* A unique identifier for the component, used to improve
* performance by React.js while rendering components
* See https://reactjs.org/docs/lists-and-keys.html for more info
*/
'key': PropTypes.string,
/**
* The ARIA role attribute
*/
'role': PropTypes.string,
/**
* A wildcard data attribute
*/
'data-*': PropTypes.string,
/**
* A wildcard aria attribute
*/
'aria-*': PropTypes.string,` +
supportedAttributes.reduce((propTypes, attributeName) => {
const attribute = attributes.attributes[attributeName];
const propType = PROP_TYPES[attributeName] || PROP_TYPES._default;
if (attributeName === 'id') {
return propTypes;
}
return propTypes + `
/**
*${attribute.description ? ' ' + attribute.description : ''}
*/
'${attributeName}': PropTypes.${propType},`;
}, '') + `
/**
* Object that holds the loading state object coming from dash-renderer
*/
'loading_state': PropTypes.shape({
/**
* Determines if the component is loading or not
*/
is_loading: PropTypes.bool,
/**
* Holds which property is loading
*/
prop_name: PropTypes.string,
/**
* Holds the name of the component that is loading
*/
component_name: PropTypes.string,
}),
'setProps': PropTypes.func`
}
function generateComponent(Component, element, attributes) {
const propTypes = generatePropTypes(element, attributes);
return `
import React from 'react';
import PropTypes from 'prop-types';
import {omit} from 'ramda';
const ${Component} = (props) => {
const dataAttributes = {};
if(props.loading_state && props.loading_state.is_loading) {
dataAttributes['data-dash-is-loading'] = true;
}
return (
<${element}
onClick={() => {
if (props.setProps) {
props.setProps({
n_clicks: props.n_clicks + 1,
n_clicks_timestamp: Date.now()
})
}
}}
{...omit(['n_clicks', 'n_clicks_timestamp', 'loading_state', 'setProps'], props)}
{...dataAttributes}
>
{props.children}
</${element}>
);
};
${Component}.defaultProps = {
n_clicks: 0,
n_clicks_timestamp: -1,
};
${Component}.propTypes = {${propTypes}
};
export default ${Component};
`;
}
/**
* Generate an object with Component names as keys, component definitions as
* values
*/
function generateComponents(list, attributes) {
return list.reduce((componentMap, element) => {
const componentName = nameComponent(element);
const Component = generateComponent(componentName, element, attributes);
componentMap[componentName] = Component;
return componentMap;
}, {});
}
/**
* Writes component definitions to disk.
*/
function writeComponents(components, destination) {
console.log(`Writing ${Object.keys(components).length} component files to ${srcPath}.`);
let componentPath;
for (const Component in components) {
componentPath = path.join(destination, `${Component}.react.js`);
fs.writeFileSync(componentPath, components[Component]);
}
}
// Get first command-line argument
const listPath = process.argv[2];
if (!listPath) {
bail('Must specify an element list.');
}
// Read the list of elements
const list = fs
.readFileSync(listPath, 'utf8')
.split('\n')
.filter(item => Boolean(item));
// Get the mapping of attributes to elements
const attributes = JSON.parse(fs.readFileSync(attributesPath, 'utf-8'));
const components = generateComponents(list, attributes);
writeComponents(components, srcPath);
console.log('Done.');