reativo 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -0
  3. data/app/controllers/reativo/crud_controller.rb +17 -2
  4. data/lib/generators/reativo/install_generator.rb +55 -0
  5. data/lib/generators/reativo/js_generator.rb +78 -0
  6. data/lib/generators/reativo/op_generator.rb +96 -0
  7. data/lib/generators/reativo/templates/component/Edit.js +43 -0
  8. data/lib/generators/reativo/templates/component/Form.js +52 -0
  9. data/lib/generators/reativo/templates/component/Index.js +51 -0
  10. data/lib/generators/reativo/templates/component/New.js +37 -0
  11. data/lib/generators/reativo/templates/component/Show.js +32 -0
  12. data/lib/generators/reativo/templates/operation/create.erb +3 -4
  13. data/lib/generators/reativo/templates/operation/destroy.erb +2 -2
  14. data/lib/generators/reativo/templates/operation/index.erb +2 -1
  15. data/lib/generators/reativo/templates/operation/update.erb +3 -4
  16. data/lib/generators/reativo/templates/representer/create.erb +1 -1
  17. data/lib/generators/reativo/templates/representer/index.erb +1 -1
  18. data/lib/generators/reativo/templates/representer/module.erb +1 -1
  19. data/lib/generators/reativo/templates/representer/update.erb +1 -1
  20. data/lib/generators/reativo/templates/support/store.js +24 -0
  21. data/lib/generators/reativo/templates/support/theme.js +25 -0
  22. data/lib/generators/reativo/templates/theme/Baseline.js +10 -0
  23. data/lib/generators/reativo/templates/theme/Drawer.js +50 -0
  24. data/lib/generators/reativo/templates/theme/MainBar.js +223 -0
  25. data/lib/generators/reativo/templates/theme/Menu.js +79 -0
  26. data/lib/generators/reativo/templates/theme/Snackbar.js +9 -0
  27. data/lib/generators/reativo/templates/theme/layout.erb +59 -0
  28. data/lib/generators/reativo/templates/theme/layout.rb +6 -0
  29. data/lib/reativo/version.rb +1 -1
  30. metadata +33 -4
  31. data/lib/generators/reativo/USAGE +0 -23
  32. data/lib/generators/reativo/reativo_generator.rb +0 -63
@@ -0,0 +1,51 @@
1
+ import { hot } from 'react-hot-loader/root'
2
+ import React from 'react'
3
+
4
+ import Button from '@material-ui/core/Button'
5
+ import MaterialTable from 'material-table'
6
+ import { wrapper } from "reativo"
7
+
8
+ function Index({model, rows}) {
9
+ return (
10
+ <>
11
+ <MaterialTable
12
+ columns={[
13
+ { title: 'ID', field: 'id' },
14
+ <%- options[:properties].each do |prop| -%>
15
+ { title: '<%= prop.humanize %>', field: '<%= prop %>' },
16
+ <%- end -%>
17
+ ]}
18
+ data={model}
19
+ title="<%= model_name_plural %>"
20
+ actions={[
21
+ rowData => ({
22
+ icon: 'create',
23
+ iconProps: {'data-testid': `edit-${rowData.id}`},
24
+ tooltip: 'Edit <%= model_name_singular %>',
25
+ onClick: (event, rowData) => {
26
+ Turbolinks.visit(`/<%= collection_path %>/${rowData.id}/edit`)
27
+ },
28
+ })
29
+ ]}
30
+ options={{
31
+ columnsButton: true,
32
+ exportButton: true,
33
+ actionsColumnIndex: -1,
34
+ }}
35
+ detailPanel={rowData => {
36
+ return (
37
+ <pre>
38
+ {rowData.key}
39
+ </pre>
40
+ )
41
+ }}
42
+ onRowClick={(event, rowData, togglePanel) => togglePanel()}
43
+ />
44
+ <Button variant="contained" color="secondary" href="/<%= collection_path %>/new">
45
+ New <%= model_name_singular %>
46
+ </Button>
47
+ </>
48
+ )
49
+ }
50
+
51
+ export default hot(wrapper(Index))
@@ -0,0 +1,37 @@
1
+ import { hot } from 'react-hot-loader/root'
2
+ import React from 'react'
3
+
4
+ import {
5
+ Typography,
6
+ Button,
7
+ } from '@material-ui/core';
8
+ import Form from './Form'
9
+
10
+ import { Form as FinalForm } from 'react-final-form'
11
+ import { RailsForm } from 'reativo'
12
+ import { validate } from './Form'
13
+ import { wrapper } from "reativo"
14
+
15
+ function New() {
16
+ return (
17
+ <div style={{ padding: 16, margin: 'auto', maxWidth: 600 }}>
18
+ <Typography variant="h4" align="center" component="h1" gutterBottom>
19
+ New <%= model_name_singular %>
20
+ </Typography>
21
+ <RailsForm
22
+ component={FinalForm}
23
+ action='create'
24
+ url='/<%= collection_path %>'
25
+ validate={validate}
26
+ render={(props) => (
27
+ <Form {...props} />
28
+ )}
29
+ />
30
+ <Button variant="contained" color="secondary" href="/<%= collection_path %>">
31
+ <%= model_name_plural %>
32
+ </Button>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export default hot(wrapper(New))
@@ -0,0 +1,32 @@
1
+ import { hot } from 'react-hot-loader/root'
2
+ import React from 'react'
3
+
4
+ import {
5
+ Typography,
6
+ Button,
7
+ } from '@material-ui/core'
8
+
9
+ import { wrapper } from "reativo"
10
+
11
+ function Show({model}) {
12
+ const { id } = model
13
+
14
+ return (
15
+ <div style={{ padding: 16, margin: 'auto', maxWidth: 600 }}>
16
+ <Typography variant="h4" align="center" component="h1" gutterBottom>
17
+ <%= model_name_singular %>
18
+ </Typography>
19
+ <%- options[:properties].each do |prop| -%>
20
+ <p><%= prop.humanize %>: {model.<%= prop %>}</p>
21
+ <%- end -%>
22
+ <Button variant="contained" color="primary" href={`/<%= collection_path %>/${id}/edit`}>
23
+ Edit
24
+ </Button>
25
+ <Button variant="contained" color="secondary" href="/<%= collection_path %>">
26
+ Back to <%= model_name_plural %>
27
+ </Button>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export default hot(wrapper(Show))
@@ -1,16 +1,15 @@
1
1
  module <%= class_name %>::Operation
2
2
  class Create < Trailblazer::Operation
3
3
  class Present < Trailblazer::Operation
4
+ step ->(options, params) { options["representer.render.class"] = <%= class_name %>::Representer::Create }
5
+ step ->(options, params) { options["representer.errors.class"] = Reativo::Representer::Errors }
4
6
  step Model(<%= model_name %>, :new)
5
7
  step Contract::Build( constant: <%= class_name %>::Contract::Create )
6
8
  end
7
9
 
8
- step ->(options, params) { options["representer.render.class"] = <%= class_name %>::Representer::Create }
9
- step ->(options, params) { options["representer.errors.class"] = Reativo::Representer::Errors }
10
10
  step Nested( Present )
11
-
12
11
  step Contract::Build(constant: <%= class_name %>::Contract::Create)
13
- step Contract::Validate(key: '<%= model_name.underscore %>')
12
+ step Contract::Validate(key: '<%= model_name.demodulize.underscore %>')
14
13
  step Contract::Persist()
15
14
  end
16
15
  end
@@ -1,12 +1,12 @@
1
1
  module <%= class_name %>::Operation
2
- class Create < Trailblazer::Operation
2
+ class Destroy < Trailblazer::Operation
3
3
  step ->(options, params) { options["representer.render.class"] = <%= class_name %>::Representer::Create }
4
4
  step ->(options, params) { options["representer.errors.class"] = Reativo::Representer::Errors }
5
5
  step Model( <%= model_name %>, :find )
6
6
  step :destroy!
7
7
 
8
8
  def destroy!(ctx, params:, current_user:, **)
9
- options[:model].destroy
9
+ ctx[:model].destroy
10
10
  end
11
11
  end
12
12
  end
@@ -15,7 +15,8 @@ module <%= class_name %>::Operation
15
15
  step :cache_key!
16
16
 
17
17
  def scope!(ctx, params:, current_user:, **)
18
- ctx[:scope] = <%= class_name.singularize %>.where(user: current_user).order(id: :asc)
18
+ # Change the scope, so avoid data leaking
19
+ ctx[:scope] = <%= model_name.singularize %>.where('created_at > ?', DateTime.current)
19
20
  end
20
21
 
21
22
  def model!(ctx, params:, current_user:, **)
@@ -1,16 +1,15 @@
1
1
  module <%= class_name %>::Operation
2
2
  class Update < Trailblazer::Operation
3
3
  class Present < Trailblazer::Operation
4
+ step ->(options, params) { options["representer.render.class"] = <%= class_name %>::Representer::Update }
5
+ step ->(options, params) { options["representer.errors.class"] = Reativo::Representer::Errors }
4
6
  step Model(<%= model_name %>, :find)
5
7
  step Contract::Build( constant: <%= class_name %>::Contract::Update )
6
8
  end
7
9
 
8
- step ->(options, params) { options["representer.render.class"] = <%= class_name %>::Representer::Update }
9
- step ->(options, params) { options["representer.errors.class"] = Reativo::Representer::Errors }
10
10
  step Nested( Present )
11
-
12
11
  step Contract::Build(constant: <%= class_name %>::Contract::Update)
13
- step Contract::Validate(key: '<%= model_name.underscore %>')
12
+ step Contract::Validate(key: '<%= model_name.demodulize.underscore %>')
14
13
  step Contract::Persist()
15
14
  end
16
15
  end
@@ -1,6 +1,6 @@
1
1
  module <%= class_name %>::Representer
2
2
  module Create
3
3
  include Representable::JSON
4
- include <%= model_name %>Module
4
+ include <%= model_name.demodulize %>Module
5
5
  end
6
6
  end
@@ -4,7 +4,7 @@ module <%= class_name %>::Representer
4
4
 
5
5
  items class: <%= model_name %> do
6
6
  include Representable::JSON
7
- include <%= model_name %>Module
7
+ include <%= model_name.demodulize %>Module
8
8
  end
9
9
  end
10
10
  end
@@ -1,5 +1,5 @@
1
1
  module <%= class_name %>::Representer
2
- module <%= model_name %>Module
2
+ module <%= model_name.demodulize %>Module
3
3
  include Reform::Form::Module
4
4
 
5
5
  <%- options[:properties].each do |property| -%>
@@ -1,6 +1,6 @@
1
1
  module <%= class_name %>::Representer
2
2
  module Update
3
3
  include Representable::JSON
4
- include <%= model_name %>Module
4
+ include <%= model_name.demodulize %>Module
5
5
  end
6
6
  end
@@ -0,0 +1,24 @@
1
+ import { createStore } from 'redux'
2
+ import { combineReducers } from 'redux'
3
+
4
+ const DRAWER_OPEN = 'DRAWER_OPEN'
5
+ const DRAWER_CLOSE = 'DRAWER_CLOSE'
6
+
7
+ function drawer(state = { open: true }, action) {
8
+ switch (action.type) {
9
+ case DRAWER_OPEN:
10
+ return {...action.props, open: true}
11
+ case DRAWER_CLOSE:
12
+ return {...action.props, open: false}
13
+ default:
14
+ return state
15
+ }
16
+ }
17
+
18
+ const rootReducer = combineReducers({
19
+ drawer,
20
+ })
21
+
22
+ const store = createStore(rootReducer)
23
+
24
+ export default store
@@ -0,0 +1,25 @@
1
+ import { createMuiTheme } from '@material-ui/core/styles';
2
+
3
+ export const theme = createMuiTheme({
4
+ palette: {
5
+ },
6
+ typography: {
7
+ useNextVariants: true,
8
+ },
9
+ overrides: {
10
+ MuiAppBar: {
11
+ root: {
12
+ boxShadow: 'unset',
13
+ backgroundColor: "#aaa"
14
+ }
15
+ },
16
+ MuiSvgIcon: { // Name of the component ⚛️ / style sheet
17
+ root: { // Name of the rule
18
+ color: '#000', // Some CSS
19
+ opacity: 0.54,
20
+ },
21
+ },
22
+ },
23
+ });
24
+
25
+ export default theme
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import CssBaseline from '@material-ui/core/CssBaseline';
3
+
4
+ function Baseline() {
5
+ return (
6
+ <CssBaseline />
7
+ );
8
+ }
9
+
10
+ export default Baseline;
@@ -0,0 +1,50 @@
1
+ import React from "react"
2
+ import PropTypes from "prop-types"
3
+ import Button from '@material-ui/core/Button'
4
+ import { hot } from 'react-hot-loader/root'
5
+ import { withStyles } from '@material-ui/core/styles';
6
+ import { wrapper } from "reativo"
7
+ import Menu from "./Menu"
8
+
9
+ const styles = theme => ({
10
+ root: {
11
+ paddingBottom: 10
12
+ },
13
+ })
14
+
15
+ class Drawer extends React.Component {
16
+ render () {
17
+ const { classes } = this.props;
18
+ return (
19
+ <div className={classes.root}>
20
+ <Menu />
21
+ </div>
22
+ );
23
+ }
24
+ }
25
+
26
+ Drawer.propTypes = {
27
+ classes: PropTypes.object.isRequired,
28
+ };
29
+
30
+
31
+ function mapStateToProps (state) {
32
+ return {
33
+ drawer: state.drawer
34
+ }
35
+ }
36
+
37
+ function mapDispatchToProps (dispatch) {
38
+ return {
39
+ toggle: () => {
40
+ dispatch({type: "DRAWER_TOGGLE"})
41
+ },
42
+ }
43
+ }
44
+
45
+ export default hot(
46
+ withStyles(styles)(
47
+ wrapper(Drawer, mapStateToProps, mapDispatchToProps)
48
+ )
49
+ )
50
+
@@ -0,0 +1,223 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import AppBar from '@material-ui/core/AppBar';
4
+ import Toolbar from '@material-ui/core/Toolbar';
5
+ import IconButton from '@material-ui/core/IconButton';
6
+ import Typography from '@material-ui/core/Typography';
7
+ import InputBase from '@material-ui/core/InputBase';
8
+ import MenuItem from '@material-ui/core/MenuItem';
9
+ import Menu from '@material-ui/core/Menu';
10
+ import { fade } from '@material-ui/core/styles/colorManipulator';
11
+ import { withStyles } from '@material-ui/core/styles';
12
+ import MenuIcon from '@material-ui/icons/Menu';
13
+ import SearchIcon from '@material-ui/icons/Search';
14
+ import AccountCircle from '@material-ui/icons/AccountCircle';
15
+ import MailIcon from '@material-ui/icons/Mail';
16
+ import NotificationsIcon from '@material-ui/icons/Notifications';
17
+ import MoreIcon from '@material-ui/icons/MoreVert';
18
+ import { wrapper } from "reativo"
19
+ import { hot } from 'react-hot-loader/root'
20
+
21
+ const styles = theme => ({
22
+ root: {
23
+ width: '100%',
24
+ },
25
+ grow: {
26
+ flexGrow: 1,
27
+ },
28
+ menuButton: {
29
+ marginLeft: -12,
30
+ marginRight: 20,
31
+ },
32
+ title: {
33
+ display: 'none',
34
+ [theme.breakpoints.up('sm')]: {
35
+ display: 'block',
36
+ },
37
+ },
38
+ search: {
39
+ position: 'relative',
40
+ borderRadius: theme.shape.borderRadius,
41
+ backgroundColor: fade(theme.palette.common.white, 0.15),
42
+ '&:hover': {
43
+ backgroundColor: fade(theme.palette.common.white, 0.25),
44
+ },
45
+ marginRight: theme.spacing.unit * 2,
46
+ marginLeft: 0,
47
+ width: '100%',
48
+ [theme.breakpoints.up('sm')]: {
49
+ marginLeft: theme.spacing.unit * 3,
50
+ width: 'auto',
51
+ },
52
+ },
53
+ searchIcon: {
54
+ width: theme.spacing.unit * 9,
55
+ height: '100%',
56
+ position: 'absolute',
57
+ pointerEvents: 'none',
58
+ display: 'flex',
59
+ alignItems: 'center',
60
+ justifyContent: 'center',
61
+ },
62
+ inputRoot: {
63
+ color: 'inherit',
64
+ width: '100%',
65
+ },
66
+ inputInput: {
67
+ paddingTop: theme.spacing.unit,
68
+ paddingRight: theme.spacing.unit,
69
+ paddingBottom: theme.spacing.unit,
70
+ paddingLeft: theme.spacing.unit * 10,
71
+ transition: theme.transitions.create('width'),
72
+ width: '100%',
73
+ [theme.breakpoints.up('md')]: {
74
+ width: 200,
75
+ },
76
+ },
77
+ sectionDesktop: {
78
+ display: 'none',
79
+ [theme.breakpoints.up('md')]: {
80
+ display: 'flex',
81
+ },
82
+ },
83
+ sectionMobile: {
84
+ display: 'flex',
85
+ [theme.breakpoints.up('md')]: {
86
+ display: 'none',
87
+ },
88
+ },
89
+ });
90
+
91
+ class PrimarySearchAppBar extends React.Component {
92
+ state = {
93
+ anchorEl: null,
94
+ mobileMoreAnchorEl: null,
95
+ };
96
+
97
+ handleProfileMenuOpen = event => {
98
+ this.setState({ anchorEl: event.currentTarget });
99
+ };
100
+
101
+ handleMenuClose = () => {
102
+ this.setState({ anchorEl: null });
103
+ this.handleMobileMenuClose();
104
+ };
105
+
106
+ handleMobileMenuOpen = event => {
107
+ this.setState({ mobileMoreAnchorEl: event.currentTarget });
108
+ };
109
+
110
+ handleMobileMenuClose = () => {
111
+ this.setState({ mobileMoreAnchorEl: null });
112
+ };
113
+
114
+ render() {
115
+ const { anchorEl, mobileMoreAnchorEl } = this.state;
116
+ const { classes } = this.props;
117
+ const isMenuOpen = Boolean(anchorEl);
118
+ const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
119
+
120
+ const renderMenu = (
121
+ <Menu
122
+ anchorEl={anchorEl}
123
+ anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
124
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
125
+ open={isMenuOpen}
126
+ onClose={this.handleMenuClose}
127
+ >
128
+ <MenuItem onClick={this.handleMenuClose}>Profile</MenuItem>
129
+ <MenuItem onClick={this.handleMenuClose}>My account</MenuItem>
130
+ </Menu>
131
+ );
132
+
133
+ const renderMobileMenu = (
134
+ <Menu
135
+ anchorEl={mobileMoreAnchorEl}
136
+ anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
137
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
138
+ open={isMobileMenuOpen}
139
+ onClose={this.handleMenuClose}
140
+ >
141
+ <MenuItem onClick={this.handleMobileMenuClose}>
142
+ <IconButton color="inherit">
143
+ <MailIcon />
144
+ </IconButton>
145
+ <p>Messages</p>
146
+ </MenuItem>
147
+ <MenuItem onClick={this.handleMobileMenuClose}>
148
+ <IconButton color="inherit">
149
+ <NotificationsIcon />
150
+ </IconButton>
151
+ <p>Notifications</p>
152
+ </MenuItem>
153
+ <MenuItem onClick={this.handleProfileMenuOpen}>
154
+ <IconButton color="inherit">
155
+ <AccountCircle />
156
+ </IconButton>
157
+ <p>Profile</p>
158
+ </MenuItem>
159
+ </Menu>
160
+ );
161
+
162
+ return (
163
+ <div className={classes.root}>
164
+ <AppBar position="fixed">
165
+ <Toolbar>
166
+ <IconButton className={classes.menuButton} color="inherit" aria-label="Open drawer">
167
+ <MenuIcon />
168
+ </IconButton>
169
+ <Typography className={classes.title} variant="h6" color="inherit" noWrap>
170
+ Reativo
171
+ </Typography>
172
+ <div className={classes.search}>
173
+ <div className={classes.searchIcon}>
174
+ <SearchIcon />
175
+ </div>
176
+ <InputBase
177
+ placeholder="Search…"
178
+ classes={{
179
+ root: classes.inputRoot,
180
+ input: classes.inputInput,
181
+ }}
182
+ />
183
+ </div>
184
+ <div className={classes.grow} />
185
+ <div className={classes.sectionDesktop}>
186
+ <IconButton color="inherit">
187
+ <MailIcon />
188
+ </IconButton>
189
+ <IconButton color="inherit">
190
+ <NotificationsIcon />
191
+ </IconButton>
192
+ <IconButton
193
+ aria-owns={isMenuOpen ? 'material-appbar' : undefined}
194
+ aria-haspopup="true"
195
+ onClick={this.handleProfileMenuOpen}
196
+ color="inherit"
197
+ >
198
+ <AccountCircle />
199
+ </IconButton>
200
+ </div>
201
+ <div className={classes.sectionMobile}>
202
+ <IconButton aria-haspopup="true" onClick={this.handleMobileMenuOpen} color="inherit">
203
+ <MoreIcon />
204
+ </IconButton>
205
+ </div>
206
+ </Toolbar>
207
+ </AppBar>
208
+ {renderMenu}
209
+ {renderMobileMenu}
210
+ </div>
211
+ );
212
+ }
213
+ }
214
+
215
+ PrimarySearchAppBar.propTypes = {
216
+ classes: PropTypes.object.isRequired,
217
+ };
218
+
219
+ export default hot(
220
+ withStyles(styles)(
221
+ wrapper(PrimarySearchAppBar)
222
+ )
223
+ );