superglue 0.54.0 → 1.0.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/superglue/install/install_generator.rb +119 -0
  3. data/lib/{install/templates/web → generators/superglue/install/templates}/application.json.props +2 -2
  4. data/lib/generators/superglue/install/templates/js/application.jsx +35 -0
  5. data/lib/generators/superglue/install/templates/js/application_visit.js +113 -0
  6. data/lib/generators/superglue/install/templates/js/components.js +2 -0
  7. data/lib/generators/superglue/install/templates/js/flash.js +44 -0
  8. data/lib/generators/superglue/install/templates/js/inputs.jsx +302 -0
  9. data/lib/generators/superglue/install/templates/js/jsconfig.json +9 -0
  10. data/lib/generators/superglue/install/templates/js/layout.jsx +16 -0
  11. data/lib/generators/superglue/install/templates/js/page_to_page_mapping.js +35 -0
  12. data/lib/generators/superglue/install/templates/js/store.js +30 -0
  13. data/lib/{install/templates/web/application.js → generators/superglue/install/templates/ts/application.tsx} +10 -16
  14. data/lib/generators/superglue/install/templates/ts/application_visit.ts +122 -0
  15. data/lib/generators/superglue/install/templates/ts/components.ts +2 -0
  16. data/lib/generators/superglue/install/templates/ts/flash.ts +46 -0
  17. data/lib/generators/superglue/install/templates/ts/inputs.tsx +547 -0
  18. data/lib/generators/superglue/install/templates/ts/layout.tsx +16 -0
  19. data/lib/generators/superglue/install/templates/ts/page_to_page_mapping.ts +34 -0
  20. data/lib/generators/superglue/install/templates/ts/store.ts +34 -0
  21. data/lib/generators/superglue/install/templates/ts/tsconfig.json +27 -0
  22. data/lib/generators/superglue/scaffold/scaffold_generator.rb +16 -0
  23. data/lib/generators/superglue/scaffold_controller/scaffold_controller_generator.rb +61 -0
  24. data/lib/generators/superglue/view_collection/templates/js/edit.jsx +40 -0
  25. data/lib/generators/superglue/view_collection/templates/js/index.jsx +62 -0
  26. data/lib/generators/superglue/view_collection/templates/js/new.jsx +38 -0
  27. data/lib/generators/superglue/view_collection/templates/js/show.jsx +26 -0
  28. data/lib/generators/superglue/view_collection/templates/props/edit.json.props +9 -0
  29. data/lib/generators/superglue/view_collection/templates/props/index.json.props +14 -0
  30. data/lib/generators/superglue/view_collection/templates/props/new.json.props +10 -0
  31. data/lib/generators/superglue/view_collection/templates/props/show.json.props +6 -0
  32. data/lib/generators/superglue/view_collection/templates/ts/edit.tsx +54 -0
  33. data/lib/generators/superglue/view_collection/templates/ts/index.tsx +77 -0
  34. data/lib/generators/superglue/view_collection/templates/ts/new.tsx +50 -0
  35. data/lib/generators/superglue/view_collection/templates/ts/show.tsx +37 -0
  36. data/lib/generators/superglue/view_collection/view_collection_generator.rb +180 -0
  37. data/lib/superglue/helpers.rb +1 -1
  38. data/lib/superglue.rb +2 -1
  39. metadata +60 -43
  40. data/lib/generators/rails/scaffold_controller_generator.rb +0 -12
  41. data/lib/generators/rails/superglue_generator.rb +0 -98
  42. data/lib/generators/rails/templates/controller.rb.tt +0 -68
  43. data/lib/generators/rails/templates/edit.json.props +0 -12
  44. data/lib/generators/rails/templates/index.json.props +0 -14
  45. data/lib/generators/rails/templates/new.json.props +0 -13
  46. data/lib/generators/rails/templates/show.json.props +0 -6
  47. data/lib/generators/rails/templates/web/edit.js +0 -35
  48. data/lib/generators/rails/templates/web/index.js +0 -56
  49. data/lib/generators/rails/templates/web/new.js +0 -33
  50. data/lib/generators/rails/templates/web/show.js +0 -28
  51. data/lib/install/templates/web/actions.js +0 -6
  52. data/lib/install/templates/web/application_visit.js +0 -65
  53. data/lib/install/templates/web/flash.js +0 -19
  54. data/lib/install/templates/web/page_to_page_mapping.js +0 -12
  55. data/lib/install/templates/web/pages.js +0 -15
  56. data/lib/install/templates/web/store.js +0 -32
  57. data/lib/install/web.rb +0 -55
  58. data/lib/tasks/install.rake +0 -9
  59. /data/lib/{install/templates/web → generators/superglue/install/templates}/initializer.rb +0 -0
  60. /data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/edit.html.erb +0 -0
  61. /data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/index.html.erb +0 -0
  62. /data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/new.html.erb +0 -0
  63. /data/lib/generators/{rails/templates/web → superglue/view_collection/templates/erb}/show.html.erb +0 -0
  64. /data/lib/generators/{rails/templates → superglue/view_collection/templates/props}/_form.json.props +0 -0
@@ -0,0 +1,35 @@
1
+ // import your page component
2
+ // e.g import PostsEdit from '../views/posts/edit'
3
+
4
+ // Mapping between your props template to Component, you must add to this
5
+ // to register any new page level component you create. If you are using the
6
+ // scaffold, it will auto append the identifers for you.
7
+ //
8
+ // For example:
9
+ //
10
+ // const pageIdentifierToPageComponent = {
11
+ // 'posts/new': PostNew
12
+ // };
13
+ //
14
+ //
15
+ // If you are using a build tool that supports globbing, you can automatically
16
+ // populate `pageIdentiferToPageComponent`. For example, if you are using vite,
17
+ // you can use the following snippet instead of manually importing.
18
+ //
19
+ // ```
20
+ // const pageIdentifierToPageComponent = {}
21
+ // const pages = import.meta.glob('../views/**/*.jsx', {eager: true})
22
+ //
23
+ // for (const key in pages) {
24
+ // if (pages.hasOwnProperty(key)) {
25
+ // const identifier = key.replace("../views/", "").split('.')[0];
26
+ // pageIdentifierToPageComponent[identifier] = pages[key].default;
27
+ // }
28
+ // }
29
+ // ```
30
+ //
31
+ const pageIdentifierToPageComponent = {
32
+ };
33
+
34
+ export { pageIdentifierToPageComponent }
35
+
@@ -0,0 +1,30 @@
1
+ import { configureStore } from "@reduxjs/toolkit"
2
+ import { useDispatch, useSelector, useStore } from "react-redux"
3
+ import { flashSlice } from "./slices/flash"
4
+ import {
5
+ beforeVisit,
6
+ beforeFetch,
7
+ beforeRemote,
8
+ rootReducer
9
+ } from "@thoughtbot/superglue"
10
+
11
+ const { pages, superglue } = rootReducer
12
+
13
+ export const store = configureStore({
14
+ devTools: process.env.NODE_ENV !== "production",
15
+ middleware: getDefaultMiddleware =>
16
+ getDefaultMiddleware({
17
+ serializableCheck: {
18
+ ignoredActions: [beforeFetch.type, beforeVisit.type, beforeRemote.type]
19
+ }
20
+ }),
21
+ reducer: {
22
+ superglue,
23
+ pages,
24
+ flash: flashSlice.reducer
25
+ }
26
+ })
27
+
28
+ export const useAppDispatch = useDispatch.withTypes()
29
+ export const useAppSelector = useSelector.withTypes()
30
+ export const useAppStore = useStore.withTypes()
@@ -1,22 +1,12 @@
1
1
  import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
- import { ApplicationBase } from '@thoughtbot/superglue';
3
+ import { Application, VisitResponse } from '@thoughtbot/superglue';
4
4
  import { buildVisitAndRemote } from './application_visit';
5
5
  import { pageIdentifierToPageComponent } from './page_to_page_mapping';
6
- import { buildStore } from './store'
6
+ import { store } from './store'
7
7
 
8
- class Application extends ApplicationBase {
9
- mapping() {
10
- return pageIdentifierToPageComponent;
11
- }
12
-
13
- visitAndRemote(navRef, store) {
14
- return buildVisitAndRemote(navRef, store);
15
- }
16
-
17
- buildStore(initialState, { superglue, pages}) {
18
- return buildStore(initialState, superglue, pages);
19
- }
8
+ declare global {
9
+ interface Window { SUPERGLUE_INITIAL_PAGE_STATE: VisitResponse; }
20
10
  }
21
11
 
22
12
  if (typeof window !== "undefined") {
@@ -28,7 +18,6 @@ if (typeof window !== "undefined") {
28
18
  const root = createRoot(appEl);
29
19
  root.render(
30
20
  <Application
31
- appEl={appEl}
32
21
  // The base url prefixed to all calls made by the `visit`
33
22
  // and `remote` thunks.
34
23
  baseUrl={location.origin}
@@ -37,10 +26,15 @@ if (typeof window !== "undefined") {
37
26
  initialPage={window.SUPERGLUE_INITIAL_PAGE_STATE}
38
27
  // The initial path of the page, e.g., /foobar
39
28
  path={location.pathname + location.search + location.hash}
29
+ // Callback used to setup visit and remote
30
+ buildVisitAndRemote={buildVisitAndRemote}
31
+ // Callback used to setup the store
32
+ store={store}
33
+ // Mapping between the page identifier to page component
34
+ mapping={pageIdentifierToPageComponent}
40
35
  />
41
36
  );
42
37
  }
43
38
  });
44
39
  }
45
40
 
46
-
@@ -0,0 +1,122 @@
1
+ import {
2
+ ApplicationRemote,
3
+ ApplicationVisit,
4
+ SuperglueStore,
5
+ BuildVisitAndRemote,
6
+ } from "@thoughtbot/superglue";
7
+ import { visit, remote } from "@thoughtbot/superglue/action_creators";
8
+
9
+ /**
10
+ * This function returns a wrapped visit and remote that will be used by UJS,
11
+ * the Navigation component, and passed to your page components through the
12
+ * NavigationContext.
13
+ *
14
+ * You can customize both functions to your liking. For example, for a progress
15
+ * bar. This file also adds support for data-sg-remote.
16
+ */
17
+ export const buildVisitAndRemote: BuildVisitAndRemote = (
18
+ ref,
19
+ store: SuperglueStore
20
+ ) => {
21
+ const appRemote: ApplicationRemote = (path, { dataset, ...options }) => {
22
+ /**
23
+ * You can make use of `dataset` to add custom UJS options.
24
+ * If you are implementing a progress bar, you can selectively
25
+ * hide it for some links. For example:
26
+ *
27
+ * ```
28
+ * <a href="/posts?props_at=data.header" data-sg-remote data-sg-hide-progress>
29
+ * Click me
30
+ * </a>
31
+ * ```
32
+ *
33
+ * This would be available as `sgHideProgress` on the dataset
34
+ */
35
+ return store.dispatch(remote(path, options));
36
+ };
37
+
38
+ const appVisit: ApplicationVisit = (path, { dataset, ...options } = {}) => {
39
+ /**
40
+ * Do something before we make a request.
41
+ * e.g, show a [progress bar](https://thoughtbot.github.io/superglue/recipes/progress-bar/).
42
+ *
43
+ * Hint: you can access the current pageKey
44
+ * via `store.getState().superglue.currentPageKey`
45
+ */
46
+ return store
47
+ .dispatch(visit(path, options))
48
+ .then((meta) => {
49
+ /**
50
+ * The assets fingerprints changed, instead of transitioning
51
+ * just go to the URL directly to retrieve new assets
52
+ */
53
+ if (meta.needsRefresh) {
54
+ window.location.href = meta.pageKey;
55
+ return meta;
56
+ }
57
+
58
+ /**
59
+ * Your first expanded UJS option, `data-sg-replace`
60
+ *
61
+ * This option overrides the `navigationAction` to allow a link click or
62
+ * a form submission to replace history instead of the usual push.
63
+ */
64
+ const navigatonAction = !!dataset?.sgReplace
65
+ ? "replace"
66
+ : meta.navigationAction;
67
+ ref.current?.navigateTo(meta.pageKey, {
68
+ action: navigatonAction,
69
+ });
70
+
71
+ /**
72
+ * Return the meta object, it's used for scroll restoration when
73
+ * handling the back button. You can skip returning, but Superglue
74
+ * will warn you about scroll restoration.
75
+ */
76
+ return meta;
77
+ })
78
+ .finally(() => {
79
+ /**
80
+ * Do something after a request.
81
+ *
82
+ * This is where you hide a progress bar.
83
+ */
84
+ })
85
+ .catch((err) => {
86
+ const response = err.response;
87
+
88
+ if (!response) {
89
+ /**
90
+ * This is for errors that are NOT from a HTTP request.
91
+ *
92
+ * Tooling like Sentry can capture console errors. If not, feel
93
+ * free to customize to send the error to your telemetry tool of choice.
94
+ */
95
+ console.error(err);
96
+ return;
97
+ }
98
+
99
+ if (response.ok) {
100
+ /**
101
+ * This is for errors that are from a HTTP request.
102
+ *
103
+ * If the response is OK, it must be an HTML body, we'll
104
+ * go to that locaton directly.
105
+ */
106
+ window.location = response.url;
107
+ } else {
108
+ if (response.status >= 400 && response.status < 500) {
109
+ window.location.href = "/400.html";
110
+ return;
111
+ }
112
+
113
+ if (response.status >= 500) {
114
+ window.location.href = "/500.html";
115
+ return;
116
+ }
117
+ }
118
+ });
119
+ };
120
+
121
+ return { visit: appVisit, remote: appRemote };
122
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./Layout";
2
+ export * from "./Inputs";
@@ -0,0 +1,46 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+ import { saveResponse, beforeVisit } from "@thoughtbot/superglue";
3
+
4
+ type FlashState = Record<string, any>
5
+
6
+ const initialState: FlashState = {};
7
+
8
+ export const flashSlice = createSlice({
9
+ name: "flash",
10
+ initialState: initialState,
11
+ reducers: {
12
+ clearFlash(state, { payload }: { payload: string }) {
13
+ const key = payload;
14
+ if (!key) {
15
+ return {};
16
+ }
17
+
18
+ delete state[key];
19
+
20
+ return {
21
+ ...state,
22
+ };
23
+ },
24
+ flash(state, { payload }) {
25
+ return {
26
+ ...state,
27
+ ...payload,
28
+ };
29
+ },
30
+ },
31
+ extraReducers: (builder) => {
32
+ builder.addCase(beforeVisit, (_state, _action) => {
33
+ return {};
34
+ });
35
+ builder.addCase(saveResponse, (state, action) => {
36
+ const { page } = action.payload;
37
+
38
+ return {
39
+ ...state,
40
+ ...(page.slices.flash as FlashState),
41
+ };
42
+ });
43
+ },
44
+ });
45
+
46
+ export const { clearFlash, flash } = flashSlice.actions;