forked from SymfonyCasts/symfony-ux
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpre-turbo-set-up-tutorial.diff
225 lines (225 loc) · 6.93 KB
/
pre-turbo-set-up-tutorial.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
diff --git a/tutorial/FeaturedProduct.js b/tutorial/FeaturedProduct.js
deleted file mode 100644
index 91b85ac..0000000
--- a/tutorial/FeaturedProduct.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-
-export default function(props) {
- return (
- <div>
- <div className="component-light product-show p-3 mb-5">
- <h5 className="text-center">Featured Product!</h5>
- <a href={'/product/'+props.product.id}>
- <img
- alt={ props.product.name }
- src={props.product.imageUrl}
- className="d-block"
- />
- </a>
- <div className="pt-3">
- <h6>
- {props.product.name}
- </h6>
-
- <a href={'/product/'+props.product.id} className="btn btn-sm btn-primary">More info</a>
- </div>
- </div>
- </div>
- )
-}
diff --git a/tutorial/MadeWithLove.js b/tutorial/MadeWithLove.js
deleted file mode 100644
index 7507a9d..0000000
--- a/tutorial/MadeWithLove.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-
-export default class MadeWithLove extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- hearts: 1,
- };
- }
-
- render() {
- return (
- <div>
- <span>Built with </span>
- <span
- onClick={() => this.setState({
- hearts: this.state.hearts + 1,
- })}
- >
- {'❤️'.repeat(this.state.hearts)}
- </span>
- <span> by your friends at </span>
- <a href="https://symfonycasts.com" target="_blank">
- SymfonyCasts
- </a>
- </div>
- )
- }
-}
diff --git a/tutorial/prefetch.js b/tutorial/prefetch.js
new file mode 100644
index 0000000..4ab3785
--- /dev/null
+++ b/tutorial/prefetch.js
@@ -0,0 +1,152 @@
+// https://gist.github.com/vitobotta/8ac3c6f65633b5edb2949aeff0dec69b
+
+// This code is to be used with https://turbo.hotwired.dev. By default Turbo keeps visited pages in its cache
+// so that when you visit one of those pages again, Turbo will fetch the copy from cache first and present that to the user, then
+// it will fetch the updated page from the server and replace the preview. This makes for a much more responsive navigation
+// between pages. We can improve this further with the code in this file. It enables automatic prefetching of a page when you
+// hover with the mouse on a link or touch it on a mobile device. There is a delay between the mouseover event and the click
+// event, so with this trick the page is already being fetched before the click happens, speeding up also the first
+// view of a page not yet in cache. When the page has been prefetched it is then added to Turbo's cache so it's available for
+// the next visit during the same session. Turbo's default behavior plus this trick make for much more responsive UIs (non SPA).
+
+import * as Turbo from '@hotwired/turbo';
+
+let lastTouchTimestamp
+let delayOnHover = 65
+let mouseoverTimer
+
+const pendingPrefetches = new Set()
+
+const eventListenersOptions = {
+ capture: true,
+ passive: true,
+}
+
+class Snapshot extends Turbo.navigator.view.snapshot.constructor {
+}
+
+document.addEventListener('touchstart', touchstartListener, eventListenersOptions)
+document.addEventListener('mouseover', mouseoverListener, eventListenersOptions)
+
+function touchstartListener(event) {
+ if (window.disablePrefetch) {
+ return
+ }
+
+ /* Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp`
+ * must be assigned on touchstart to be measured on mouseover. */
+ lastTouchTimestamp = performance.now()
+
+ const linkElement = event.target.closest('a')
+
+ if (!isPreloadable(linkElement)) {
+ return
+ }
+
+ preload(linkElement)
+}
+
+function mouseoverListener(event) {
+ if (window.disablePrefetch) {
+ return
+ }
+
+ if (performance.now() - lastTouchTimestamp < 1111) {
+ return
+ }
+
+ const linkElement = event.target.closest('a')
+
+ if (!isPreloadable(linkElement)) {
+ return
+ }
+
+ const url = linkElement.getAttribute("href")
+ const loc = new URL(url, location.protocol + "//" + location.host).toString()
+ const absoluteUrl = loc.toString()
+
+ if (pendingPrefetches.has(absoluteUrl)) {
+ return
+ }
+
+ pendingPrefetches.add(absoluteUrl)
+
+ linkElement.addEventListener('mouseout', mouseoutListener, {passive: true})
+
+ mouseoverTimer = setTimeout(() => {
+ preload(linkElement)
+ mouseoverTimer = undefined
+ }, delayOnHover)
+}
+
+function mouseoutListener(event) {
+ if (event.relatedTarget && event.target.closest('a') == event.relatedTarget.closest('a')) {
+ return
+ }
+
+ if (mouseoverTimer) {
+ clearTimeout(mouseoverTimer)
+ mouseoverTimer = undefined
+ }
+}
+
+function isPreloadable(linkElement) {
+ if (!linkElement || !linkElement.getAttribute("href") || linkElement.dataset.turbo == "false" || linkElement.dataset.prefetch == "false") {
+ return
+ }
+
+ if (linkElement.origin != location.origin) {
+ return
+ }
+
+ if (!['http:', 'https:'].includes(linkElement.protocol)) {
+ return
+ }
+
+ if (linkElement.protocol == 'http:' && location.protocol == 'https:') {
+ return
+ }
+
+ if (linkElement.search && !('prefetch' in linkElement.dataset)) {
+ return
+ }
+
+ if (linkElement.hash && linkElement.pathname + linkElement.search == location.pathname + location.search) {
+ return
+ }
+
+ return true
+}
+
+function fetchPage(url, success) {
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', url)
+ xhr.setRequestHeader('VND.PREFETCH', 'true')
+ xhr.setRequestHeader('Accept', 'text/html')
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState !== XMLHttpRequest.DONE) return
+ if (xhr.status !== 200) return
+ success(xhr.responseText)
+ }
+ xhr.send()
+}
+
+function preload(link) {
+ const url = link.getAttribute("href")
+ const loc = new URL(url, location.protocol + "//" + location.host)
+ const absoluteUrl = loc.toString()
+
+ if (link.dataset.prefetchWithLink == "true") {
+ const prefetcher = document.createElement('link')
+ prefetcher.rel = 'prefetch'
+ prefetcher.href = url
+ document.head.appendChild(prefetcher)
+ pendingPrefetches.delete(absoluteUrl)
+ } else if (!Turbo.navigator.view.snapshotCache.has(loc)) {
+ fetchPage(url, responseText => {
+ const snapshot = Snapshot.fromHTMLString(responseText)
+ Turbo.navigator.view.snapshotCache.put(loc, snapshot)
+ pendingPrefetches.delete(absoluteUrl)
+ })
+ }
+}