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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/lib/generators/rails/scaffold_controller_generator.rb +12 -0
  3. data/lib/generators/rails/superglue_generator.rb +92 -0
  4. data/lib/generators/rails/templates/_form.json.props +13 -0
  5. data/lib/generators/rails/templates/controller.rb.tt +82 -0
  6. data/lib/generators/rails/templates/edit.json.props +14 -0
  7. data/lib/generators/rails/templates/index.json.props +13 -0
  8. data/lib/generators/rails/templates/new.json.props +15 -0
  9. data/lib/generators/rails/templates/show.json.props +7 -0
  10. data/lib/generators/rails/templates/web/edit.html.erb +12 -0
  11. data/lib/generators/rails/templates/web/edit.js +31 -0
  12. data/lib/generators/rails/templates/web/index.html.erb +7 -0
  13. data/lib/generators/rails/templates/web/index.js +49 -0
  14. data/lib/generators/rails/templates/web/new.html.erb +7 -0
  15. data/lib/generators/rails/templates/web/new.js +29 -0
  16. data/lib/generators/rails/templates/web/show.html.erb +7 -0
  17. data/lib/generators/rails/templates/web/show.js +28 -0
  18. data/lib/install/templates/web/action_creators.js +14 -0
  19. data/lib/install/templates/web/actions.js +4 -0
  20. data/lib/install/templates/web/application.js +114 -0
  21. data/lib/install/templates/web/application.json.props +27 -0
  22. data/lib/install/templates/web/application_visit.js +65 -0
  23. data/lib/install/templates/web/initializer.rb +1 -0
  24. data/lib/install/templates/web/reducer.js +72 -0
  25. data/lib/install/web.rb +66 -0
  26. data/lib/superglue/helpers.rb +552 -0
  27. data/lib/superglue/redirection.rb +30 -0
  28. data/lib/superglue.rb +38 -0
  29. data/lib/tasks/install.rake +42 -0
  30. data/test/engine_test.rb +7 -0
  31. data/test/helpers_test.rb +23 -0
  32. data/test/render_test.rb +81 -0
  33. data/test/test_helper.rb +20 -0
  34. 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,7 @@
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"></div>
@@ -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,7 @@
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"></div>
@@ -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,7 @@
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"></div>
@@ -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,14 @@
1
+ // Example:
2
+ //
3
+ // import {
4
+ // CLEAR_FORM_ERRORS
5
+ // } from './actions'
6
+ //
7
+ // export function clearFormErrors(pageKey) {
8
+ // return {
9
+ // type: CLEAR_FORM_ERRORS,
10
+ // payload: {
11
+ // pageKey,
12
+ // }
13
+ // }
14
+ // }
@@ -0,0 +1,4 @@
1
+ // Example:
2
+ //
3
+ // export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'
4
+ export const REHYDRATE = 'persist/REHYDRATE'
@@ -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
+