Skip to content

Commit 3d43b1f

Browse files
committed
scaffold skeleton for React Native
1 parent 0262e98 commit 3d43b1f

27 files changed

+543
-19
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"lint": "eslint src",
4444
"build": "babel src -d lib --ignore '*.test.js'",
4545
"watch": "babel --watch src -d lib --ignore '*.test.js'",
46-
"test-gen": "rm -rf ./tmp && npm run build && ./lib/index.js https://demo.api-platform.com ./tmp"
46+
"test-gen": "rm -rf ./tmp && npm run build && ./lib/index.js https://demo.api-platform.com ./tmp/react && ./lib/index.js https://demo.api-platform.com ./tmp/react-native -g react-native"
4747
},
4848
"bin": {
4949
"api-platform-generate-crud": "./lib/index.js"

src/generators.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ReactCrudGenerator from './generators/ReactCrudGenerator';
2+
import ReactNativeCrudGenerator from './generators/ReactNativeCrudGenerator';
23
import TypescriptInterfaceGenerator from './generators/TypescriptInterfaceGenerator';
34

45
function wrap (cl) {
@@ -9,6 +10,8 @@ function generators (generator = 'react') {
910
switch (generator) {
1011
case 'react':
1112
return wrap(ReactCrudGenerator);
13+
case 'react-native':
14+
return wrap(ReactNativeCrudGenerator);
1215
case 'typescript':
1316
return wrap(TypescriptInterfaceGenerator);
1417
}

src/generators/ReactCrudGenerator.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ export default class ReactCrudGenerator {
88
templates = {};
99

1010
constructor(hydraPrefix) {
11+
const templatePathCommon = `${__dirname}/../../templates/react-common/`;
1112
const templatePath = `${__dirname}/../../templates/react/`;
1213

1314
this.hydraPrefix = hydraPrefix;
1415

1516
// actions
16-
this.registerTemplate(templatePath, 'actions/foo/create.js');
17-
this.registerTemplate(templatePath, 'actions/foo/delete.js');
18-
this.registerTemplate(templatePath, 'actions/foo/list.js');
19-
this.registerTemplate(templatePath, 'actions/foo/update.js');
20-
this.registerTemplate(templatePath, 'actions/foo/show.js');
17+
this.registerTemplate(templatePathCommon, 'actions/foo/create.js');
18+
this.registerTemplate(templatePathCommon, 'actions/foo/delete.js');
19+
this.registerTemplate(templatePathCommon, 'actions/foo/list.js');
20+
this.registerTemplate(templatePathCommon, 'actions/foo/update.js');
21+
this.registerTemplate(templatePathCommon, 'actions/foo/show.js');
2122

2223
// api
23-
this.registerTemplate(templatePath, 'api/fooFetch.js');
24+
this.registerTemplate(templatePathCommon, 'api/fooFetch.js');
2425

2526
// components
2627
this.registerTemplate(templatePath, 'components/foo/Create.js');
@@ -31,18 +32,18 @@ export default class ReactCrudGenerator {
3132
this.registerTemplate(templatePath, 'components/foo/Show.js');
3233

3334
// reducers
34-
this.registerTemplate(templatePath, 'reducers/foo/create.js');
35-
this.registerTemplate(templatePath, 'reducers//foo/delete.js');
36-
this.registerTemplate(templatePath, 'reducers/foo/index.js');
37-
this.registerTemplate(templatePath, 'reducers/foo/list.js');
38-
this.registerTemplate(templatePath, 'reducers/foo/update.js');
39-
this.registerTemplate(templatePath, 'reducers/foo/show.js');
35+
this.registerTemplate(templatePathCommon, 'reducers/foo/create.js');
36+
this.registerTemplate(templatePathCommon, 'reducers//foo/delete.js');
37+
this.registerTemplate(templatePathCommon, 'reducers/foo/index.js');
38+
this.registerTemplate(templatePathCommon, 'reducers/foo/list.js');
39+
this.registerTemplate(templatePathCommon, 'reducers/foo/update.js');
40+
this.registerTemplate(templatePathCommon, 'reducers/foo/show.js');
4041

4142
// routes
4243
this.registerTemplate(templatePath, 'routes/foo.js');
4344

4445
// entrypoint
45-
this.registerTemplate(templatePath, 'api/_entrypoint.js');
46+
this.registerTemplate(templatePathCommon, 'api/_entrypoint.js');
4647

4748
// utils
4849
this.registerTemplate(templatePath, 'utils/helpers.js');

src/generators/ReactCrudGenerator.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import Resource from 'api-doc-parser/lib/Resource';
33
import Field from 'api-doc-parser/lib/Field';
44
import fs from 'fs';
55
import tmp from 'tmp';
6-
import ReactCrudGenerator from './ReactCrudGenerator';
6+
import ReactNativeCrudGenerator from './ReactNativeCrudGenerator';
77

88

9-
test('Generate a React app', () => {
10-
const generator = new ReactCrudGenerator('hydra:');
9+
test('Generate a React Native app', () => {
10+
const generator = new ReactNativeCrudGenerator('hydra:');
1111
const tmpobj = tmp.dirSync({unsafeCleanup: true});
1212

1313
const fields = [new Field('bar', {
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import mkdirp from 'mkdirp';
2+
import handlebars from 'handlebars';
3+
import fs from 'fs';
4+
import urlapi from 'url';
5+
import chalk from 'chalk';
6+
7+
export default class ReactCrudGenerator {
8+
templates = {};
9+
10+
constructor(hydraPrefix) {
11+
const templatePathCommon = `${__dirname}/../../templates/react-common/`;
12+
const templatePath = `${__dirname}/../../templates/react-native/`;
13+
14+
this.hydraPrefix = hydraPrefix;
15+
16+
// actions
17+
this.registerTemplate(templatePathCommon, 'actions/foo/create.js');
18+
this.registerTemplate(templatePathCommon, 'actions/foo/delete.js');
19+
this.registerTemplate(templatePathCommon, 'actions/foo/list.js');
20+
this.registerTemplate(templatePathCommon, 'actions/foo/update.js');
21+
this.registerTemplate(templatePathCommon, 'actions/foo/show.js');
22+
23+
// api
24+
this.registerTemplate(templatePathCommon, 'api/fooFetch.js');
25+
26+
// components
27+
this.registerTemplate(templatePath, 'components/foo/Create.js');
28+
this.registerTemplate(templatePath, 'components/foo/Form.js');
29+
this.registerTemplate(templatePath, 'components/foo/index.js');
30+
this.registerTemplate(templatePath, 'components/foo/List.js');
31+
this.registerTemplate(templatePath, 'components/foo/Update.js');
32+
this.registerTemplate(templatePath, 'components/foo/Show.js');
33+
34+
// reducers
35+
this.registerTemplate(templatePathCommon, 'reducers/foo/create.js');
36+
this.registerTemplate(templatePathCommon, 'reducers//foo/delete.js');
37+
this.registerTemplate(templatePathCommon, 'reducers/foo/index.js');
38+
this.registerTemplate(templatePathCommon, 'reducers/foo/list.js');
39+
this.registerTemplate(templatePathCommon, 'reducers/foo/update.js');
40+
this.registerTemplate(templatePathCommon, 'reducers/foo/show.js');
41+
42+
// entrypoint
43+
this.registerTemplate(templatePathCommon, 'api/_entrypoint.js');
44+
}
45+
46+
registerTemplate(templatePath, path) {
47+
this.templates[path] = handlebars.compile(fs.readFileSync(templatePath+path).toString());
48+
}
49+
50+
help(resource) {
51+
const titleLc = resource.title.toLowerCase()
52+
53+
console.log('Code for the "%s" resource type has been generated!', resource.title);
54+
console.log('Paste the following definitions in your application configuration:');
55+
console.log(chalk.green(`
56+
// import reducers
57+
import ${titleLc} from './reducers/${titleLc}/';
58+
59+
// Add the reducer
60+
combineReducers(${titleLc},{/* ... */}),
61+
62+
`));
63+
}
64+
65+
generate(api, resource, dir) {
66+
const lc = resource.title.toLowerCase();
67+
const titleUcFirst = resource.title.charAt(0).toUpperCase() + resource.title.slice(1);
68+
69+
const context = {
70+
title: resource.title,
71+
name: resource.name,
72+
lc,
73+
uc: resource.title.toUpperCase(),
74+
fields: resource.readableFields,
75+
formFields: this.buildFields(resource.writableFields),
76+
hydraPrefix: this.hydraPrefix,
77+
titleUcFirst
78+
};
79+
80+
81+
// Create directories
82+
// These directories may already exist
83+
mkdirp.sync(`${dir}/api`);
84+
85+
this.createDir(`${dir}/actions/${lc}`);
86+
this.createDir(`${dir}/components/${lc}`);
87+
this.createDir(`${dir}/reducers/${lc}`);
88+
89+
// actions
90+
this.createFile('actions/foo/create.js', `${dir}/actions/${lc}/create.js`, context);
91+
this.createFile('actions/foo/delete.js', `${dir}/actions/${lc}/delete.js`, context);
92+
this.createFile('actions/foo/list.js', `${dir}/actions/${lc}/list.js`, context);
93+
this.createFile('actions/foo/update.js', `${dir}/actions/${lc}/update.js`, context);
94+
this.createFile('actions/foo/show.js', `${dir}/actions/${lc}/show.js`, context);
95+
96+
// api
97+
this.createFile('api/fooFetch.js', `${dir}/api/${lc}Fetch.js`, context);
98+
99+
// components
100+
this.createFile('components/foo/Create.js', `${dir}/components/${lc}/Create.js`, context);
101+
this.createFile('components/foo/Form.js', `${dir}/components/${lc}/Form.js`, context);
102+
this.createFile('components/foo/index.js', `${dir}/components/${lc}/index.js`, context);
103+
this.createFile('components/foo/List.js', `${dir}/components/${lc}/List.js`, context);
104+
this.createFile('components/foo/Update.js', `${dir}/components/${lc}/Update.js`, context);
105+
this.createFile('components/foo/Show.js', `${dir}/components/${lc}/Show.js`, context);
106+
107+
// reducers
108+
this.createFile('reducers/foo/create.js', `${dir}/reducers/${lc}/create.js`, context);
109+
this.createFile('reducers//foo/delete.js', `${dir}/reducers/${lc}/delete.js`, context);
110+
this.createFile('reducers/foo/index.js', `${dir}/reducers/${lc}/index.js`, context);
111+
this.createFile('reducers/foo/list.js', `${dir}/reducers/${lc}/list.js`, context);
112+
this.createFile('reducers/foo/update.js', `${dir}/reducers/${lc}/update.js`, context);
113+
this.createFile('reducers/foo/show.js', `${dir}/reducers/${lc}/show.js`, context);
114+
}
115+
116+
entrypoint(apiEntry, dir) {
117+
const url = urlapi.parse(apiEntry);
118+
const {protocol, host, port, pathname} = url;
119+
const hostUrl = `${protocol}//${host}${port ? `:${port}` : ''}`;
120+
121+
const context = {
122+
host: hostUrl,
123+
path: pathname
124+
}
125+
126+
this.createFile('api/_entrypoint.js', `${dir}/api/_entrypoint.js`, context);
127+
}
128+
129+
getInputTypeFromField(field) {
130+
switch (field.id) {
131+
case 'http://schema.org/email':
132+
return {type: 'email'};
133+
134+
case 'http://schema.org/url':
135+
return {type: 'url'};
136+
}
137+
138+
switch (field.range) {
139+
case 'http://www.w3.org/2001/XMLSchema#integer':
140+
return {type: 'number'};
141+
142+
case 'http://www.w3.org/2001/XMLSchema#decimal':
143+
return {type: 'number', step: '0.1'};
144+
145+
case 'http://www.w3.org/2001/XMLSchema#boolean':
146+
return {type: 'checkbox'};
147+
148+
case 'http://www.w3.org/2001/XMLSchema#date':
149+
return {type: 'date'};
150+
151+
case 'http://www.w3.org/2001/XMLSchema#time':
152+
return {type: 'time'};
153+
154+
default:
155+
return {type: 'text'};
156+
}
157+
}
158+
159+
buildFields(apiFields) {
160+
let fields = [];
161+
for (let apiField of apiFields) {
162+
let field = this.getInputTypeFromField(apiField);
163+
field.required = apiField.required;
164+
field.name = apiField.name;
165+
field.description = apiField.description.replace(/"/g, "'"); // fix for Form placeholder description
166+
167+
fields.push(field)
168+
}
169+
170+
return fields;
171+
}
172+
173+
createDir(dir) {
174+
if (fs.existsSync(dir)) throw new Error(`The directory "${dir}" already exists`);
175+
mkdirp.sync(dir);
176+
}
177+
178+
createFile(template, dest, context) {
179+
fs.writeFileSync(dest, this.templates[template](context));
180+
}
181+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Api from 'api-doc-parser/lib/Api';
2+
import Resource from 'api-doc-parser/lib/Resource';
3+
import Field from 'api-doc-parser/lib/Field';
4+
import fs from 'fs';
5+
import tmp from 'tmp';
6+
import ReactCrudGenerator from './ReactCrudGenerator';
7+
8+
9+
test('Generate a React app', () => {
10+
const generator = new ReactCrudGenerator('hydra:');
11+
const tmpobj = tmp.dirSync({unsafeCleanup: true});
12+
13+
const fields = [new Field('bar', {
14+
id: 'http://schema.org/url',
15+
range: 'http://www.w3.org/2001/XMLSchema#string',
16+
reference: null,
17+
required: true,
18+
description: 'An URL'
19+
})];
20+
const resource = new Resource('abc', 'http://example.com/foos', {
21+
id: 'foo',
22+
title: 'Foo',
23+
readableFields: fields,
24+
writableFields: fields
25+
});
26+
const api = new Api('http://example.com', {
27+
title: 'My API',
28+
resources: [resource]
29+
});
30+
generator.generate(api, resource, tmpobj.name);
31+
32+
expect(fs.existsSync(tmpobj.name+'/actions/abc/create.js'), true);
33+
expect(fs.existsSync(tmpobj.name+'/actions/abc/delete.js'), true);
34+
expect(fs.existsSync(tmpobj.name+'/actions/abc/list.js'), true);
35+
expect(fs.existsSync(tmpobj.name+'/actions/abc/update.js'), true);
36+
37+
expect(fs.existsSync(tmpobj.name+'/api/abcFetch.js'), true);
38+
39+
expect(fs.existsSync(tmpobj.name+'/components/abc/Create.js'), true);
40+
expect(fs.existsSync(tmpobj.name+'/components/abc/Form.js'), true);
41+
expect(fs.existsSync(tmpobj.name+'/components/abc/List.js'), true);
42+
expect(fs.existsSync(tmpobj.name+'/components/abc/Update.js'), true);
43+
44+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/create.js'), true);
45+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/delete.js'), true);
46+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/index.js'), true);
47+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/list.js'), true);
48+
expect(fs.existsSync(tmpobj.name+'/reducers/abc/update.js'), true);
49+
50+
tmpobj.removeCallback();
51+
});

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ program
1212
.usage('apiEntrypoint outputDirectory')
1313
.option('-r, --resource [resourceName]', 'Generate CRUD for the given resource')
1414
.option('-p, --hydra-prefix [hydraPrefix]', 'The hydra prefix used by the API', 'hydra:')
15-
.option('-g, --generator [generator]', 'The generator to use, one of "react", "angular" etc.', 'react')
15+
.option('-g, --generator [generator]', 'The generator to use, one of "react", "react-native" ', 'react')
1616
.parse(process.argv);
1717

1818
if (2 !== program.args.length) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, {Component} from 'react';
2+
import {ScrollView, Text} from 'react-native';
3+
import {connect} from 'react-redux';
4+
import PropTypes from 'prop-types';
5+
import Form from './Form';
6+
import { create, loading, error } from '../../actions/{{{ lc }}}/create';
7+
8+
class Create extends Component {
9+
static propTypes = {
10+
error: PropTypes.string,
11+
loading: PropTypes.bool.isRequired,
12+
created: PropTypes.object,
13+
create: PropTypes.func.isRequired,
14+
reset: PropTypes.func.isRequired,
15+
};
16+
17+
componentWillUnmount() {
18+
this.props.reset();
19+
}
20+
21+
render() {
22+
return (
23+
<ScrollView>
24+
<Text>Create component {{lc}}</Text>
25+
</ScrollView>
26+
);
27+
}
28+
}
29+
30+
const mapStateToProps = (state) => {
31+
return {
32+
created: state.{{{ lc }}}.create.created,
33+
error: state.{{{ lc }}}.create.error,
34+
loading: state.{{{ lc }}}.create.loading,
35+
};
36+
};
37+
38+
const mapDispatchToProps = (dispatch) => {
39+
return {
40+
create: values => dispatch(create(values)),
41+
reset: () => {
42+
dispatch(loading(false));
43+
dispatch(error(null));
44+
},
45+
};
46+
};
47+
48+
export default connect(mapStateToProps, mapDispatchToProps)(Create);

0 commit comments

Comments
 (0)