superglue 0.40.0
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 +7 -0
- data/lib/generators/rails/scaffold_controller_generator.rb +12 -0
- data/lib/generators/rails/superglue_generator.rb +92 -0
- data/lib/generators/rails/templates/_form.json.props +13 -0
- data/lib/generators/rails/templates/controller.rb.tt +82 -0
- data/lib/generators/rails/templates/edit.json.props +14 -0
- data/lib/generators/rails/templates/index.json.props +13 -0
- data/lib/generators/rails/templates/new.json.props +15 -0
- data/lib/generators/rails/templates/show.json.props +7 -0
- data/lib/generators/rails/templates/web/edit.html.erb +12 -0
- data/lib/generators/rails/templates/web/edit.js +31 -0
- data/lib/generators/rails/templates/web/index.html.erb +7 -0
- data/lib/generators/rails/templates/web/index.js +49 -0
- data/lib/generators/rails/templates/web/new.html.erb +7 -0
- data/lib/generators/rails/templates/web/new.js +29 -0
- data/lib/generators/rails/templates/web/show.html.erb +7 -0
- data/lib/generators/rails/templates/web/show.js +28 -0
- data/lib/install/templates/web/action_creators.js +14 -0
- data/lib/install/templates/web/actions.js +4 -0
- data/lib/install/templates/web/application.js +114 -0
- data/lib/install/templates/web/application.json.props +27 -0
- data/lib/install/templates/web/application_visit.js +65 -0
- data/lib/install/templates/web/initializer.rb +1 -0
- data/lib/install/templates/web/reducer.js +72 -0
- data/lib/install/web.rb +66 -0
- data/lib/superglue/helpers.rb +552 -0
- data/lib/superglue/redirection.rb +30 -0
- data/lib/superglue.rb +38 -0
- data/lib/tasks/install.rake +42 -0
- data/test/engine_test.rb +7 -0
- data/test/helpers_test.rb +23 -0
- data/test/render_test.rb +81 -0
- data/test/test_helper.rb +20 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a72d44f915a1102e37b882a5492a0bae8427c68715acf5a59a809fc70cd47aba
|
4
|
+
data.tar.gz: 00cb36e39bbc5d47001c3c6ad9a588204e5ccf27deabbce41ddae66a1ba83ac5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dfa4b3fc28f855eca34ff1533050afd0abf800d7106ffdac25ee44e051e530546990138cae9b33b78526264a9235692a0f878011ea46da84baef8a4278f49280
|
7
|
+
data.tar.gz: a2198132a6bf2d004745ddb1be7b49de05a21571dd55b507c0d8f09928b06771ebcefb25271b9d84ff9f88303e3f359fea9f6c197ff40db907eb3bf65e75620f
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
|
3
|
+
|
4
|
+
module Rails
|
5
|
+
module Generators
|
6
|
+
class ScaffoldControllerGenerator
|
7
|
+
source_paths << File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
hook_for :superglue, type: :boolean, default: true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
require 'rails/generators/resource_helpers'
|
3
|
+
|
4
|
+
module Rails
|
5
|
+
module Generators
|
6
|
+
class SuperglueGenerator < NamedBase # :nodoc:
|
7
|
+
include Rails::Generators::ResourceHelpers
|
8
|
+
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
|
11
|
+
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
|
12
|
+
|
13
|
+
def create_root_folder
|
14
|
+
path = File.join('app/views', controller_file_path)
|
15
|
+
empty_directory path unless File.directory?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def copy_view_files
|
19
|
+
%w(index show new edit).each do |view|
|
20
|
+
@action_name = view
|
21
|
+
filename = filename_with_extensions(view)
|
22
|
+
template filename, File.join('app/views', controller_file_path, filename)
|
23
|
+
end
|
24
|
+
template '_form.json.props', File.join('app/views', controller_file_path, '_form.json.props')
|
25
|
+
|
26
|
+
%w(index show new edit).each do |view|
|
27
|
+
@action_name = view
|
28
|
+
filename = filename_with_js_extensions(view)
|
29
|
+
template 'web/' + filename, File.join('app/views', controller_file_path, filename)
|
30
|
+
end
|
31
|
+
|
32
|
+
%w(index show new edit).each do |view|
|
33
|
+
@action_name = view
|
34
|
+
filename = filename_with_html_extensions(view)
|
35
|
+
template 'web/' + filename, File.join('app/views', controller_file_path, filename)
|
36
|
+
end
|
37
|
+
|
38
|
+
%w(index show new edit).each do |view|
|
39
|
+
append_mapping(view)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
protected
|
45
|
+
def append_mapping(action)
|
46
|
+
app_js = 'app/javascript/packs/application.js'
|
47
|
+
|
48
|
+
component_name = [plural_table_name, action].map(&:camelcase).join
|
49
|
+
|
50
|
+
inject_into_file app_js, after: "from '@thoughtbot/superglue'" do
|
51
|
+
"\nimport #{component_name} from 'views/#{controller_file_path}/#{action}'"
|
52
|
+
end
|
53
|
+
|
54
|
+
inject_into_file app_js, after: 'identifierToComponentMapping = {' do
|
55
|
+
"\n '#{[controller_file_path, action].join('/')}': #{component_name},"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def action_name
|
60
|
+
@action_name
|
61
|
+
end
|
62
|
+
|
63
|
+
def attributes_names
|
64
|
+
[:id] + super
|
65
|
+
end
|
66
|
+
|
67
|
+
def filename_with_extensions(name)
|
68
|
+
[name, :json, :props] * '.'
|
69
|
+
end
|
70
|
+
|
71
|
+
def filename_with_js_extensions(name)
|
72
|
+
[name, :js] * '.'
|
73
|
+
end
|
74
|
+
|
75
|
+
def filename_with_html_extensions(name)
|
76
|
+
[name, :html, :erb] * '.'
|
77
|
+
end
|
78
|
+
|
79
|
+
def attributes_list_with_timestamps
|
80
|
+
attributes_list(attributes_names + %w(created_at updated_at))
|
81
|
+
end
|
82
|
+
|
83
|
+
def attributes_list(attributes = attributes_names)
|
84
|
+
if self.attributes.any? {|attr| attr.name == 'password' && attr.type == :digest}
|
85
|
+
attributes = attributes.reject {|name| %w(password password_confirmation).include? name}
|
86
|
+
end
|
87
|
+
|
88
|
+
attributes
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
html = form_with(model: @<%= model_resource_name %>, local: true) do |form|
|
2
|
+
inner = "".html_safe
|
3
|
+
|
4
|
+
<%- attributes.each do |attr| -%>
|
5
|
+
inner << form.label(:<%= attr.column_name %>)
|
6
|
+
inner << form.<%= attr.field_type %>(:<%= attr.column_name %>)
|
7
|
+
<%- end -%>
|
8
|
+
inner << form.submit
|
9
|
+
|
10
|
+
inner
|
11
|
+
end
|
12
|
+
|
13
|
+
json.html html
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<% if namespaced? -%>
|
2
|
+
require_dependency "<%= namespaced_path %>/application_controller"
|
3
|
+
|
4
|
+
<% end -%>
|
5
|
+
<% module_namespacing do -%>
|
6
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
7
|
+
before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
|
8
|
+
|
9
|
+
# GET <%= route_url %>
|
10
|
+
def index
|
11
|
+
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
|
12
|
+
end
|
13
|
+
|
14
|
+
# GET <%= route_url %>/1
|
15
|
+
def show
|
16
|
+
end
|
17
|
+
|
18
|
+
# GET <%= route_url %>/new
|
19
|
+
def new
|
20
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
|
21
|
+
end
|
22
|
+
|
23
|
+
# GET <%= route_url %>/1/edit
|
24
|
+
def edit
|
25
|
+
end
|
26
|
+
|
27
|
+
# POST <%= route_url %>
|
28
|
+
def create
|
29
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
|
30
|
+
|
31
|
+
if @<%= orm_instance.save %>
|
32
|
+
<%- if options[:platform] == 'mobile' -%>
|
33
|
+
redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully created.'" %>
|
34
|
+
<%- else -%>
|
35
|
+
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
|
36
|
+
<%- end -%>
|
37
|
+
else
|
38
|
+
response.set_header("content-location", new_<%= singular_table_name %>_path)
|
39
|
+
render :new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# PATCH/PUT <%= route_url %>/1
|
44
|
+
def update
|
45
|
+
if @<%= orm_instance.update("#{singular_table_name}_params") %>
|
46
|
+
<%- if options[:platform] == 'mobile' -%>
|
47
|
+
redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully updated.'" %>
|
48
|
+
<%- else -%>
|
49
|
+
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
|
50
|
+
<%- end -%>
|
51
|
+
else
|
52
|
+
response.set_header("content-location", edit_<%= singular_table_name %>_path(@<%= singular_table_name %>))
|
53
|
+
render :edit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# DELETE <%= route_url %>/1
|
58
|
+
def destroy
|
59
|
+
@<%= orm_instance.destroy %>
|
60
|
+
<%- if options[:platform] == 'mobile' -%>
|
61
|
+
redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %>
|
62
|
+
<%- else -%>
|
63
|
+
redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %>
|
64
|
+
<%- end -%>
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
# Use callbacks to share common setup or constraints between actions.
|
69
|
+
def set_<%= singular_table_name %>
|
70
|
+
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
|
71
|
+
end
|
72
|
+
|
73
|
+
# Only allow a trusted parameter "white list" through.
|
74
|
+
def <%= "#{singular_table_name}_params" %>
|
75
|
+
<%- if attributes_names.empty? -%>
|
76
|
+
params.fetch(:<%= singular_table_name %>, {})
|
77
|
+
<%- else -%>
|
78
|
+
params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
|
79
|
+
<%- end -%>
|
80
|
+
end
|
81
|
+
end
|
82
|
+
<% end -%>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
if @post.errors.any?
|
2
|
+
content = {
|
3
|
+
explanation: "#{pluralize(@<%= singular_table_name %>.errors.count, "error")} prohibited this post from being saved:",
|
4
|
+
messages: @<%= singular_table_name %>.errors.full_messages.map{|msg| {body: msg}}
|
5
|
+
}
|
6
|
+
|
7
|
+
flash.now[:form_error] = content
|
8
|
+
end
|
9
|
+
|
10
|
+
json.form(partial: 'form') do
|
11
|
+
end
|
12
|
+
|
13
|
+
json.<%= singular_table_name%>_path <%= singular_table_name%>_path(@<%=singular_table_name%>)
|
14
|
+
json.<%= plural_table_name %>_path <%= plural_table_name %>_path
|
@@ -0,0 +1,13 @@
|
|
1
|
+
json.<%= plural_table_name %> do
|
2
|
+
json.array! @<%= plural_table_name %> do |<%= singular_table_name %>|
|
3
|
+
<%- attributes_list_with_timestamps.each do |attr| -%>
|
4
|
+
json.<%=attr%> <%= singular_table_name %>.<%=attr%>
|
5
|
+
<%- end -%>
|
6
|
+
json.edit_<%=singular_table_name%>_path edit_<%=singular_table_name%>_path(<%=singular_table_name%>)
|
7
|
+
json.<%=singular_table_name%>_path <%=singular_table_name%>_path(<%=singular_table_name%>)
|
8
|
+
json.delete_<%=singular_table_name%>_path <%=singular_table_name%>_path(<%=singular_table_name%>)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
json.new_<%= singular_table_name %>_path new_<%= singular_table_name %>_path
|
@@ -0,0 +1,15 @@
|
|
1
|
+
if @post.errors.any?
|
2
|
+
content = {
|
3
|
+
explanation: "#{pluralize(@<%= singular_table_name %>.errors.count, "error")} prohibited this post from being saved:",
|
4
|
+
messages: @<%= singular_table_name %>.errors.full_messages.map{|msg| {body: msg}}
|
5
|
+
}
|
6
|
+
|
7
|
+
flash.now[:form_error] = content
|
8
|
+
end
|
9
|
+
|
10
|
+
json.form(partial: 'form') do
|
11
|
+
end
|
12
|
+
|
13
|
+
json.<%= plural_table_name %>_path <%= plural_table_name %>_path
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%- attributes_list_with_timestamps.each do |attr|-%>
|
2
|
+
json.<%=attr%> @<%= singular_table_name %>.<%=attr%>
|
3
|
+
<%- end -%>
|
4
|
+
|
5
|
+
|
6
|
+
json.<%= plural_table_name %>_path <%= plural_table_name %>_path
|
7
|
+
json.edit_<%= singular_table_name %>_path edit_<%= singular_table_name %>_path(@<%= singular_table_name %>)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>
|
2
|
+
|
3
|
+
<script type="text/javascript">
|
4
|
+
window.SUPERGLUE_INITIAL_PAGE_STATE=<%%= initial_state.html_safe %>;
|
5
|
+
</script>
|
6
|
+
|
7
|
+
<div id="app">
|
8
|
+
<%%# If you need SSR follow instructions at %>
|
9
|
+
<%%# https://github.com/thoughtbot/superglue/blob/main/docs/recipes/server-side-rendering.md %>
|
10
|
+
<%%# and uncomment the following line %>
|
11
|
+
<%%#= Humid.render(initial_state).html_safe %>
|
12
|
+
</div>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import RailsTag from '@thoughtbot/superglue/components/RailsTag'
|
3
|
+
// import * as actionCreators from 'javascript/packs/action_creators'
|
4
|
+
// import {useDispatch} from 'react-redux'
|
5
|
+
|
6
|
+
export default function <%= plural_table_name.camelize %>Edit ({
|
7
|
+
// visit,
|
8
|
+
// remote,
|
9
|
+
form,
|
10
|
+
flash,
|
11
|
+
<%= singular_table_name.camelize(:lower) %>Path,
|
12
|
+
<%= plural_table_name.camelize(:lower) %>Path,
|
13
|
+
}) {
|
14
|
+
const error = flash.form_error
|
15
|
+
|
16
|
+
const messagesEl = error && (
|
17
|
+
<div id="error_explanation">
|
18
|
+
<h2>{ error.explanation }</h2>
|
19
|
+
<ul>{ error.messages.map(({body})=> <li key={body}>{body}</li>) }</ul>
|
20
|
+
</div>
|
21
|
+
)
|
22
|
+
|
23
|
+
return (
|
24
|
+
<div>
|
25
|
+
{messagesEl}
|
26
|
+
<RailsTag {...form} data-sg-visit={true}/>
|
27
|
+
<a href={<%= singular_table_name.camelize(:lower) %>Path} data-sg-visit={true}>Show</a>
|
28
|
+
<a href={<%= plural_table_name.camelize(:lower) %>Path} data-sg-visit={true}>Back</a>
|
29
|
+
</div>
|
30
|
+
)
|
31
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
// import * as actionCreators from 'javascript/packs/action_creators'
|
3
|
+
// import {useDispatch} from 'react-redux'
|
4
|
+
|
5
|
+
export default function <%= plural_table_name.camelize %>Index({
|
6
|
+
// visit,
|
7
|
+
// remote,
|
8
|
+
flash,
|
9
|
+
new<%= singular_table_name.camelize %>Path,
|
10
|
+
<%= plural_table_name.camelize(:lower) %> = [],
|
11
|
+
}) {
|
12
|
+
const <%= singular_table_name.camelize(:lower) %>Items = <%= plural_table_name.camelize(:lower) %>.map((<%= singular_table_name.camelize(:lower) %>, key) => {
|
13
|
+
return (
|
14
|
+
<tr key={<%= singular_table_name.camelize(:lower) %>.id}>
|
15
|
+
<%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
|
16
|
+
<td>{<%=singular_table_name.camelize(:lower)%>.<%=attr.camelize(:lower)%>}</td>
|
17
|
+
<%- end -%>
|
18
|
+
<td><a href={ <%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path } data-sg-visit={true}>Show</a></td>
|
19
|
+
<td><a href={ <%=singular_table_name%>.edit<%=singular_table_name.camelize%>Path } data-sg-visit={true}>Edit</a></td>
|
20
|
+
<td><a href={ <%=singular_table_name%>.delete<%=singular_table_name.camelize%>Path }data-sg-visit={true} data-sg-method={"DELETE"}>Delete</a></td>
|
21
|
+
</tr>
|
22
|
+
)
|
23
|
+
})
|
24
|
+
|
25
|
+
return (
|
26
|
+
<div>
|
27
|
+
<p id="notice">{flash.notice}</p>
|
28
|
+
|
29
|
+
<h1><%= plural_table_name.capitalize %></h1>
|
30
|
+
|
31
|
+
<table>
|
32
|
+
<thead>
|
33
|
+
<%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
|
34
|
+
<tr><th><%=attr.capitalize%></th></tr>
|
35
|
+
<%- end -%>
|
36
|
+
<tr>
|
37
|
+
<th colSpan="3"></th>
|
38
|
+
</tr>
|
39
|
+
</thead>
|
40
|
+
|
41
|
+
<tbody>
|
42
|
+
{<%= singular_table_name %>Items}
|
43
|
+
</tbody>
|
44
|
+
</table>
|
45
|
+
<br />
|
46
|
+
<a href={new<%= singular_table_name.camelize %>Path} data-sg-visit={true}>New <%= singular_table_name.capitalize %></a>
|
47
|
+
</div>
|
48
|
+
)
|
49
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import RailsTag from '@thoughtbot/superglue/components/RailsTag'
|
3
|
+
// import * as actionCreators from 'javascript/packs/action_creators'
|
4
|
+
// import { useDispatch } from 'react-redux'
|
5
|
+
|
6
|
+
export default function <%= plural_table_name.camelize %>New({
|
7
|
+
// visit,
|
8
|
+
// remote
|
9
|
+
form,
|
10
|
+
flash,
|
11
|
+
<%= plural_table_name.camelize(:lower) %>Path,
|
12
|
+
}) {
|
13
|
+
const error = flash.form_error
|
14
|
+
|
15
|
+
const messagesEl = error && (
|
16
|
+
<div id="error_explanation">
|
17
|
+
<h2>{ error.explanation }</h2>
|
18
|
+
<ul>{ error.messages.map(({body})=> <li key={body}>{body}</li>) }</ul>
|
19
|
+
</div>
|
20
|
+
)
|
21
|
+
|
22
|
+
return (
|
23
|
+
<div>
|
24
|
+
{messagesEl}
|
25
|
+
<RailsTag {...form} data-sg-visit={true}/>
|
26
|
+
<a href={<%= plural_table_name.camelize(:lower) %>Path} data-sg-visit={true}>Back</a>
|
27
|
+
</div>
|
28
|
+
)
|
29
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
// import * as actionCreators from 'javascript/packs/action_creators'
|
3
|
+
// import {useDispatch} from 'react-redux'
|
4
|
+
|
5
|
+
export default function <%= plural_table_name.camelize %>Show({
|
6
|
+
// visit,
|
7
|
+
// remote,
|
8
|
+
flash,
|
9
|
+
<%- attributes_list_with_timestamps.select{|attr| attr != :id }.each do |attr| -%>
|
10
|
+
<%=attr.camelize(:lower)%>,
|
11
|
+
<%- end -%>
|
12
|
+
edit<%= singular_table_name.camelize %>Path,
|
13
|
+
<%= plural_table_name.camelize(:lower) %>Path
|
14
|
+
}) {
|
15
|
+
return (
|
16
|
+
<div>
|
17
|
+
<p id="notice">{flash && flash.notice}</p>
|
18
|
+
<%- attributes_list_with_timestamps.select{|attr| attr != :id }.each do |attr| -%>
|
19
|
+
<p>
|
20
|
+
<strong><%= attr.capitalize %>:</strong>
|
21
|
+
{<%=attr.camelize(:lower)%>}
|
22
|
+
</p>
|
23
|
+
<%- end -%>
|
24
|
+
<a href={ edit<%= singular_table_name.camelize %>Path } data-sg-visit={true}>Edit</a>
|
25
|
+
<a href={ <%= plural_table_name.camelize(:lower) %>Path } data-sg-visit={true}>Back</a>
|
26
|
+
</div>
|
27
|
+
)
|
28
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
|
3
|
+
import reduceReducers from 'reduce-reducers';
|
4
|
+
import thunk from 'redux-thunk';
|
5
|
+
import { Provider } from 'react-redux';
|
6
|
+
import { render } from 'react-dom';
|
7
|
+
import { ApplicationBase, fragmentMiddleware } from '@thoughtbot/superglue';
|
8
|
+
import { persistStore, persistReducer } from 'redux-persist';
|
9
|
+
import storage from 'redux-persist/lib/storage';
|
10
|
+
import { applicationRootReducer, applicationPagesReducer } from './reducer';
|
11
|
+
import { buildVisitAndRemote } from './application_visit';
|
12
|
+
|
13
|
+
// Mapping between your props template to Component, you must add to this
|
14
|
+
// to register any new page level component you create. If you are using the
|
15
|
+
// scaffold, it will auto append the identifers for you.
|
16
|
+
//
|
17
|
+
// e.g {'posts/new': PostNew}
|
18
|
+
const identifierToComponentMapping = {
|
19
|
+
};
|
20
|
+
|
21
|
+
if (typeof window !== "undefined") {
|
22
|
+
document.addEventListener("DOMContentLoaded", function () {
|
23
|
+
const appEl = document.getElementById("app");
|
24
|
+
const location = window.location;
|
25
|
+
|
26
|
+
if (appEl) {
|
27
|
+
render(
|
28
|
+
<Application
|
29
|
+
appEl={appEl}
|
30
|
+
// The base url prefixed to all calls made by the `visit`
|
31
|
+
// and `remote` thunks.
|
32
|
+
baseUrl={location.origin}
|
33
|
+
// The global var SUPERGLUE_INITIAL_PAGE_STATE is set by your erb
|
34
|
+
// template, e.g., index.html.erb
|
35
|
+
initialPage={window.SUPERGLUE_INITIAL_PAGE_STATE}
|
36
|
+
// The initial path of the page, e.g., /foobar
|
37
|
+
path={location.pathname + location.search + location.hash}
|
38
|
+
buildVisitAndRemote={buildVisitAndRemote}
|
39
|
+
/>,
|
40
|
+
appEl
|
41
|
+
);
|
42
|
+
}
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
export default class Application extends ApplicationBase {
|
47
|
+
mapping() {
|
48
|
+
return identifierToComponentMapping;
|
49
|
+
}
|
50
|
+
|
51
|
+
visitAndRemote(navRef, store) {
|
52
|
+
return buildVisitAndRemote(navRef, store);
|
53
|
+
}
|
54
|
+
|
55
|
+
buildStore(initialState, { superglue: superglueReducer, pages: pagesReducer }) {
|
56
|
+
// Create the store
|
57
|
+
// See `./reducer.js` for an explaination of the two included reducers
|
58
|
+
const composeEnhancers =
|
59
|
+
(this.hasWindow && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
|
60
|
+
compose;
|
61
|
+
const reducer = this.wrapWithPersistReducer(
|
62
|
+
reduceReducers(
|
63
|
+
combineReducers({
|
64
|
+
superglue: superglueReducer,
|
65
|
+
pages: reduceReducers(pagesReducer, applicationPagesReducer),
|
66
|
+
}),
|
67
|
+
applicationRootReducer
|
68
|
+
)
|
69
|
+
);
|
70
|
+
const store = createStore(
|
71
|
+
reducer,
|
72
|
+
initialState,
|
73
|
+
composeEnhancers(applyMiddleware(thunk, fragmentMiddleware))
|
74
|
+
);
|
75
|
+
|
76
|
+
if (this.hasWindow) {
|
77
|
+
// Persist the store using Redux-Persist
|
78
|
+
persistStore(store);
|
79
|
+
}
|
80
|
+
|
81
|
+
return store;
|
82
|
+
}
|
83
|
+
|
84
|
+
wrapWithPersistReducer(reducers) {
|
85
|
+
// Redux Persist settings
|
86
|
+
// The key is set to the stringified JS asset path to remove the need for
|
87
|
+
// migrations when hydrating.
|
88
|
+
if (!this.hasWindow) {
|
89
|
+
return reducers;
|
90
|
+
}
|
91
|
+
const prefix = "superglue";
|
92
|
+
const persistKey =
|
93
|
+
prefix +
|
94
|
+
this.props.initialPage.assets
|
95
|
+
.filter((asset) => asset.endsWith(".js"))
|
96
|
+
.join(",");
|
97
|
+
const persistConfig = {
|
98
|
+
key: persistKey,
|
99
|
+
storage,
|
100
|
+
};
|
101
|
+
|
102
|
+
// Remove older storage items that were used by previous JS assets
|
103
|
+
if (this.hasWindow) {
|
104
|
+
const storedKeys = Object.keys(localStorage);
|
105
|
+
storedKeys.forEach((key) => {
|
106
|
+
if (key.startsWith(`persist:${prefix}`) && key !== persistKey) {
|
107
|
+
localStorage.removeItem(key);
|
108
|
+
}
|
109
|
+
});
|
110
|
+
}
|
111
|
+
|
112
|
+
return persistReducer(persistConfig, reducers);
|
113
|
+
}
|
114
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
path = request.format.json? ? param_to_search_path(params[:props_at]) : nil
|
2
|
+
|
3
|
+
json.data(search: path) do
|
4
|
+
yield json
|
5
|
+
end
|
6
|
+
|
7
|
+
json.component_identifier local_assigns[:virtual_path_of_template]
|
8
|
+
json.defers json.deferred!
|
9
|
+
json.fragments json.fragments!
|
10
|
+
json.assets [
|
11
|
+
asset_pack_path('application.js'),
|
12
|
+
asset_path('application.css')
|
13
|
+
]
|
14
|
+
|
15
|
+
if protect_against_forgery?
|
16
|
+
json.csrf_token form_authenticity_token
|
17
|
+
end
|
18
|
+
|
19
|
+
if path
|
20
|
+
json.action 'graft'
|
21
|
+
json.path search_path_to_camelized_param(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
json.restore_strategy 'fromCacheAndRevisitInBackground'
|
25
|
+
|
26
|
+
json.rendered_at Time.now.to_i
|
27
|
+
json.flash flash.to_h
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { visit, remote } from '@thoughtbot/superglue/action_creators'
|
2
|
+
|
3
|
+
export function buildVisitAndRemote(ref, store) {
|
4
|
+
const appRemote = (...args) => {
|
5
|
+
return store.dispatch(remote(...args))
|
6
|
+
}
|
7
|
+
|
8
|
+
const appVisit = (...args) => {
|
9
|
+
// Do something before
|
10
|
+
// e.g, show loading state, you can access the current pageKey
|
11
|
+
// via store.getState().superglue.currentPageKey
|
12
|
+
let { action } = args
|
13
|
+
|
14
|
+
return store
|
15
|
+
.dispatch(visit(...args))
|
16
|
+
.then((meta) => {
|
17
|
+
// The assets fingerprints changed, instead of transitioning
|
18
|
+
// just go to the URL directly to retrieve new assets
|
19
|
+
if (meta.needsRefresh) {
|
20
|
+
window.location = meta.url
|
21
|
+
return
|
22
|
+
}
|
23
|
+
|
24
|
+
ref.current.navigateTo(meta.pageKey, {
|
25
|
+
action: meta.suggestedAction,
|
26
|
+
})
|
27
|
+
|
28
|
+
// always return meta
|
29
|
+
return meta
|
30
|
+
})
|
31
|
+
.finally(() => {
|
32
|
+
// Do something after
|
33
|
+
// e.g, hide loading state, you can access the changed pageKey
|
34
|
+
// via getState().superglue.currentPageKey
|
35
|
+
})
|
36
|
+
.catch((err) => {
|
37
|
+
const response = err.response
|
38
|
+
|
39
|
+
if (!response) {
|
40
|
+
console.error(err)
|
41
|
+
return
|
42
|
+
}
|
43
|
+
|
44
|
+
if (response.ok) {
|
45
|
+
// err gets thrown, but if the response is ok,
|
46
|
+
// it must be an html body that
|
47
|
+
// superglue can't parse, just go to the location
|
48
|
+
window.location = response.url
|
49
|
+
} else {
|
50
|
+
if (response.status >= 400 && response.status < 500) {
|
51
|
+
window.location = '/400.html'
|
52
|
+
return
|
53
|
+
}
|
54
|
+
|
55
|
+
if (response.status >= 500) {
|
56
|
+
window.location = '/500.html'
|
57
|
+
return
|
58
|
+
}
|
59
|
+
}
|
60
|
+
})
|
61
|
+
}
|
62
|
+
|
63
|
+
return { visit: appVisit, remote: appRemote }
|
64
|
+
}
|
65
|
+
|