breezy 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|