breezy 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/breezy.rb +11 -6
- data/lib/breezy/render.rb +8 -1
- data/lib/breezy/version.rb +1 -1
- data/lib/breezy/xhr_headers.rb +9 -22
- data/lib/generators/breezy/view/templates/{view.jsx → view.jsx.mobile} +0 -0
- data/lib/generators/breezy/view/templates/{view.js → view.jsx.web} +0 -0
- data/lib/generators/breezy/view/view_generator.rb +3 -3
- data/lib/generators/rails/breezy_generator.rb +103 -0
- data/lib/generators/rails/scaffold_controller_generator.rb +13 -0
- data/lib/generators/rails/templates/controller.rb.tt +85 -0
- data/lib/generators/rails/templates/edit.js.props +18 -0
- data/lib/generators/rails/templates/index.js.props +20 -0
- data/lib/generators/rails/templates/mobile/edit.jsx +76 -0
- data/lib/generators/rails/templates/mobile/elements.js +88 -0
- data/lib/generators/rails/templates/mobile/form.jsx +34 -0
- data/lib/generators/rails/templates/mobile/index.jsx +103 -0
- data/lib/generators/rails/templates/mobile/new.jsx +75 -0
- data/lib/generators/rails/templates/mobile/show.jsx +32 -0
- data/lib/generators/rails/templates/new.js.props +7 -0
- data/lib/generators/rails/templates/show.js.props +12 -0
- data/lib/generators/rails/templates/web/base.jsx +68 -0
- data/lib/generators/rails/templates/web/edit.jsx +34 -0
- data/lib/generators/rails/templates/web/form.jsx +37 -0
- data/lib/generators/rails/templates/web/index.jsx +56 -0
- data/lib/generators/rails/templates/web/new.jsx +31 -0
- data/lib/generators/rails/templates/web/show.jsx +28 -0
- data/lib/install/templates/mobile/app.js +31 -38
- data/lib/install/templates/mobile/package.json +5 -2
- data/lib/install/templates/web/application.js +7 -3
- data/lib/install/web.rb +2 -4
- metadata +23 -6
- data/lib/assets/javascript/breezy.js +0 -6067
- data/lib/breezy/xhr_redirect.rb +0 -13
@@ -0,0 +1,88 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {View, Text, ScrollView, TouchableOpacity, Alert} from 'react-native'
|
3
|
+
import {Header, Card, ListItem, Icon, Button} from 'react-native-elements'
|
4
|
+
|
5
|
+
export function Container(props) {
|
6
|
+
return <ScrollView contentContainerStyle={{paddingBottom: 100, flexDirection: 'column'}}>
|
7
|
+
{props.children}
|
8
|
+
</ScrollView>
|
9
|
+
}
|
10
|
+
|
11
|
+
export function BarSaveAndGoBack({onPress}) {
|
12
|
+
return (
|
13
|
+
<TouchableOpacity onPress={onPress} style={{flex: 1, flexDirection: 'row', alignItems: 'flex-end'}}>
|
14
|
+
<Icon
|
15
|
+
name='chevron-left'
|
16
|
+
color='#fff'
|
17
|
+
size={18}
|
18
|
+
underlayColor='rgba(0, 0, 0, 0)'
|
19
|
+
/>
|
20
|
+
<Text style={{color: '#fff'}}>SAVE AND GO BACK</Text>
|
21
|
+
</TouchableOpacity>
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
export function BarGoBack({onPress}) {
|
26
|
+
return (
|
27
|
+
<TouchableOpacity onPress={onPress} style={{flex: 1, flexDirection: 'row', alignItems: 'flex-end'}}>
|
28
|
+
<Icon
|
29
|
+
name='chevron-left'
|
30
|
+
color='#fff'
|
31
|
+
size={18}
|
32
|
+
underlayColor='rgba(0, 0, 0, 0)'
|
33
|
+
/>
|
34
|
+
<Text style={{color: '#fff'}}>BACK</Text>
|
35
|
+
</TouchableOpacity>
|
36
|
+
)
|
37
|
+
}
|
38
|
+
|
39
|
+
export function BarCancel({onPress}) {
|
40
|
+
return (
|
41
|
+
<TouchableOpacity onPress={onPress} style={{flex: 1, flexDirection: 'row', alignItems: 'flex-end'}}>
|
42
|
+
<Text style={{color: '#fff'}}>CANCEL</Text>
|
43
|
+
</TouchableOpacity>
|
44
|
+
)
|
45
|
+
}
|
46
|
+
|
47
|
+
export function BarNew ({onPress}) {
|
48
|
+
return (
|
49
|
+
<TouchableOpacity
|
50
|
+
onPress={onPress}
|
51
|
+
style={{flex: 1, flexDirection: 'row', alignItems: 'flex-end'}}>
|
52
|
+
<Icon
|
53
|
+
name='note-add'
|
54
|
+
color='#fff'
|
55
|
+
size={18}
|
56
|
+
underlayColor='rgba(0, 0, 0, 0)'
|
57
|
+
/>
|
58
|
+
<Text style={{color: '#fff'}}>NEW</Text>
|
59
|
+
</TouchableOpacity>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
export function IconDelete({onPress}) {
|
64
|
+
return <Icon
|
65
|
+
raised
|
66
|
+
name='close'
|
67
|
+
color='#f50'
|
68
|
+
size={13}
|
69
|
+
onPress={onPress}/>
|
70
|
+
}
|
71
|
+
export function IconEdit({onPress}) {
|
72
|
+
return <Icon
|
73
|
+
raised
|
74
|
+
name='mode-edit'
|
75
|
+
color='#f50'
|
76
|
+
size={13}
|
77
|
+
onPress={onPress}/>
|
78
|
+
|
79
|
+
}
|
80
|
+
export function IconShow({onPress}) {
|
81
|
+
return <Icon
|
82
|
+
raised
|
83
|
+
name='more-horiz'
|
84
|
+
color='#f50'
|
85
|
+
size={13}
|
86
|
+
onPress={onPress}/>
|
87
|
+
}
|
88
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { Field, reduxForm, stopSubmit, touch} from 'redux-form'
|
3
|
+
import {View, Text} from 'react-native'
|
4
|
+
import { FormLabel, FormInput, FormValidationMessage } from 'react-native-elements'
|
5
|
+
|
6
|
+
const RenderField = ({ input, label, type, meta: { touched, error } }) => {
|
7
|
+
return (
|
8
|
+
<View>
|
9
|
+
<FormLabel><Text>{label}</Text></FormLabel>
|
10
|
+
<FormInput onChangeText={input.onChange} value={input.value} placeholder={`Please enter ${label}`}/>
|
11
|
+
{touched && error && <FormValidationMessage><Text>{error}</Text></FormValidationMessage>}
|
12
|
+
</View>
|
13
|
+
)
|
14
|
+
}
|
15
|
+
const SimpleForm = props => {
|
16
|
+
const { error, handleSubmit, initialValue } = props
|
17
|
+
|
18
|
+
return (
|
19
|
+
<View><% attributes_list.select{|attr| attr != :id }.each do |attr| %>
|
20
|
+
<Field
|
21
|
+
name="<%=attr.to_s.camelize(:lower)%>"
|
22
|
+
label="<%=attr.to_s.humanize%>"
|
23
|
+
component={RenderField}
|
24
|
+
type="text"
|
25
|
+
/>
|
26
|
+
<%end%></View>
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
export default reduxForm({
|
31
|
+
form: '<%=plural_table_name%>_form' // a unique identifier for this form
|
32
|
+
})(SimpleForm)
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {mapDispatchToProps, mapStateToProps} from '@jho406/breezy'
|
3
|
+
import {connect} from 'react-redux'
|
4
|
+
import {View, Text, TouchableOpacity, Alert} from 'react-native'
|
5
|
+
import {Header, Card} from 'react-native-elements'
|
6
|
+
import DropdownAlert from 'react-native-dropdownalert';
|
7
|
+
import {Container, IconDelete, IconEdit, IconShow, BarNew} from 'components/elements'
|
8
|
+
|
9
|
+
class <%= plural_table_name.camelize %>Index extends React.Component {
|
10
|
+
static defaultProps = {
|
11
|
+
<%= plural_table_name %>: []
|
12
|
+
}
|
13
|
+
|
14
|
+
constructor (props) {
|
15
|
+
super(props)
|
16
|
+
this.handleClick = this.handleClick.bind(this)
|
17
|
+
this.handleDeleteClick = this.handleDeleteClick.bind(this)
|
18
|
+
}
|
19
|
+
|
20
|
+
handleDeleteClick(path) {
|
21
|
+
const method = 'DELETE'
|
22
|
+
|
23
|
+
Alert.alert(
|
24
|
+
'Wait!',
|
25
|
+
'Are you sure you want to delete this <%=singular_table_name%>?',
|
26
|
+
[
|
27
|
+
{
|
28
|
+
text: 'Delete',
|
29
|
+
onPress: e => {this.props.visit(path, {method})},
|
30
|
+
style: 'destructive'
|
31
|
+
},
|
32
|
+
{ text: 'Cancel', style: 'cancel' },
|
33
|
+
],
|
34
|
+
{ cancelable: false }
|
35
|
+
)
|
36
|
+
}
|
37
|
+
|
38
|
+
handleClick(path, method='GET') {
|
39
|
+
const nav = this.props.navigation
|
40
|
+
this.props.visit(path, {method}).then((rsp)=>{
|
41
|
+
if (rsp.canNavigate) {
|
42
|
+
return nav.navigate(rsp.screen, {pathQuery: rsp.pathQuery})
|
43
|
+
} else {
|
44
|
+
// There can only be one visit at a time, if `canNavigate`
|
45
|
+
// is false, then this request is being ignored for a more
|
46
|
+
// recent visit. Do Nothing.
|
47
|
+
return
|
48
|
+
}
|
49
|
+
})
|
50
|
+
}
|
51
|
+
|
52
|
+
showNotice (message) {
|
53
|
+
if (message) {
|
54
|
+
setImmediate(() => {
|
55
|
+
this.dropdown.alertWithType('success', 'Success', message);
|
56
|
+
})
|
57
|
+
|
58
|
+
return <DropdownAlert ref={ref => this.dropdown = ref} translucent={true}/>
|
59
|
+
} else {
|
60
|
+
return null
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
render () {
|
65
|
+
const headerOptions = {
|
66
|
+
centerComponent: { text: 'POSTS', style: { color: '#fff' } },
|
67
|
+
rightComponent: (<BarNew
|
68
|
+
onPress={e => this.handleClick('/<%=plural_table_name%>/new')}
|
69
|
+
/>)
|
70
|
+
}
|
71
|
+
|
72
|
+
return (
|
73
|
+
<View>
|
74
|
+
<Header {...headerOptions}/>
|
75
|
+
<Container>
|
76
|
+
{this.props.<%=plural_table_name%>.map((<%=singular_table_name%>) => {
|
77
|
+
const {show_path, edit_path, delete_path} = <%=singular_table_name%>.meta
|
78
|
+
return (
|
79
|
+
<Card key={<%=singular_table_name%>.id}>
|
80
|
+
<TouchableOpacity>
|
81
|
+
<% attributes_list.select{|attr| attr != :id }.each do |attr| %><Text style={{marginBottom: 10}}>{<%=singular_table_name%>.<%=attr%>}</Text>
|
82
|
+
<% end %>
|
83
|
+
</TouchableOpacity>
|
84
|
+
<View style={{flex: 1, justifyContent: 'flex-end', flexDirection: 'row'}}>
|
85
|
+
<IconDelete onPress={e => this.handleDeleteClick(delete_path)}/>
|
86
|
+
<IconEdit onPress={e => this.handleClick(edit_path)}/>
|
87
|
+
<IconShow onPress={e => this.handleClick(show_path)}/>
|
88
|
+
</View>
|
89
|
+
</Card>
|
90
|
+
)
|
91
|
+
})}
|
92
|
+
</Container>
|
93
|
+
{this.showNotice(this.props.notice)}
|
94
|
+
</View>
|
95
|
+
)
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
export default connect(
|
100
|
+
mapStateToProps,
|
101
|
+
mapDispatchToProps
|
102
|
+
)(<%= plural_table_name.camelize %>Index)
|
103
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {submit} from 'redux-form'
|
3
|
+
import {Header} from 'react-native-elements'
|
4
|
+
import {SubmissionError} from 'redux-form'
|
5
|
+
import {View} from 'react-native'
|
6
|
+
import {connect} from 'react-redux'
|
7
|
+
import {delInPage} from '@jho406/breezy/dist/action_creators'
|
8
|
+
import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
|
9
|
+
import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
|
10
|
+
import {Container, BarSaveAndGoBack, BarCancel} from 'components/elements'
|
11
|
+
|
12
|
+
class <%= plural_table_name.camelize %>New extends React.Component {
|
13
|
+
constructor (props) {
|
14
|
+
super(props)
|
15
|
+
this.submit = this.submit.bind(this)
|
16
|
+
this.remoteSubmit = this.remoteSubmit.bind(this)
|
17
|
+
}
|
18
|
+
|
19
|
+
submit (body) {
|
20
|
+
const options = {
|
21
|
+
method: 'POST',
|
22
|
+
body: JSON.stringify(body),
|
23
|
+
contentType: 'application/json'
|
24
|
+
}
|
25
|
+
|
26
|
+
this.props.delInPage({pathQuery: this.props.pathQuery, keypath: 'errors'})
|
27
|
+
return this.props.visit('/<%= plural_table_name %>', options).then((rsp) => {
|
28
|
+
if (this.props.errors) {
|
29
|
+
throw new SubmissionError({
|
30
|
+
...this.props.errors
|
31
|
+
})
|
32
|
+
}
|
33
|
+
if (rsp.canNavigate) {
|
34
|
+
return this.props.navigation.goBack()
|
35
|
+
} else {
|
36
|
+
// There can only ve one visit at a time, if `canNavigate`
|
37
|
+
// is false, then this request is being ignored for a more
|
38
|
+
// recent visit. Do Nothing.
|
39
|
+
return
|
40
|
+
}
|
41
|
+
}).catch((err) => {
|
42
|
+
if (err instanceof SubmissionError) {
|
43
|
+
throw err
|
44
|
+
} else {
|
45
|
+
//handle other errors here
|
46
|
+
}
|
47
|
+
})
|
48
|
+
}
|
49
|
+
|
50
|
+
remoteSubmit () {
|
51
|
+
this.props.remoteSubmit('<%=plural_table_name%>_form')
|
52
|
+
}
|
53
|
+
|
54
|
+
render () {
|
55
|
+
const headerOptions = {
|
56
|
+
leftComponent: (<BarSaveAndGoBack onPress={this.remoteSubmit} />),
|
57
|
+
rightComponent: (<BarCancel onPress={e => this.props.navigation.goBack()} />)
|
58
|
+
}
|
59
|
+
|
60
|
+
return (
|
61
|
+
<View>
|
62
|
+
<Header {...headerOptions}/>
|
63
|
+
<Container>
|
64
|
+
<<%= plural_table_name.camelize %>Form error={this.props.error} onSubmit={this.submit}/>
|
65
|
+
</Container>
|
66
|
+
</View>
|
67
|
+
)
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
export default connect(
|
72
|
+
mapStateToProps,
|
73
|
+
{...mapDispatchToProps, remoteSubmit: submit, delInPage}
|
74
|
+
)(<%= plural_table_name.camelize %>New)
|
75
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
|
3
|
+
import {connect} from 'react-redux'
|
4
|
+
import {Text, View} from 'react-native'
|
5
|
+
import {Header, Card} from 'react-native-elements'
|
6
|
+
import {Container, BarGoBack} from 'components/elements'
|
7
|
+
|
8
|
+
class <%= plural_table_name.camelize %>Show extends React.Component {
|
9
|
+
render() {
|
10
|
+
const headerOptions = {
|
11
|
+
leftComponent: (<BarGoBack onPress={ e => this.props.navigation.goBack()} />)
|
12
|
+
}
|
13
|
+
|
14
|
+
return (
|
15
|
+
<View>
|
16
|
+
<Header {...headerOptions}/>
|
17
|
+
<Container>
|
18
|
+
<Card><% attributes_list_with_timestamps.select{|attr| attr != :id }.each do |attr| %>
|
19
|
+
<Text style={{fontWeight: 'bold'}}><%=attr%></Text>
|
20
|
+
<Text>{this.props.<%=attr%>}</Text><% end %>
|
21
|
+
</Card>
|
22
|
+
</Container>
|
23
|
+
</View>
|
24
|
+
)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
export default connect(
|
29
|
+
mapStateToProps,
|
30
|
+
mapDispatchToProps
|
31
|
+
)(<%= plural_table_name.camelize %>Show)
|
32
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%- attributes_list_with_timestamps.each do |attr|-%>
|
2
|
+
json.<%=attr%> @<%= singular_table_name %>.<%=attr%>
|
3
|
+
<%- end -%>
|
4
|
+
|
5
|
+
if notice
|
6
|
+
json.notice notice
|
7
|
+
end
|
8
|
+
|
9
|
+
json.meta do
|
10
|
+
json.index_path <%= plural_table_name %>_path
|
11
|
+
json.edit_path edit_<%= singular_table_name %>_path(@<%= singular_table_name %>)
|
12
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {SubmissionError} from 'redux-form'
|
3
|
+
|
4
|
+
export default class extends React.Component {
|
5
|
+
constructor (props) {
|
6
|
+
super(props)
|
7
|
+
this.handleSubmit = this.handleSubmit.bind(this)
|
8
|
+
this.handleClick = this.handleClick.bind(this)
|
9
|
+
}
|
10
|
+
|
11
|
+
handleClick(path, method='GET') {
|
12
|
+
this.props.visit(path, {method}).then((rsp)=>{
|
13
|
+
// the window needs a full reload when asset fingerprint changes
|
14
|
+
if (rsp.needsRefresh) {
|
15
|
+
return window.location = rsp.url
|
16
|
+
}
|
17
|
+
|
18
|
+
if (rsp.canNavigate) {
|
19
|
+
return this.props.navigateTo(rsp.screen, rsp.pathQuery)
|
20
|
+
} else {
|
21
|
+
// There can only be one visit at a time, if `canNavigate`
|
22
|
+
// is false, then this request is being ignored for a more
|
23
|
+
// recent visit. Do Nothing.
|
24
|
+
return
|
25
|
+
}
|
26
|
+
})
|
27
|
+
}
|
28
|
+
|
29
|
+
handleSubmit (url, body, method='POST') {
|
30
|
+
const options = {
|
31
|
+
method,
|
32
|
+
body: JSON.stringify(body),
|
33
|
+
contentType: 'application/json'
|
34
|
+
}
|
35
|
+
|
36
|
+
return this.props.visit(url, options).then((rsp) => {
|
37
|
+
// the window needs a full reload when asset fingerprint changes
|
38
|
+
if (rsp.needsRefresh) {
|
39
|
+
return window.location = rsp.url
|
40
|
+
}
|
41
|
+
|
42
|
+
if (this.props.errors) {
|
43
|
+
throw new SubmissionError({
|
44
|
+
...this.props.errors
|
45
|
+
})
|
46
|
+
}
|
47
|
+
|
48
|
+
if (rsp.canNavigate) {
|
49
|
+
//Uncomment this if you want full-page reloads
|
50
|
+
// window.location = rsp.url
|
51
|
+
|
52
|
+
return this.props.navigateTo(rsp.screen, rsp.pathQuery)
|
53
|
+
} else {
|
54
|
+
// There can only ve one visit at a time, if `canNavigate`
|
55
|
+
// is false, then this request is being ignored for a more
|
56
|
+
// recent visit. Do Nothing.
|
57
|
+
return
|
58
|
+
}
|
59
|
+
}).catch((err) => {
|
60
|
+
if (err.name === 'SubmissionError') {
|
61
|
+
throw err
|
62
|
+
} else {
|
63
|
+
window.location = err.url
|
64
|
+
}
|
65
|
+
})
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
|
3
|
+
import {delInPage} from '@jho406/breezy/dist/action_creators'
|
4
|
+
import {connect} from 'react-redux'
|
5
|
+
import BaseScreen from 'components/BaseScreen'
|
6
|
+
import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
|
7
|
+
|
8
|
+
class <%= plural_table_name.camelize %>Edit extends BaseScreen {
|
9
|
+
handleSubmit (body) {
|
10
|
+
this.props.delInPage({pathQuery: this.props.pathQuery, keypath: 'errors'})
|
11
|
+
return super.handleSubmit('/<%= plural_table_name %>/' + this.props.id, body, 'PATCH')
|
12
|
+
}
|
13
|
+
|
14
|
+
render () {
|
15
|
+
return (
|
16
|
+
<div>
|
17
|
+
<<%= plural_table_name.camelize %>Form
|
18
|
+
error={this.props.error}
|
19
|
+
onSubmit={this.handleSubmit}
|
20
|
+
initialValues={this.props.attributes_for_form}
|
21
|
+
/>
|
22
|
+
<a onClick={ e => this.handleClick(this.props.meta.show_path)}>Show</a>
|
23
|
+
<a onClick={ e => this.handleClick(this.props.meta.index_path)}>Back</a>
|
24
|
+
</div>
|
25
|
+
)
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
export default connect(
|
30
|
+
mapStateToProps,
|
31
|
+
{...mapDispatchToProps, delInPage}
|
32
|
+
)(<%= plural_table_name.camelize %>Edit)
|
33
|
+
|
34
|
+
|