Skip to content

Commit d79022c

Browse files
authored
admin-on-rest generator (#55)
* admin-on-rest generator initial * Configuration for each component to display fields, configuration for List buttons display, resources for each endpoint, resource import list * refactoring of component template & generator, configuration for Show & Edit buttons * fix 1
1 parent 38bae80 commit d79022c

File tree

12 files changed

+1268
-12
lines changed

12 files changed

+1268
-12
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"chalk": "^2.1.0",
3636
"commander": "^2.9.0",
3737
"handlebars": "^4.0.6",
38+
"handlebars-helpers": "^0.9.8",
3839
"isomorphic-fetch": "^2.2.1",
3940
"mkdirp": "^0.5.1",
4041
"sprintf-js": "^1.1.1"
@@ -44,7 +45,7 @@
4445
"lint": "eslint src",
4546
"build": "babel src -d lib --ignore '*.test.js'",
4647
"watch": "babel --watch src -d lib --ignore '*.test.js'",
47-
"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 && ./lib/index.js https://demo.api-platform.com ./tmp/vue -g vue",
48+
"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 && ./lib/index.js https://demo.api-platform.com ./tmp/vue -g vue && ./lib/index.js https://demo.api-platform.com ./tmp/admin-on-rest -g admin-on-rest",
4849
"test-gen-env": "rm -rf ./tmp && npm run build && API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=https://demo.api-platform.com API_PLATFORM_CLIENT_GENERATOR_OUTPUT=./tmp ./lib/index.js"
4950
},
5051
"bin": {

resource.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import React from 'react'
2+
import { Resource, Delete } from 'admin-on-rest'

src/generators.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AdminOnRestGenerator from './generators/AdminOnRestGenerator';
12
import ReactGenerator from './generators/ReactGenerator';
23
import ReactNativeGenerator from './generators/ReactNativeGenerator';
34
import TypescriptInterfaceGenerator from './generators/TypescriptInterfaceGenerator';
@@ -9,6 +10,8 @@ function wrap (cl) {
910

1011
export default function generators (generator = 'react') {
1112
switch (generator) {
13+
case 'admin-on-rest':
14+
return wrap(AdminOnRestGenerator);
1215
case 'react':
1316
return wrap(ReactGenerator);
1417
case 'react-native':
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import fs from 'fs';
2+
import BaseGenerator from './BaseGenerator';
3+
import handlebars from 'handlebars';
4+
import hbh_comparison from 'handlebars-helpers/lib/comparison';
5+
6+
export default class extends BaseGenerator {
7+
constructor(params) {
8+
super(params);
9+
10+
this.registerTemplates(`admin-on-rest/`, [
11+
'components/foo.js',
12+
'config/foo.js',
13+
'resources/foo.js',
14+
'resource-import.js',
15+
]);
16+
17+
handlebars.registerHelper('compare', hbh_comparison.compare);
18+
}
19+
20+
help(resource) {
21+
console.log('Code for the "%s" resource type has been generated!', resource.title);
22+
}
23+
24+
appendFile(template, dest, context = {}) {
25+
fs.appendFileSync(dest, this.templates[template](context));
26+
}
27+
28+
generate(api, resource, dir) {
29+
const lc = resource.title.toLowerCase();
30+
const titleUcFirst = resource.title.charAt(0).toUpperCase() + resource.title.slice(1);
31+
32+
const context = {
33+
title: resource.title,
34+
name: resource.name,
35+
lc,
36+
uc: resource.title.toUpperCase(),
37+
fields: resource.readableFields,
38+
formFields: this.buildFields(resource.writableFields),
39+
hydraPrefix: this.hydraPrefix,
40+
titleUcFirst,
41+
};
42+
43+
// Create directories
44+
// These directories may already exist
45+
for (let dir of [`${dir}/config`, `${dir}/resources`, `${dir}/components/`]) {
46+
this.createDir(dir, false);
47+
}
48+
49+
for (let pattern of [
50+
'components/%s.js',
51+
'config/%s.js',
52+
'resources/%s.js',
53+
]) {
54+
this.createFileFromPattern(pattern, dir, lc, context)
55+
}
56+
57+
this.appendFile('resource-import.js', `${dir}/resource-import.js`, context);
58+
59+
this.createEntrypoint(api.entrypoint, `${dir}/config/_entrypoint.js`)
60+
}
61+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Api from '@api-platform/api-doc-parser/lib/Api';
2+
import Resource from '@api-platform/api-doc-parser/lib/Resource';
3+
import Field from '@api-platform/api-doc-parser/lib/Field';
4+
import fs from 'fs';
5+
import tmp from 'tmp';
6+
import AdminOnRestGenerator from './AdminOnRestGenerator';
7+
8+
9+
test('Generate a Admin On Rest app', () => {
10+
const generator = new AdminOnRestGenerator({hydraPrefix: 'hydra:', templateDirectory: `${__dirname}/../../templates`});
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+
entrypoint: 'http://example.com:8080',
28+
title: 'My API',
29+
resources: [resource]
30+
});
31+
generator.generate(api, resource, tmpobj.name);
32+
33+
expect(fs.existsSync(tmpobj.name+'/config/_entrypoint.js'), true);
34+
35+
expect(fs.existsSync(tmpobj.name+'/components/abc.js'), true);
36+
37+
expect(fs.existsSync(tmpobj.name+'/config/abc.js'), true);
38+
39+
expect(fs.existsSync(tmpobj.name+'/resources/abc.js'), true);
40+
41+
expect(fs.existsSync(tmpobj.name+'/resource-import.js'), true);
42+
43+
tmpobj.removeCallback();
44+
});

src/generators/BaseGenerator.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export default class {
7676
case 'http://www.w3.org/2001/XMLSchema#time':
7777
return {type: 'time'};
7878

79+
case 'http://www.w3.org/2001/XMLSchema#dateTime':
80+
return {type: 'dateTime'};
81+
7982
default:
8083
return {type: 'text'};
8184
}

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ program
1212
.usage('entrypoint 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", "react-native", "vue"', 'react')
15+
.option('-g, --generator [generator]', 'The generator to use, one of "react", "react-native", "vue", "admin-on-rest"', 'react')
1616
.option('-t, --template-directory [templateDirectory]', 'The templates directory base to use. Final directory will be ${templateDirectory}/${generator}', `${__dirname}/../templates/`)
1717
.parse(process.argv);
1818

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
import {CardActions} from 'material-ui/Card';
3+
import {
4+
List, Datagrid, Edit, Create, Show, SimpleShowLayout, SimpleForm,
5+
DateField, TextField,
6+
TextInput, DateInput,
7+
EditButton,ShowButton, DeleteButton, RefreshButton, ListButton, CreateButton
8+
} from 'admin-on-rest';
9+
import {configList, configEdit, configCreate, configShow} from '../config/{{{lc}}}';
10+
11+
export const {{title}}List = (props) => (
12+
<List
13+
actions={<{{title}}ListActions/>}
14+
{...props}
15+
>
16+
<Datagrid>
17+
{{#each fields}}
18+
{configList.{{name}} && <TextField source="{{{name}}}" />}
19+
{{/each}}
20+
{configList.buttons.show && <ShowButton />}
21+
{configList.buttons.edit && <EditButton />}
22+
{configList.buttons.delete && <DeleteButton />}
23+
</Datagrid>
24+
</List>
25+
);
26+
27+
const {{title}}Title = ({record}) => {
28+
return <span>{{title}} {record && record.id ? ` : ${record.id}` : ''}</span>;
29+
};
30+
31+
export const {{title}}Edit = (props) => (
32+
<Edit
33+
actions={<{{title}}EditActions/>}
34+
title={<{{title}}Title />}
35+
{...props}
36+
>
37+
<SimpleForm>
38+
{{#each formFields}}
39+
{{#compare type "==" "dateTime" }}
40+
{configEdit.{{name}} && <DateInput source="{{{name}}}" />}
41+
{{else}}
42+
{configEdit.{{name}} && <TextInput source="{{{name}}}" />}
43+
{{/compare}}
44+
{{/each}}
45+
</SimpleForm>
46+
</Edit>
47+
);
48+
49+
export const {{title}}Create = (props) => (
50+
<Create title='Create a {{{title}}}' {...props}>
51+
<SimpleForm>
52+
{{#each formFields}}
53+
{{#compare type "==" "dateTime" }}
54+
{configCreate.{{name}} && <DateField source="{{{name}}}" />}
55+
{{else}}
56+
{configCreate.{{name}} && <TextInput source="{{{name}}}" />}
57+
{{/compare}}
58+
{{/each}}
59+
</SimpleForm>
60+
</Create>
61+
);
62+
63+
export const {{title}}Show = (props) => (
64+
<Show
65+
actions={<{{title}}ShowActions/>}
66+
title={<{{title}}Title />}
67+
{...props}
68+
>
69+
<SimpleShowLayout>
70+
{{#each fields}}
71+
{configShow.{{name}} && <TextField source="{{{name}}}" />}
72+
{{/each}}
73+
</SimpleShowLayout>
74+
</Show>
75+
);
76+
77+
const cardActionStyle = {
78+
zIndex: 2,
79+
display: 'inline-block',
80+
float: 'right',
81+
};
82+
83+
const {{title}}ListActions = ({basePath, data}) => (
84+
<CardActions style={cardActionStyle}>
85+
{configList.buttons.create && <CreateButton basePath={basePath} />}
86+
{configList.buttons.refresh && <RefreshButton basePath={basePath} record={data} />}
87+
</CardActions>
88+
);
89+
90+
const {{title}}ShowActions = ({basePath, data}) => (
91+
<CardActions style={cardActionStyle}>
92+
{configShow.buttons.edit && <EditButton basePath={basePath} record={data}/>}
93+
{configShow.buttons.list && <ListButton basePath={basePath}/>}
94+
{configShow.buttons.delete && <DeleteButton basePath={basePath} record={data}/>}
95+
{configShow.buttons.refresh && <RefreshButton basePath={basePath} record={data}/>}
96+
</CardActions>
97+
);
98+
99+
const {{title}}EditActions = ({basePath, data}) => (
100+
<CardActions style={cardActionStyle}>
101+
{configShow.buttons.show && <ShowButton basePath={basePath} record={data}/>}
102+
{configShow.buttons.list && <ListButton basePath={basePath}/>}
103+
{configShow.buttons.delete && <DeleteButton basePath={basePath} record={data}/>}
104+
{configShow.buttons.refresh && <RefreshButton basePath={basePath} record={data}/>}
105+
</CardActions>
106+
);

templates/admin-on-rest/config/foo.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export const configList = {
2+
'@id': true,
3+
{{#each fields}}
4+
{{{ name }}}: true,
5+
{{/each}}
6+
buttons: {
7+
show: true,
8+
edit: true,
9+
create: true,
10+
refresh: true,
11+
delete: true,
12+
}
13+
}
14+
15+
export const configEdit = {
16+
'@id': true,
17+
{{#each fields}}
18+
{{{ name }}}: true,
19+
{{/each}}
20+
buttons: {
21+
show: true,
22+
list: true,
23+
delete: true,
24+
refresh: true,
25+
}
26+
}
27+
28+
export const configCreate = {
29+
'@id': true,
30+
{{#each fields}}
31+
{{{ name }}}: true,
32+
{{/each}}
33+
buttons: {
34+
list: true,
35+
}
36+
}
37+
38+
export const configShow = {
39+
'@id': true,
40+
{{#each fields}}
41+
{{{ name }}}: true,
42+
{{/each}}
43+
buttons: {
44+
edit: true,
45+
list: true,
46+
delete: true,
47+
refresh: true,
48+
}
49+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as {{lc}} } from './resources/{{{lc}}}';

0 commit comments

Comments
 (0)