├── .dockerignore
├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .gitignore
├── .nvm
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── etc
├── mime.types
└── nginx.conf
├── jsconfig.json
├── package.json
├── preview.gif
├── public
├── android-chrome-144x144.png
├── apple-touch-icon.png
├── browserconfig.xml
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── index.html
├── mstile-150x150.png
├── robots.txt
├── safari-pinned-tab.svg
└── site.webmanifest
├── src
├── api
│ ├── fetchCommits.js
│ └── index.js
├── assets
│ ├── .gitkeep
│ └── fonts
│ │ └── pt-mono
│ │ ├── ptm55ft.fnt
│ │ └── ptm55ft.png
├── components
│ ├── App
│ │ ├── App.test.js
│ │ ├── index.js
│ │ └── style.scss
│ ├── BlinkingBadge
│ │ ├── index.js
│ │ └── style.scss
│ ├── Branch
│ │ ├── BranchGeometry.js
│ │ ├── BranchMaterial.js
│ │ └── index.js
│ ├── CameraOperator
│ │ └── index.js
│ ├── Commit
│ │ └── index.js
│ ├── Debris
│ │ └── index.js
│ ├── Effects
│ │ └── index.js
│ ├── FlatText
│ │ ├── index.js
│ │ └── loadFont.js
│ ├── HUD
│ │ ├── index.js
│ │ └── style.scss
│ ├── LoadingScreen
│ │ ├── index.js
│ │ └── style.scss
│ ├── OrbitControls
│ │ └── index.js
│ ├── Stats
│ │ └── index.js
│ ├── Surf
│ │ ├── index.js
│ │ └── useBranches.js
│ └── Track
│ │ └── index.js
├── data
│ ├── facebook.react.master.json
│ └── mrdoob.three.js.master.json
├── globals.js
├── hooks
│ └── useRepo.js
├── index.js
├── serviceWorker.js
├── styles
│ ├── base.scss
│ ├── breakpoints.scss
│ ├── colors.scss
│ ├── index.scss
│ ├── normalize.css
│ ├── typography.css
│ └── z.scss
└── utils
│ ├── clamp.js
│ ├── config.js
│ ├── delay.js
│ ├── getBranchColor.js
│ ├── hsb2rgb.js
│ ├── isEnvironment.js
│ ├── isInView.js
│ ├── loadingManager
│ ├── FetchResource.js
│ ├── FontResource.js
│ ├── LoadingManager.js
│ ├── Resource.js
│ └── index.js
│ └── rgb2hex.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .cache
3 | node_modules
4 | build
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/.env
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_API_BASE_URL = http://localhost:5000
2 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_API_BASE_URL = https://api.repo.surf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # ide / editor
26 | .vscode
27 |
--------------------------------------------------------------------------------
/.nvm:
--------------------------------------------------------------------------------
1 | 12
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-alpine
2 |
3 | # Add ipv6 support
4 | RUN echo "ipv6" >> /etc/modules
5 |
6 | RUN apk update
7 |
8 | RUN node --version
9 |
10 | # Install git
11 | RUN apk add git
12 | RUN git --version
13 |
14 | # Install yarn
15 | # RUN apk add --no-cache yarn
16 | RUN yarn --version
17 |
18 | # Install yarn dependencies
19 | ADD package.json /yarn-tmp/package.json
20 | ADD yarn.lock /yarn-tmp/yarn.lock
21 | RUN cd /yarn-tmp/ && yarn && mkdir -p /code && mv /yarn-tmp/node_modules /code/
22 |
23 | # Copy all directory
24 | COPY . /code/
25 |
26 | WORKDIR /code/
27 |
28 | ENTRYPOINT ["yarn", "run"]
29 | CMD ["build"]
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Dario Carella
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Repo Surf 🏄♂️
2 |
3 | repo-surf is a repository viewer that let you dive through its commits and branches.
4 |
5 | [repo.surf](https://repo.surf)
6 |
7 |
8 |
9 |
10 |
11 | ## Repo selection 🔍
12 |
13 | Repo Surf supports Github repositories only pointing to `master` branch (that's a current limitation, see Wishlist).
14 |
15 | To select a repository, `author` and `repository-name` can be defined as follow:
16 |
17 | [repo.surf/splact/repo-surf](https://repo.surf/splact/repo-surf)
18 |
19 | ## Development 🔨
20 |
21 | ### Start
22 |
23 | Runs the site in the development mode with `yarn start`.
24 |
25 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
26 |
27 | ### Backend
28 |
29 | Backend repo can be found [here](https://github.com/Splact/repo-surf-backend).
30 |
31 | ### Wishlist ✨
32 |
33 | - [ ] Intro screen with a selection of most famous repositories to play (show 4 randomly picked from a larger list)
34 | - [ ] Debris (see [r3f-game](https://codesandbox.io/embed/r3f-game-i2160))
35 | - [ ] Support for branch selection
36 | - [ ] Performance improvement
37 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.0"
2 | services:
3 | nginx:
4 | image: nginx
5 | volumes:
6 | - wwwroot:/wwwroot
7 | - ./etc/nginx.conf:/etc/nginx/nginx.conf
8 | environment:
9 | - VIRTUAL_HOST=repo.surf
10 | - VIRTUAL_NETWORK=beaver-sites
11 | - VIRTUAL_PORT=80
12 | - LETSENCRYPT_HOST=repo.surf
13 | - LETSENCRYPT_EMAIL=dario.kar@gmail.com
14 | restart: always
15 | web:
16 | build: .
17 | volumes:
18 | - wwwroot:/code/build
19 | volumes:
20 | wwwroot:
21 | networks:
22 | default:
23 | external:
24 | name: beaver-sites
25 |
--------------------------------------------------------------------------------
/etc/mime.types:
--------------------------------------------------------------------------------
1 | application/activemessage
2 | application/andrew-inset ez
3 | application/annodex anx
4 | application/applefile
5 | application/atom+xml atom
6 | application/atomcat+xml atomcat
7 | application/atomicmail
8 | application/atomserv+xml atomsrv
9 | application/batch-SMTP
10 | application/bbolin lin
11 | application/beep+xml
12 | application/cals-1840
13 | application/commonground
14 | application/cu-seeme cu
15 | application/cybercash
16 | application/davmount+xml davmount
17 | application/dca-rft
18 | application/dec-dx
19 | application/dicom dcm
20 | application/docbook+xml
21 | application/dsptype tsp
22 | application/dvcs
23 | application/ecmascript es
24 | application/edi-consent
25 | application/edi-x12
26 | application/edifact
27 | application/eshop
28 | application/font-tdpfr
29 | application/futuresplash spl
30 | application/ghostview
31 | application/hta hta
32 | application/http
33 | application/hyperstudio
34 | application/iges
35 | application/index
36 | application/index.cmd
37 | application/index.obj
38 | application/index.response
39 | application/index.vnd
40 | application/iotp
41 | application/ipp
42 | application/isup
43 | application/java-archive jar
44 | application/java-serialized-object ser
45 | application/java-vm class
46 | application/javascript js
47 | application/json json
48 | application/m3g m3g
49 | application/mac-binhex40 hqx
50 | application/mac-compactpro cpt
51 | application/macwriteii
52 | application/marc
53 | application/mathematica nb nbp
54 | application/mbox mbox
55 | application/ms-tnef
56 | application/msaccess mdb
57 | application/msword doc dot
58 | application/mxf mxf
59 | application/news-message-id
60 | application/news-transmission
61 | application/ocsp-request
62 | application/ocsp-response
63 | application/octet-stream bin
64 | application/oda oda
65 | application/ogg ogx
66 | application/onenote one onetoc2 onetmp onepkg
67 | application/parityfec
68 | application/pdf pdf
69 | application/pgp-encrypted pgp
70 | application/pgp-keys key
71 | application/pgp-signature sig
72 | application/pics-rules prf
73 | application/pkcs10
74 | application/pkcs7-mime
75 | application/pkcs7-signature
76 | application/pkix-cert
77 | application/pkix-crl
78 | application/pkixcmp
79 | application/postscript ps ai eps epsi epsf eps2 eps3
80 | application/prs.alvestrand.titrax-sheet
81 | application/prs.cww
82 | application/prs.nprend
83 | application/qsig
84 | application/rar rar
85 | application/rdf+xml rdf
86 | application/remote-printing
87 | application/riscos
88 | application/rtf rtf
89 | application/sdp
90 | application/set-payment
91 | application/set-payment-initiation
92 | application/set-registration
93 | application/set-registration-initiation
94 | application/sgml
95 | application/sgml-open-catalog
96 | application/sieve
97 | application/sla stl
98 | application/slate
99 | application/smil+xml smi smil
100 | application/timestamp-query
101 | application/timestamp-reply
102 | application/vemmi
103 | application/whoispp-query
104 | application/whoispp-response
105 | application/wita
106 | application/x400-bp
107 | application/xhtml+xml xhtml xht
108 | application/xml xml xsd
109 | application/xml-dtd
110 | application/xml-external-parsed-entity
111 | application/xslt+xml xsl xslt
112 | application/xspf+xml xspf
113 | application/zip zip
114 | application/vnd.3M.Post-it-Notes
115 | application/vnd.accpac.simply.aso
116 | application/vnd.accpac.simply.imp
117 | application/vnd.acucobol
118 | application/vnd.aether.imp
119 | application/vnd.android.package-archive apk
120 | application/vnd.anser-web-certificate-issue-initiation
121 | application/vnd.anser-web-funds-transfer-initiation
122 | application/vnd.audiograph
123 | application/vnd.bmi
124 | application/vnd.businessobjects
125 | application/vnd.canon-cpdl
126 | application/vnd.canon-lips
127 | application/vnd.cinderella cdy
128 | application/vnd.claymore
129 | application/vnd.commerce-battelle
130 | application/vnd.commonspace
131 | application/vnd.comsocaller
132 | application/vnd.contact.cmsg
133 | application/vnd.cosmocaller
134 | application/vnd.ctc-posml
135 | application/vnd.cups-postscript
136 | application/vnd.cups-raster
137 | application/vnd.cups-raw
138 | application/vnd.cybank
139 | application/vnd.dna
140 | application/vnd.dpgraph
141 | application/vnd.dxr
142 | application/vnd.ecdis-update
143 | application/vnd.ecowin.chart
144 | application/vnd.ecowin.filerequest
145 | application/vnd.ecowin.fileupdate
146 | application/vnd.ecowin.series
147 | application/vnd.ecowin.seriesrequest
148 | application/vnd.ecowin.seriesupdate
149 | application/vnd.enliven
150 | application/vnd.epson.esf
151 | application/vnd.epson.msf
152 | application/vnd.epson.quickanime
153 | application/vnd.epson.salt
154 | application/vnd.epson.ssf
155 | application/vnd.ericsson.quickcall
156 | application/vnd.eudora.data
157 | application/vnd.fdf
158 | application/vnd.ffsns
159 | application/vnd.flographit
160 | application/vnd.framemaker
161 | application/vnd.fsc.weblaunch
162 | application/vnd.fujitsu.oasys
163 | application/vnd.fujitsu.oasys2
164 | application/vnd.fujitsu.oasys3
165 | application/vnd.fujitsu.oasysgp
166 | application/vnd.fujitsu.oasysprs
167 | application/vnd.fujixerox.ddd
168 | application/vnd.fujixerox.docuworks
169 | application/vnd.fujixerox.docuworks.binder
170 | application/vnd.fut-misnet
171 | application/vnd.google-earth.kml+xml kml
172 | application/vnd.google-earth.kmz kmz
173 | application/vnd.grafeq
174 | application/vnd.groove-account
175 | application/vnd.groove-identity-message
176 | application/vnd.groove-injector
177 | application/vnd.groove-tool-message
178 | application/vnd.groove-tool-template
179 | application/vnd.groove-vcard
180 | application/vnd.hhe.lesson-player
181 | application/vnd.hp-HPGL
182 | application/vnd.hp-PCL
183 | application/vnd.hp-PCLXL
184 | application/vnd.hp-hpid
185 | application/vnd.hp-hps
186 | application/vnd.httphone
187 | application/vnd.hzn-3d-crossword
188 | application/vnd.ibm.MiniPay
189 | application/vnd.ibm.afplinedata
190 | application/vnd.ibm.modcap
191 | application/vnd.informix-visionary
192 | application/vnd.intercon.formnet
193 | application/vnd.intertrust.digibox
194 | application/vnd.intertrust.nncp
195 | application/vnd.intu.qbo
196 | application/vnd.intu.qfx
197 | application/vnd.irepository.package+xml
198 | application/vnd.is-xpr
199 | application/vnd.japannet-directory-service
200 | application/vnd.japannet-jpnstore-wakeup
201 | application/vnd.japannet-payment-wakeup
202 | application/vnd.japannet-registration
203 | application/vnd.japannet-registration-wakeup
204 | application/vnd.japannet-setstore-wakeup
205 | application/vnd.japannet-verification
206 | application/vnd.japannet-verification-wakeup
207 | application/vnd.koan
208 | application/vnd.lotus-1-2-3
209 | application/vnd.lotus-approach
210 | application/vnd.lotus-freelance
211 | application/vnd.lotus-notes
212 | application/vnd.lotus-organizer
213 | application/vnd.lotus-screencam
214 | application/vnd.lotus-wordpro
215 | application/vnd.mcd
216 | application/vnd.mediastation.cdkey
217 | application/vnd.meridian-slingshot
218 | application/vnd.mif
219 | application/vnd.minisoft-hp3000-save
220 | application/vnd.mitsubishi.misty-guard.trustweb
221 | application/vnd.mobius.daf
222 | application/vnd.mobius.dis
223 | application/vnd.mobius.msl
224 | application/vnd.mobius.plc
225 | application/vnd.mobius.txf
226 | application/vnd.motorola.flexsuite
227 | application/vnd.motorola.flexsuite.adsi
228 | application/vnd.motorola.flexsuite.fis
229 | application/vnd.motorola.flexsuite.gotap
230 | application/vnd.motorola.flexsuite.kmr
231 | application/vnd.motorola.flexsuite.ttc
232 | application/vnd.motorola.flexsuite.wem
233 | application/vnd.mozilla.xul+xml xul
234 | application/vnd.ms-artgalry
235 | application/vnd.ms-asf
236 | application/vnd.ms-excel xls xlb xlt
237 | application/vnd.ms-excel.addin.macroEnabled.12 xlam
238 | application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb
239 | application/vnd.ms-excel.sheet.macroEnabled.12 xlsm
240 | application/vnd.ms-excel.template.macroEnabled.12 xltm
241 | application/vnd.ms-fontobject eot
242 | application/vnd.ms-lrm
243 | application/vnd.ms-officetheme thmx
244 | application/vnd.ms-pki.seccat cat
245 | #application/vnd.ms-pki.stl stl
246 | application/vnd.ms-powerpoint ppt pps
247 | application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam
248 | application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm
249 | application/vnd.ms-powerpoint.slide.macroEnabled.12 sldm
250 | application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm
251 | application/vnd.ms-powerpoint.template.macroEnabled.12 potm
252 | application/vnd.ms-project
253 | application/vnd.ms-tnef
254 | application/vnd.ms-word.document.macroEnabled.12 docm
255 | application/vnd.ms-word.template.macroEnabled.12 dotm
256 | application/vnd.ms-works
257 | application/vnd.mseq
258 | application/vnd.msign
259 | application/vnd.music-niff
260 | application/vnd.musician
261 | application/vnd.netfpx
262 | application/vnd.noblenet-directory
263 | application/vnd.noblenet-sealer
264 | application/vnd.noblenet-web
265 | application/vnd.novadigm.EDM
266 | application/vnd.novadigm.EDX
267 | application/vnd.novadigm.EXT
268 | application/vnd.oasis.opendocument.chart odc
269 | application/vnd.oasis.opendocument.database odb
270 | application/vnd.oasis.opendocument.formula odf
271 | application/vnd.oasis.opendocument.graphics odg
272 | application/vnd.oasis.opendocument.graphics-template otg
273 | application/vnd.oasis.opendocument.image odi
274 | application/vnd.oasis.opendocument.presentation odp
275 | application/vnd.oasis.opendocument.presentation-template otp
276 | application/vnd.oasis.opendocument.spreadsheet ods
277 | application/vnd.oasis.opendocument.spreadsheet-template ots
278 | application/vnd.oasis.opendocument.text odt
279 | application/vnd.oasis.opendocument.text-master odm
280 | application/vnd.oasis.opendocument.text-template ott
281 | application/vnd.oasis.opendocument.text-web oth
282 | application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
283 | application/vnd.openxmlformats-officedocument.presentationml.slide sldx
284 | application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
285 | application/vnd.openxmlformats-officedocument.presentationml.template potx
286 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
287 | application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
288 | application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
289 | application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
290 | application/vnd.osa.netdeploy
291 | application/vnd.palm
292 | application/vnd.pg.format
293 | application/vnd.pg.osasli
294 | application/vnd.powerbuilder6
295 | application/vnd.powerbuilder6-s
296 | application/vnd.powerbuilder7
297 | application/vnd.powerbuilder7-s
298 | application/vnd.powerbuilder75
299 | application/vnd.powerbuilder75-s
300 | application/vnd.previewsystems.box
301 | application/vnd.publishare-delta-tree
302 | application/vnd.pvi.ptid1
303 | application/vnd.pwg-xhtml-print+xml
304 | application/vnd.rapid
305 | application/vnd.rim.cod cod
306 | application/vnd.s3sms
307 | application/vnd.seemail
308 | application/vnd.shana.informed.formdata
309 | application/vnd.shana.informed.formtemplate
310 | application/vnd.shana.informed.interchange
311 | application/vnd.shana.informed.package
312 | application/vnd.smaf mmf
313 | application/vnd.sss-cod
314 | application/vnd.sss-dtf
315 | application/vnd.sss-ntf
316 | application/vnd.stardivision.calc sdc
317 | application/vnd.stardivision.chart sds
318 | application/vnd.stardivision.draw sda
319 | application/vnd.stardivision.impress sdd
320 | application/vnd.stardivision.math sdf
321 | application/vnd.stardivision.writer sdw
322 | application/vnd.stardivision.writer-global sgl
323 | application/vnd.street-stream
324 | application/vnd.sun.xml.calc sxc
325 | application/vnd.sun.xml.calc.template stc
326 | application/vnd.sun.xml.draw sxd
327 | application/vnd.sun.xml.draw.template std
328 | application/vnd.sun.xml.impress sxi
329 | application/vnd.sun.xml.impress.template sti
330 | application/vnd.sun.xml.math sxm
331 | application/vnd.sun.xml.writer sxw
332 | application/vnd.sun.xml.writer.global sxg
333 | application/vnd.sun.xml.writer.template stw
334 | application/vnd.svd
335 | application/vnd.swiftview-ics
336 | application/vnd.symbian.install sis
337 | application/vnd.tcpdump.pcap cap pcap
338 | application/vnd.triscape.mxs
339 | application/vnd.trueapp
340 | application/vnd.truedoc
341 | application/vnd.tve-trigger
342 | application/vnd.ufdl
343 | application/vnd.uplanet.alert
344 | application/vnd.uplanet.alert-wbxml
345 | application/vnd.uplanet.bearer-choice
346 | application/vnd.uplanet.bearer-choice-wbxml
347 | application/vnd.uplanet.cacheop
348 | application/vnd.uplanet.cacheop-wbxml
349 | application/vnd.uplanet.channel
350 | application/vnd.uplanet.channel-wbxml
351 | application/vnd.uplanet.list
352 | application/vnd.uplanet.list-wbxml
353 | application/vnd.uplanet.listcmd
354 | application/vnd.uplanet.listcmd-wbxml
355 | application/vnd.uplanet.signal
356 | application/vnd.vcx
357 | application/vnd.vectorworks
358 | application/vnd.vidsoft.vidconference
359 | application/vnd.visio vsd
360 | application/vnd.vividence.scriptfile
361 | application/vnd.wap.sic
362 | application/vnd.wap.slc
363 | application/vnd.wap.wbxml wbxml
364 | application/vnd.wap.wmlc wmlc
365 | application/vnd.wap.wmlscriptc wmlsc
366 | application/vnd.webturbo
367 | application/vnd.wordperfect wpd
368 | application/vnd.wordperfect5.1 wp5
369 | application/vnd.wrq-hp3000-labelled
370 | application/vnd.wt.stf
371 | application/vnd.xara
372 | application/vnd.xfdl
373 | application/vnd.yellowriver-custom-menu
374 | application/x-123 wk
375 | application/x-7z-compressed 7z
376 | application/x-abiword abw
377 | application/x-apple-diskimage dmg
378 | application/x-bcpio bcpio
379 | application/x-bittorrent torrent
380 | application/x-cab cab
381 | application/x-cbr cbr
382 | application/x-cbz cbz
383 | application/x-cdf cdf cda
384 | application/x-cdlink vcd
385 | application/x-chess-pgn pgn
386 | application/x-comsol mph
387 | application/x-core
388 | application/x-cpio cpio
389 | application/x-csh csh
390 | application/x-debian-package deb udeb
391 | application/x-director dcr dir dxr
392 | application/x-dms dms
393 | application/x-doom wad
394 | application/x-dvi dvi
395 | application/x-executable
396 | application/x-font pfa pfb gsf pcf pcf.Z
397 | application/x-font-woff woff
398 | application/x-freemind mm
399 | application/x-futuresplash spl
400 | application/x-ganttproject gan
401 | application/x-gnumeric gnumeric
402 | application/x-go-sgf sgf
403 | application/x-graphing-calculator gcf
404 | application/x-gtar gtar
405 | application/x-gtar-compressed tgz taz
406 | application/x-hdf hdf
407 | #application/x-httpd-eruby rhtml
408 | #application/x-httpd-php phtml pht php
409 | #application/x-httpd-php-source phps
410 | #application/x-httpd-php3 php3
411 | #application/x-httpd-php3-preprocessed php3p
412 | #application/x-httpd-php4 php4
413 | #application/x-httpd-php5 php5
414 | application/x-hwp hwp
415 | application/x-ica ica
416 | application/x-info info
417 | application/x-internet-signup ins isp
418 | application/x-iphone iii
419 | application/x-iso9660-image iso
420 | application/x-jam jam
421 | application/x-java-applet
422 | application/x-java-bean
423 | application/x-java-jnlp-file jnlp
424 | application/x-jmol jmz
425 | application/x-kchart chrt
426 | application/x-kdelnk
427 | application/x-killustrator kil
428 | application/x-koan skp skd skt skm
429 | application/x-kpresenter kpr kpt
430 | application/x-kspread ksp
431 | application/x-kword kwd kwt
432 | application/x-latex latex
433 | application/x-lha lha
434 | application/x-lyx lyx
435 | application/x-lzh lzh
436 | application/x-lzx lzx
437 | application/x-maker frm maker frame fm fb book fbdoc
438 | application/x-md5 md5
439 | application/x-mif mif
440 | application/x-mpegURL m3u8
441 | application/x-ms-wmd wmd
442 | application/x-ms-wmz wmz
443 | application/x-msdos-program com exe bat dll
444 | application/x-msi msi
445 | application/x-netcdf nc
446 | application/x-ns-proxy-autoconfig pac dat
447 | application/x-nwc nwc
448 | application/x-object o
449 | application/x-oz-application oza
450 | application/x-pkcs7-certreqresp p7r
451 | application/x-pkcs7-crl crl
452 | application/x-python-code pyc pyo
453 | application/x-qgis qgs shp shx
454 | application/x-quicktimeplayer qtl
455 | application/x-rdp rdp
456 | application/x-redhat-package-manager rpm
457 | application/x-rss+xml rss
458 | application/x-ruby rb
459 | application/x-rx
460 | application/x-scilab sci sce
461 | application/x-scilab-xcos xcos
462 | application/x-sh sh
463 | application/x-sha1 sha1
464 | application/x-shar shar
465 | application/x-shellscript
466 | application/x-shockwave-flash swf swfl
467 | application/x-silverlight scr
468 | application/x-sql sql
469 | application/x-stuffit sit sitx
470 | application/x-sv4cpio sv4cpio
471 | application/x-sv4crc sv4crc
472 | application/x-tar tar
473 | application/x-tcl tcl
474 | application/x-tex-gf gf
475 | application/x-tex-pk pk
476 | application/x-texinfo texinfo texi
477 | application/x-trash ~ % bak old sik
478 | application/x-troff t tr roff
479 | application/x-troff-man man
480 | application/x-troff-me me
481 | application/x-troff-ms ms
482 | application/x-ustar ustar
483 | application/x-videolan
484 | application/x-wais-source src
485 | application/x-wingz wz
486 | application/x-x509-ca-cert crt
487 | application/x-xcf xcf
488 | application/x-xfig fig
489 | application/x-xpinstall xpi
490 |
491 | audio/32kadpcm
492 | audio/3gpp
493 | audio/amr amr
494 | audio/amr-wb awb
495 | audio/annodex axa
496 | audio/basic au snd
497 | audio/csound csd orc sco
498 | audio/flac flac
499 | audio/g.722.1
500 | audio/l16
501 | audio/midi mid midi kar
502 | audio/mp4a-latm
503 | audio/mpa-robust
504 | audio/mpeg mpga mpega mp2 mp3 m4a
505 | audio/mpegurl m3u
506 | audio/ogg oga ogg opus spx
507 | audio/parityfec
508 | audio/prs.sid sid
509 | audio/telephone-event
510 | audio/tone
511 | audio/vnd.cisco.nse
512 | audio/vnd.cns.anp1
513 | audio/vnd.cns.inf1
514 | audio/vnd.digital-winds
515 | audio/vnd.everad.plj
516 | audio/vnd.lucent.voice
517 | audio/vnd.nortel.vbk
518 | audio/vnd.nuera.ecelp4800
519 | audio/vnd.nuera.ecelp7470
520 | audio/vnd.nuera.ecelp9600
521 | audio/vnd.octel.sbc
522 | audio/vnd.qcelp
523 | audio/vnd.rhetorex.32kadpcm
524 | audio/vnd.vmx.cvsd
525 | audio/x-aiff aif aiff aifc
526 | audio/x-gsm gsm
527 | audio/x-mpegurl m3u
528 | audio/x-ms-wma wma
529 | audio/x-ms-wax wax
530 | audio/x-pn-realaudio-plugin
531 | audio/x-pn-realaudio ra rm ram
532 | audio/x-realaudio ra
533 | audio/x-scpls pls
534 | audio/x-sd2 sd2
535 | audio/x-wav wav
536 |
537 | chemical/x-alchemy alc
538 | chemical/x-cache cac cache
539 | chemical/x-cache-csf csf
540 | chemical/x-cactvs-binary cbin cascii ctab
541 | chemical/x-cdx cdx
542 | chemical/x-cerius cer
543 | chemical/x-chem3d c3d
544 | chemical/x-chemdraw chm
545 | chemical/x-cif cif
546 | chemical/x-cmdf cmdf
547 | chemical/x-cml cml
548 | chemical/x-compass cpa
549 | chemical/x-crossfire bsd
550 | chemical/x-csml csml csm
551 | chemical/x-ctx ctx
552 | chemical/x-cxf cxf cef
553 | #chemical/x-daylight-smiles smi
554 | chemical/x-embl-dl-nucleotide emb embl
555 | chemical/x-galactic-spc spc
556 | chemical/x-gamess-input inp gam gamin
557 | chemical/x-gaussian-checkpoint fch fchk
558 | chemical/x-gaussian-cube cub
559 | chemical/x-gaussian-input gau gjc gjf
560 | chemical/x-gaussian-log gal
561 | chemical/x-gcg8-sequence gcg
562 | chemical/x-genbank gen
563 | chemical/x-hin hin
564 | chemical/x-isostar istr ist
565 | chemical/x-jcamp-dx jdx dx
566 | chemical/x-kinemage kin
567 | chemical/x-macmolecule mcm
568 | chemical/x-macromodel-input mmd mmod
569 | chemical/x-mdl-molfile mol
570 | chemical/x-mdl-rdfile rd
571 | chemical/x-mdl-rxnfile rxn
572 | chemical/x-mdl-sdfile sd sdf
573 | chemical/x-mdl-tgf tgf
574 | #chemical/x-mif mif
575 | chemical/x-mmcif mcif
576 | chemical/x-mol2 mol2
577 | chemical/x-molconn-Z b
578 | chemical/x-mopac-graph gpt
579 | chemical/x-mopac-input mop mopcrt mpc zmt
580 | chemical/x-mopac-out moo
581 | chemical/x-mopac-vib mvb
582 | chemical/x-ncbi-asn1 asn
583 | chemical/x-ncbi-asn1-ascii prt ent
584 | chemical/x-ncbi-asn1-binary val aso
585 | chemical/x-ncbi-asn1-spec asn
586 | chemical/x-pdb pdb ent
587 | chemical/x-rosdal ros
588 | chemical/x-swissprot sw
589 | chemical/x-vamas-iso14976 vms
590 | chemical/x-vmd vmd
591 | chemical/x-xtel xtel
592 | chemical/x-xyz xyz
593 |
594 | image/cgm
595 | image/g3fax
596 | image/gif gif
597 | image/ief ief
598 | image/jp2 jp2 jpg2
599 | image/jpeg jpeg jpg jpe
600 | image/jpm jpm
601 | image/jpx jpx jpf
602 | image/naplps
603 | image/pcx pcx
604 | image/png png
605 | image/prs.btif
606 | image/prs.pti
607 | image/svg+xml svg svgz
608 | image/tiff tiff tif
609 | image/vnd.cns.inf2
610 | image/vnd.djvu djvu djv
611 | image/vnd.dwg
612 | image/vnd.dxf
613 | image/vnd.fastbidsheet
614 | image/vnd.fpx
615 | image/vnd.fst
616 | image/vnd.fujixerox.edmics-mmr
617 | image/vnd.fujixerox.edmics-rlc
618 | image/vnd.microsoft.icon ico
619 | image/vnd.mix
620 | image/vnd.net-fpx
621 | image/vnd.svf
622 | image/vnd.wap.wbmp wbmp
623 | image/vnd.xiff
624 | image/x-canon-cr2 cr2
625 | image/x-canon-crw crw
626 | image/x-cmu-raster ras
627 | image/x-coreldraw cdr
628 | image/x-coreldrawpattern pat
629 | image/x-coreldrawtemplate cdt
630 | image/x-corelphotopaint cpt
631 | image/x-epson-erf erf
632 | image/x-icon
633 | image/x-jg art
634 | image/x-jng jng
635 | image/x-ms-bmp bmp
636 | image/x-nikon-nef nef
637 | image/x-olympus-orf orf
638 | image/x-photoshop psd
639 | image/x-portable-anymap pnm
640 | image/x-portable-bitmap pbm
641 | image/x-portable-graymap pgm
642 | image/x-portable-pixmap ppm
643 | image/x-rgb rgb
644 | image/x-xbitmap xbm
645 | image/x-xpixmap xpm
646 | image/x-xwindowdump xwd
647 |
648 | inode/chardevice
649 | inode/blockdevice
650 | inode/directory-locked
651 | inode/directory
652 | inode/fifo
653 | inode/socket
654 |
655 | message/delivery-status
656 | message/disposition-notification
657 | message/external-body
658 | message/http
659 | message/s-http
660 | message/news
661 | message/partial
662 | message/rfc822 eml
663 |
664 | model/iges igs iges
665 | model/mesh msh mesh silo
666 | model/vnd.dwf
667 | model/vnd.flatland.3dml
668 | model/vnd.gdl
669 | model/vnd.gs-gdl
670 | model/vnd.gtw
671 | model/vnd.mts
672 | model/vnd.vtu
673 | model/vrml wrl vrml
674 | model/x3d+vrml x3dv
675 | model/x3d+xml x3d
676 | model/x3d+binary x3db
677 |
678 | multipart/alternative
679 | multipart/appledouble
680 | multipart/byteranges
681 | multipart/digest
682 | multipart/encrypted
683 | multipart/form-data
684 | multipart/header-set
685 | multipart/mixed
686 | multipart/parallel
687 | multipart/related
688 | multipart/report
689 | multipart/signed
690 | multipart/voice-message
691 |
692 | text/cache-manifest appcache
693 | text/calendar ics icz
694 | text/css css
695 | text/csv csv
696 | text/directory
697 | text/english
698 | text/enriched
699 | text/h323 323
700 | text/html html htm shtml
701 | text/iuls uls
702 | text/mathml mml
703 | text/parityfec
704 | text/plain asc txt text pot brf srt
705 | text/prs.lines.tag
706 | text/rfc822-headers
707 | text/richtext rtx
708 | text/rtf
709 | text/scriptlet sct wsc
710 | text/t140
711 | text/texmacs tm
712 | text/tab-separated-values tsv
713 | text/turtle ttl
714 | text/uri-list
715 | text/vnd.abc
716 | text/vnd.curl
717 | text/vnd.debian.copyright
718 | text/vnd.DMClientScript
719 | text/vnd.flatland.3dml
720 | text/vnd.fly
721 | text/vnd.fmi.flexstor
722 | text/vnd.in3d.3dml
723 | text/vnd.in3d.spot
724 | text/vnd.IPTC.NewsML
725 | text/vnd.IPTC.NITF
726 | text/vnd.latex-z
727 | text/vnd.motorola.reflex
728 | text/vnd.ms-mediapackage
729 | text/vnd.sun.j2me.app-descriptor jad
730 | text/vnd.wap.si
731 | text/vnd.wap.sl
732 | text/vnd.wap.wml wml
733 | text/vnd.wap.wmlscript wmls
734 | text/x-bibtex bib
735 | text/x-boo boo
736 | text/x-c++hdr h++ hpp hxx hh
737 | text/x-c++src c++ cpp cxx cc
738 | text/x-chdr h
739 | text/x-component htc
740 | text/x-crontab
741 | text/x-csh csh
742 | text/x-csrc c
743 | text/x-dsrc d
744 | text/x-diff diff patch
745 | text/x-haskell hs
746 | text/x-java java
747 | text/x-lilypond ly
748 | text/x-literate-haskell lhs
749 | text/x-makefile
750 | text/x-moc moc
751 | text/x-pascal p pas
752 | text/x-pcs-gcd gcd
753 | text/x-perl pl pm
754 | text/x-python py
755 | text/x-scala scala
756 | text/x-server-parsed-html
757 | text/x-setext etx
758 | text/x-sfv sfv
759 | text/x-sh sh
760 | text/x-tcl tcl tk
761 | text/x-tex tex ltx sty cls
762 | text/x-vcalendar vcs
763 | text/x-vcard vcf
764 |
765 | video/3gpp 3gp
766 | video/annodex axv
767 | video/dl dl
768 | video/dv dif dv
769 | video/fli fli
770 | video/gl gl
771 | video/mpeg mpeg mpg mpe
772 | video/MP2T ts
773 | video/mp4 mp4
774 | video/quicktime qt mov
775 | video/mp4v-es
776 | video/ogg ogv
777 | video/parityfec
778 | video/pointer
779 | video/webm webm
780 | video/vnd.fvt
781 | video/vnd.motorola.video
782 | video/vnd.motorola.videop
783 | video/vnd.mpegurl mxu
784 | video/vnd.mts
785 | video/vnd.nokia.interleaved-multimedia
786 | video/vnd.vivo
787 | video/x-flv flv
788 | video/x-la-asf lsf lsx
789 | video/x-mng mng
790 | video/x-ms-asf asf asx
791 | video/x-ms-wm wm
792 | video/x-ms-wmv wmv
793 | video/x-ms-wmx wmx
794 | video/x-ms-wvx wvx
795 | video/x-msvideo avi
796 | video/x-sgi-movie movie
797 | video/x-matroska mpv mkv
798 |
799 | x-conference/x-cooltalk ice
800 |
801 | x-epoc/x-sisx-app sisx
802 | x-world/x-vrml vrm vrml wrl
803 |
--------------------------------------------------------------------------------
/etc/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes 4;
2 |
3 | events { worker_connections 1024; }
4 |
5 | http {
6 | client_max_body_size 20M;
7 | server_tokens off;
8 | add_header X-Frame-Options "SAMEORIGIN";
9 |
10 | server {
11 | listen 80;
12 |
13 | if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$ ) {
14 | return 444;
15 | }
16 |
17 | location / {
18 | root /wwwroot;
19 |
20 | include mime.types;
21 |
22 | try_files $uri $uri/ /index.html;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "repo-surf",
3 | "version": "0.2.0",
4 | "private": true,
5 | "dependencies": {
6 | "husky": "^3.0.9",
7 | "lint-staged": "^9.4.2",
8 | "load-bmfont": "^1.4.0",
9 | "node-sass": "^4.13.0",
10 | "prettier": "^1.18.2",
11 | "react": "^16.12.0",
12 | "react-dat-gui": "^4.0.0",
13 | "react-dom": "^16.12.0",
14 | "react-scripts": "4.0.0",
15 | "react-spring": "^8.0.27",
16 | "react-three-fiber": "^4.0.11",
17 | "react-transition-group": "^4.4.1",
18 | "stats.js": "^0.17.0",
19 | "three": "^0.112.0",
20 | "three-bmfont-text": "https://github.com/dmarcos/three-bmfont-text.git",
21 | "zustand": "^2.2.1"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject",
28 | "lint-staged": "lint-staged"
29 | },
30 | "husky": {
31 | "hooks": {
32 | "pre-commit": [
33 | "npm run lint-staged"
34 | ]
35 | }
36 | },
37 | "lint-staged": {
38 | "*.{js,jsx,scss,css,md}": [
39 | "prettier --write",
40 | "git add"
41 | ]
42 | },
43 | "eslintConfig": {
44 | "extends": "react-app"
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/preview.gif
--------------------------------------------------------------------------------
/public/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/android-chrome-144x144.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Repo Surf
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
32 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-144x144.png",
7 | "sizes": "144x144",
8 | "type": "image/png"
9 | }
10 | ],
11 | "theme_color": "#0c0e16",
12 | "background_color": "#0c0e16",
13 | "display": "standalone"
14 | }
15 |
--------------------------------------------------------------------------------
/src/api/fetchCommits.js:
--------------------------------------------------------------------------------
1 | const fetchCommits = async (owner, repo) => {
2 | const response = await fetch(
3 | `${process.env.REACT_APP_API_BASE_URL}/github/${owner}/${repo}`
4 | );
5 |
6 | return response.json();
7 | };
8 |
9 | export default fetchCommits;
10 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | export { default as fetchCommits } from "./fetchCommits";
2 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/fonts/pt-mono/ptm55ft.fnt:
--------------------------------------------------------------------------------
1 | {"pages":["PTM55FT.png"],"chars":[{"id":123,"width":32,"height":49,"xoffset":0,"yoffset":-29.442,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":0,"page":0},{"id":91,"width":32,"height":49,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":51,"page":0},{"id":40,"width":31,"height":49,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":102,"page":0},{"id":41,"width":31,"height":49,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":153,"page":0},{"id":125,"width":32,"height":49,"xoffset":0,"yoffset":-29.442,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":204,"page":0},{"id":106,"width":28,"height":49,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":255,"page":0},{"id":93,"width":29,"height":49,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":306,"page":0},{"id":36,"width":31,"height":48,"xoffset":0,"yoffset":-33.6,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":357,"page":0},{"id":47,"width":32,"height":46,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":407,"page":0},{"id":92,"width":32,"height":46,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":455,"page":0},{"id":81,"width":33,"height":46,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":34,"y":455,"page":0},{"id":124,"width":24,"height":45,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":69,"y":455,"page":0},{"id":64,"width":35,"height":42,"xoffset":0,"yoffset":-22.806,"xadvance":25.200000000000003,"chnl":15,"x":95,"y":455,"page":0},{"id":37,"width":34,"height":41,"xoffset":0,"yoffset":-30.156000000000002,"xadvance":25.200000000000003,"chnl":15,"x":132,"y":455,"page":0},{"id":79,"width":33,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":168,"y":455,"page":0},{"id":48,"width":33,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":203,"y":455,"page":0},{"id":42,"width":33,"height":40,"xoffset":0,"yoffset":-30.03,"xadvance":25.200000000000003,"chnl":15,"x":238,"y":455,"page":0},{"id":50,"width":32,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":273,"y":455,"page":0},{"id":51,"width":31,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":307,"y":455,"page":0},{"id":105,"width":32,"height":40,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":340,"y":455,"page":0},{"id":53,"width":31,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":374,"y":455,"page":0},{"id":54,"width":33,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":407,"y":455,"page":0},{"id":103,"width":32,"height":40,"xoffset":0,"yoffset":-21.336000000000002,"xadvance":25.200000000000003,"chnl":15,"x":442,"y":455,"page":0},{"id":56,"width":32,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":476,"y":455,"page":0},{"id":57,"width":33,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":34,"y":407,"page":0},{"id":102,"width":31,"height":40,"xoffset":0,"yoffset":-29.652,"xadvance":25.200000000000003,"chnl":15,"x":69,"y":407,"page":0},{"id":100,"width":35,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":102,"y":407,"page":0},{"id":98,"width":33,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":139,"y":407,"page":0},{"id":63,"width":32,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":174,"y":407,"page":0},{"id":108,"width":29,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":208,"y":407,"page":0},{"id":96,"width":26,"height":40,"xoffset":0,"yoffset":-30.240000000000002,"xadvance":25.200000000000003,"chnl":15,"x":239,"y":407,"page":0},{"id":66,"width":33,"height":40,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":267,"y":407,"page":0},{"id":67,"width":33,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":302,"y":407,"page":0},{"id":68,"width":33,"height":40,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":337,"y":407,"page":0},{"id":94,"width":34,"height":40,"xoffset":0,"yoffset":-29.862000000000002,"xadvance":25.200000000000003,"chnl":15,"x":372,"y":407,"page":0},{"id":38,"width":34,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":408,"y":407,"page":0},{"id":71,"width":32,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":444,"y":407,"page":0},{"id":112,"width":33,"height":40,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":478,"y":407,"page":0},{"id":121,"width":33,"height":40,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":33,"y":357,"page":0},{"id":74,"width":31,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":68,"y":357,"page":0},{"id":85,"width":33,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":101,"y":357,"page":0},{"id":83,"width":32,"height":40,"xoffset":0,"yoffset":-29.904000000000003,"xadvance":25.200000000000003,"chnl":15,"x":136,"y":357,"page":0},{"id":82,"width":34,"height":40,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":170,"y":357,"page":0},{"id":113,"width":32,"height":40,"xoffset":0,"yoffset":-21.336000000000002,"xadvance":25.200000000000003,"chnl":15,"x":206,"y":357,"page":0},{"id":33,"width":25,"height":40,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":240,"y":357,"page":0},{"id":80,"width":33,"height":40,"xoffset":0,"yoffset":-29.736,"xadvance":25.200000000000003,"chnl":15,"x":267,"y":357,"page":0},{"id":78,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":302,"y":357,"page":0},{"id":77,"width":33,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":336,"y":357,"page":0},{"id":76,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":371,"y":357,"page":0},{"id":84,"width":34,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":405,"y":357,"page":0},{"id":75,"width":35,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":441,"y":357,"page":0},{"id":86,"width":35,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":31,"y":306,"page":0},{"id":87,"width":35,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":68,"y":306,"page":0},{"id":88,"width":34,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":478,"y":357,"page":0},{"id":89,"width":35,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":105,"y":306,"page":0},{"id":90,"width":33,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":142,"y":306,"page":0},{"id":73,"width":33,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":177,"y":306,"page":0},{"id":72,"width":33,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":212,"y":306,"page":0},{"id":70,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":247,"y":306,"page":0},{"id":69,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":281,"y":306,"page":0},{"id":39,"width":25,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":315,"y":306,"page":0},{"id":65,"width":35,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":342,"y":306,"page":0},{"id":107,"width":33,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":379,"y":306,"page":0},{"id":49,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":414,"y":306,"page":0},{"id":52,"width":34,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":448,"y":306,"page":0},{"id":34,"width":29,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":30,"y":255,"page":0},{"id":104,"width":32,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":61,"y":255,"page":0},{"id":55,"width":31,"height":39,"xoffset":0,"yoffset":-29.400000000000002,"xadvance":25.200000000000003,"chnl":15,"x":95,"y":255,"page":0},{"id":59,"width":26,"height":38,"xoffset":0,"yoffset":-21.630000000000003,"xadvance":25.200000000000003,"chnl":15,"x":484,"y":306,"page":0},{"id":35,"width":34,"height":37,"xoffset":0,"yoffset":-26.964000000000002,"xadvance":25.200000000000003,"chnl":15,"x":128,"y":255,"page":0},{"id":116,"width":32,"height":37,"xoffset":0,"yoffset":-26.082,"xadvance":25.200000000000003,"chnl":15,"x":164,"y":255,"page":0},{"id":60,"width":33,"height":36,"xoffset":0,"yoffset":-26.082,"xadvance":25.200000000000003,"chnl":15,"x":198,"y":255,"page":0},{"id":62,"width":33,"height":36,"xoffset":0,"yoffset":-26.082,"xadvance":25.200000000000003,"chnl":15,"x":233,"y":255,"page":0},{"id":43,"width":33,"height":35,"xoffset":0,"yoffset":-24.990000000000002,"xadvance":25.200000000000003,"chnl":15,"x":268,"y":255,"page":0},{"id":119,"width":35,"height":31,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":303,"y":255,"page":0},{"id":117,"width":34,"height":32,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":34,"y":204,"page":0},{"id":99,"width":33,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":70,"y":204,"page":0},{"id":97,"width":33,"height":32,"xoffset":0,"yoffset":-21.336000000000002,"xadvance":25.200000000000003,"chnl":15,"x":105,"y":204,"page":0},{"id":109,"width":33,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":140,"y":204,"page":0},{"id":111,"width":33,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":175,"y":204,"page":0},{"id":118,"width":33,"height":31,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":340,"y":255,"page":0},{"id":95,"width":33,"height":19,"xoffset":0,"yoffset":0,"xadvance":25.200000000000003,"chnl":15,"x":375,"y":255,"page":0},{"id":120,"width":33,"height":31,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":210,"y":204,"page":0},{"id":114,"width":33,"height":31,"xoffset":0,"yoffset":-21.42,"xadvance":25.200000000000003,"chnl":15,"x":245,"y":204,"page":0},{"id":110,"width":32,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":33,"y":153,"page":0},{"id":126,"width":32,"height":28,"xoffset":0,"yoffset":-17.64,"xadvance":25.200000000000003,"chnl":15,"x":280,"y":204,"page":0},{"id":58,"width":25,"height":32,"xoffset":0,"yoffset":-21.630000000000003,"xadvance":25.200000000000003,"chnl":15,"x":67,"y":153,"page":0},{"id":61,"width":32,"height":29,"xoffset":0,"yoffset":-19.194000000000003,"xadvance":25.200000000000003,"chnl":15,"x":94,"y":153,"page":0},{"id":101,"width":32,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":33,"y":102,"page":0},{"id":115,"width":32,"height":32,"xoffset":0,"yoffset":-21.504,"xadvance":25.200000000000003,"chnl":15,"x":67,"y":102,"page":0},{"id":122,"width":32,"height":31,"xoffset":0,"yoffset":-21,"xadvance":25.200000000000003,"chnl":15,"x":101,"y":102,"page":0},{"id":45,"width":30,"height":24,"xoffset":0,"yoffset":-13.65,"xadvance":25.200000000000003,"chnl":15,"x":314,"y":204,"page":0},{"id":44,"width":26,"height":21,"xoffset":0,"yoffset":-5.1240000000000006,"xadvance":25.200000000000003,"chnl":15,"x":346,"y":204,"page":0},{"id":46,"width":25,"height":16,"xoffset":0,"yoffset":-5.1240000000000006,"xadvance":25.200000000000003,"chnl":15,"x":410,"y":255,"page":0},{"id":32,"width":0,"height":0,"xoffset":0,"yoffset":0,"xadvance":25.200000000000003,"chnl":15,"x":0,"y":503,"page":0}],"info":{"face":"PT Mono","size":42,"bold":0,"italic":0,"charset":[" ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_","`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","{","|","}","~"],"unicode":1,"stretchH":100,"smooth":1,"aa":1,"padding":[0,0,0,0],"spacing":[2,2]},"common":{"lineHeight":47.040000000000006,"base":37.17,"scaleW":512,"scaleH":512,"pages":1,"packed":0,"alphaChnl":0,"redChnl":0,"greenChnl":0,"blueChnl":0},"kernings":[]}
2 |
--------------------------------------------------------------------------------
/src/assets/fonts/pt-mono/ptm55ft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Splact/repo-surf/258ad24fd0dc859dd651a2573315cb526c6dd947/src/assets/fonts/pt-mono/ptm55ft.png
--------------------------------------------------------------------------------
/src/components/App/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from ".";
4 |
5 | it("renders without crashing", () => {
6 | const div = document.createElement("div");
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useEffect } from "react";
2 | import { Canvas } from "react-three-fiber";
3 | import * as THREE from "three";
4 |
5 | import BlinkingBadge from "components/BlinkingBadge";
6 | import Effects from "components/Effects";
7 | import LoadingScreen from "components/LoadingScreen";
8 | import Stats from "components/Stats";
9 | import Surf from "components/Surf";
10 | import useRepo from "hooks/useRepo";
11 | import { DatGui, useConfig } from "utils/config";
12 | import loadingManager, { useLoadingState } from "utils/loadingManager";
13 | import { isDevelopment } from "utils/isEnvironment";
14 |
15 | import "./style.scss";
16 |
17 | const backgroundColor = new THREE.Color("#0C0E16");
18 |
19 | const App = () => {
20 | const [isCanvasReady, setIsCanvasReady] = useState(false);
21 | const [isLoaded, setIsLoaded] = useState(false);
22 | const { owner, repo, commits } = useRepo();
23 | const { progress } = useLoadingState();
24 |
25 | const handleCanvasCreate = useCallback(
26 | ({ gl }) => {
27 | gl.gammaInput = true;
28 | gl.setClearColor(backgroundColor);
29 | setIsCanvasReady(true);
30 | },
31 | [setIsCanvasReady]
32 | );
33 |
34 | useEffect(() => {
35 | loadingManager.load();
36 | }, []);
37 |
38 | useEffect(() => {
39 | if (!isLoaded && progress === 1) {
40 | setTimeout(() => {
41 | setIsLoaded(true);
42 | }, 1000);
43 | }
44 | }, [isLoaded, progress]);
45 |
46 | const cameraSettings = useConfig(c => ({
47 | near: c.camera.near,
48 | far: c.camera.far,
49 | fov: c.camera.fov,
50 | position: [0, 0, -0.1]
51 | }));
52 |
53 | return (
54 | <>
55 | {isLoaded && progress === 1 && (
56 |
71 | )}
72 |
73 |
74 |
75 | {isDevelopment && }
76 |
77 | Preview
78 | >
79 | );
80 | };
81 |
82 | export default App;
83 |
--------------------------------------------------------------------------------
/src/components/App/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/colors.scss";
2 |
3 | .App {
4 | min-height: 100vh;
5 |
6 | background: $c--vulcan;
7 |
8 | h1 {
9 | margin: 0 0 1em;
10 | color: $c--mantis;
11 | }
12 |
13 | p {
14 | margin: 0;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/components/BlinkingBadge/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./style.scss";
3 |
4 | const BlinkingBadge = ({ className = "", children = "beta" }) => (
5 | {children}
6 | );
7 |
8 | export default BlinkingBadge;
9 |
--------------------------------------------------------------------------------
/src/components/BlinkingBadge/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/colors";
2 | @import "styles/z.scss";
3 |
4 | .BlinkingBadge {
5 | margin: 2rem;
6 |
7 | color: $c--oxford-blue;
8 | font-size: 1.4rem;
9 | text-transform: lowercase;
10 | letter-spacing: 0.2em;
11 |
12 | animation: blink infinite 2s forwards cubic-bezier(0.445, 0.05, 0.55, 0.95);
13 |
14 | position: fixed;
15 | top: 0;
16 | left: 0;
17 | z-index: $z--blinking-message;
18 | }
19 |
20 | @keyframes blink {
21 | 0% {
22 | opacity: 0;
23 | }
24 | 40% {
25 | opacity: 0;
26 | }
27 | 50% {
28 | opacity: 1;
29 | }
30 | 90% {
31 | opacity: 1;
32 | }
33 | 100% {
34 | opacity: 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/Branch/BranchGeometry.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | export default class BranchGeometry {
4 | constructor() {
5 | this.positions = [];
6 |
7 | this.previous = [];
8 | this.next = [];
9 | this.side = [];
10 | this.width = [];
11 | this.indices_array = [];
12 | this.uvs = [];
13 | this.counters = [];
14 | this.geometry = new THREE.BufferGeometry();
15 |
16 | this.widthCallback = null;
17 |
18 | // Used to raycast
19 | this.matrixWorld = new THREE.Matrix4();
20 |
21 | this.raycastInverseMatrix = new THREE.Matrix4();
22 | this.raycastRay = new THREE.Ray();
23 | this.raycastSphere = new THREE.Sphere();
24 | }
25 |
26 | setMatrixWorld = matrixWorld => {
27 | this.matrixWorld = matrixWorld;
28 | };
29 |
30 | setGeometry = (g, c) => {
31 | this.widthCallback = c;
32 |
33 | this.positions = [];
34 | this.counters = [];
35 | // g.computeBoundingBox();
36 | // g.computeBoundingSphere();
37 |
38 | // set the normals
39 | // g.computeVertexNormals();
40 | if (g instanceof THREE.Geometry) {
41 | for (let j = 0; j < g.vertices.length; j++) {
42 | let v = g.vertices[j];
43 | let c = j / g.vertices.length;
44 | this.positions.push(v.x, v.y, v.z);
45 | this.positions.push(v.x, v.y, v.z);
46 | this.counters.push(c);
47 | this.counters.push(c);
48 | }
49 | }
50 |
51 | if (g instanceof THREE.BufferGeometry) {
52 | // read attribute positions ?
53 | }
54 |
55 | if (g instanceof Float32Array || g instanceof Array) {
56 | for (let j = 0; j < g.length; j += 3) {
57 | const c = j / g.length;
58 | this.positions.push(g[j], g[j + 1], g[j + 2]);
59 | this.positions.push(g[j], g[j + 1], g[j + 2]);
60 | this.counters.push(c);
61 | this.counters.push(c);
62 | }
63 | }
64 |
65 | this.process();
66 | };
67 |
68 | raycast = (raycaster, intersects) => {
69 | var precision = raycaster.linePrecision;
70 | var precisionSq = precision * precision;
71 |
72 | var geometry = this.geometry;
73 |
74 | if (geometry.boundingSphere === null) geometry.computeBoundingSphere();
75 |
76 | // Checking boundingSphere distance to ray
77 |
78 | this.raycastSphere.copy(geometry.boundingSphere);
79 | this.raycastSphere.applyMatrix4(this.matrixWorld);
80 |
81 | if (raycaster.ray.intersectSphere(this.raycastSphere) === false) {
82 | return;
83 | }
84 |
85 | this.raycastInverseMatrix.getInverse(this.matrixWorld);
86 | this.raycastRay.copy(raycaster.ray).applyMatrix4(this.raycastInverseMatrix);
87 |
88 | var vStart = new THREE.Vector3();
89 | var vEnd = new THREE.Vector3();
90 | var interSegment = new THREE.Vector3();
91 | var interRay = new THREE.Vector3();
92 | var step = this instanceof THREE.LineSegments ? 2 : 1;
93 |
94 | if (geometry instanceof THREE.BufferGeometry) {
95 | var index = geometry.index;
96 | var attributes = geometry.attributes;
97 |
98 | if (index !== null) {
99 | var indices = index.array;
100 | var positions = attributes.position.array;
101 |
102 | for (var i = 0, l = indices.length - 1; i < l; i += step) {
103 | var a = indices[i];
104 | var b = indices[i + 1];
105 |
106 | vStart.fromArray(positions, a * 3);
107 | vEnd.fromArray(positions, b * 3);
108 |
109 | var distSq = this.raycastRay.distanceSqToSegment(
110 | vStart,
111 | vEnd,
112 | interRay,
113 | interSegment
114 | );
115 |
116 | if (distSq > precisionSq) continue;
117 |
118 | interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation
119 |
120 | var distance = raycaster.ray.origin.distanceTo(interRay);
121 |
122 | if (distance < raycaster.near || distance > raycaster.far) continue;
123 |
124 | intersects.push({
125 | distance: distance,
126 | // What do we want? intersection point on the ray or on the segment??
127 | // point: raycaster.ray.at( distance ),
128 | point: interSegment.clone().applyMatrix4(this.matrixWorld),
129 | index: i,
130 | face: null,
131 | faceIndex: null,
132 | object: this
133 | });
134 | }
135 | } else {
136 | const positions = attributes.position.array;
137 |
138 | for (let i = 0, l = positions.length / 3 - 1; i < l; i += step) {
139 | vStart.fromArray(positions, 3 * i);
140 | vEnd.fromArray(positions, 3 * i + 3);
141 |
142 | const distSq = this.raycastRay.distanceSqToSegment(
143 | vStart,
144 | vEnd,
145 | interRay,
146 | interSegment
147 | );
148 |
149 | if (distSq > precisionSq) continue;
150 |
151 | interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation
152 |
153 | const distance = raycaster.ray.origin.distanceTo(interRay);
154 |
155 | if (distance < raycaster.near || distance > raycaster.far) continue;
156 |
157 | intersects.push({
158 | distance: distance,
159 | // What do we want? intersection point on the ray or on the segment??
160 | // point: raycaster.ray.at( distance ),
161 | point: interSegment.clone().applyMatrix4(this.matrixWorld),
162 | index: i,
163 | face: null,
164 | faceIndex: null,
165 | object: this
166 | });
167 | }
168 | }
169 | } else if (geometry instanceof THREE.Geometry) {
170 | var vertices = geometry.vertices;
171 | var nbVertices = vertices.length;
172 |
173 | for (let i = 0; i < nbVertices - 1; i += step) {
174 | const distSq = this.raycastRay.distanceSqToSegment(
175 | vertices[i],
176 | vertices[i + 1],
177 | interRay,
178 | interSegment
179 | );
180 |
181 | if (distSq > precisionSq) continue;
182 |
183 | interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation
184 |
185 | const distance = raycaster.ray.origin.distanceTo(interRay);
186 |
187 | if (distance < raycaster.near || distance > raycaster.far) continue;
188 |
189 | intersects.push({
190 | distance: distance,
191 | // What do we want? intersection point on the ray or on the segment??
192 | // point: raycaster.ray.at( distance ),
193 | point: interSegment.clone().applyMatrix4(this.matrixWorld),
194 | index: i,
195 | face: null,
196 | faceIndex: null,
197 | object: this
198 | });
199 | }
200 | }
201 | };
202 |
203 | compareV3 = (a, b) => {
204 | const aa = a * 6;
205 | const ab = b * 6;
206 |
207 | return (
208 | this.positions[aa] === this.positions[ab] &&
209 | this.positions[aa + 1] === this.positions[ab + 1] &&
210 | this.positions[aa + 2] === this.positions[ab + 2]
211 | );
212 | };
213 |
214 | copyV3 = a => {
215 | const aa = a * 6;
216 |
217 | return [this.positions[aa], this.positions[aa + 1], this.positions[aa + 2]];
218 | };
219 |
220 | process = () => {
221 | const l = this.positions.length / 6;
222 |
223 | this.previous = [];
224 | this.next = [];
225 | this.side = [];
226 | this.width = [];
227 | this.indices_array = [];
228 | this.uvs = [];
229 |
230 | for (let j = 0; j < l; j++) {
231 | this.side.push(1);
232 | this.side.push(-1);
233 | }
234 |
235 | let w;
236 | for (let j = 0; j < l; j++) {
237 | if (this.widthCallback) w = this.widthCallback(j / (l - 1));
238 | else w = 1;
239 | this.width.push(w);
240 | this.width.push(w);
241 | }
242 |
243 | for (var j = 0; j < l; j++) {
244 | this.uvs.push(j / (l - 1), 0);
245 | this.uvs.push(j / (l - 1), 1);
246 | }
247 |
248 | var v;
249 |
250 | if (this.compareV3(0, l - 1)) {
251 | v = this.copyV3(l - 2);
252 | } else {
253 | v = this.copyV3(0);
254 | }
255 | this.previous.push(v[0], v[1], v[2]);
256 | this.previous.push(v[0], v[1], v[2]);
257 | for (let j = 0; j < l - 1; j++) {
258 | v = this.copyV3(j);
259 | this.previous.push(v[0], v[1], v[2]);
260 | this.previous.push(v[0], v[1], v[2]);
261 | }
262 |
263 | for (let j = 1; j < l; j++) {
264 | v = this.copyV3(j);
265 | this.next.push(v[0], v[1], v[2]);
266 | this.next.push(v[0], v[1], v[2]);
267 | }
268 |
269 | if (this.compareV3(l - 1, 0)) {
270 | v = this.copyV3(1);
271 | } else {
272 | v = this.copyV3(l - 1);
273 | }
274 | this.next.push(v[0], v[1], v[2]);
275 | this.next.push(v[0], v[1], v[2]);
276 |
277 | for (let j = 0; j < l - 1; j++) {
278 | var n = j * 2;
279 | this.indices_array.push(n, n + 1, n + 2);
280 | this.indices_array.push(n + 2, n + 1, n + 3);
281 | }
282 |
283 | if (!this.attributes) {
284 | this.attributes = {
285 | position: new THREE.BufferAttribute(
286 | new Float32Array(this.positions),
287 | 3
288 | ),
289 | previous: new THREE.BufferAttribute(new Float32Array(this.previous), 3),
290 | next: new THREE.BufferAttribute(new Float32Array(this.next), 3),
291 | side: new THREE.BufferAttribute(new Float32Array(this.side), 1),
292 | width: new THREE.BufferAttribute(new Float32Array(this.width), 1),
293 | uv: new THREE.BufferAttribute(new Float32Array(this.uvs), 2),
294 | index: new THREE.BufferAttribute(
295 | new Uint16Array(this.indices_array),
296 | 1
297 | ),
298 | counters: new THREE.BufferAttribute(new Float32Array(this.counters), 1)
299 | };
300 | } else {
301 | this.attributes.position.copyArray(new Float32Array(this.positions));
302 | this.attributes.position.needsUpdate = true;
303 | this.attributes.previous.copyArray(new Float32Array(this.previous));
304 | this.attributes.previous.needsUpdate = true;
305 | this.attributes.next.copyArray(new Float32Array(this.next));
306 | this.attributes.next.needsUpdate = true;
307 | this.attributes.side.copyArray(new Float32Array(this.side));
308 | this.attributes.side.needsUpdate = true;
309 | this.attributes.width.copyArray(new Float32Array(this.width));
310 | this.attributes.width.needsUpdate = true;
311 | this.attributes.uv.copyArray(new Float32Array(this.uvs));
312 | this.attributes.uv.needsUpdate = true;
313 | this.attributes.index.copyArray(new Uint16Array(this.indices_array));
314 | this.attributes.index.needsUpdate = true;
315 | }
316 |
317 | this.geometry.setAttribute("position", this.attributes.position);
318 | this.geometry.setAttribute("previous", this.attributes.previous);
319 | this.geometry.setAttribute("next", this.attributes.next);
320 | this.geometry.setAttribute("side", this.attributes.side);
321 | this.geometry.setAttribute("width", this.attributes.width);
322 | this.geometry.setAttribute("uv", this.attributes.uv);
323 | this.geometry.setAttribute("counters", this.attributes.counters);
324 |
325 | this.geometry.setIndex(this.attributes.index);
326 | };
327 |
328 | memcpy(src, srcOffset, dst, dstOffset, length) {
329 | var i;
330 |
331 | src = src.subarray || src.slice ? src : src.buffer;
332 | dst = dst.subarray || dst.slice ? dst : dst.buffer;
333 |
334 | src = srcOffset
335 | ? src.subarray
336 | ? src.subarray(srcOffset, length && srcOffset + length)
337 | : src.slice(srcOffset, length && srcOffset + length)
338 | : src;
339 |
340 | if (dst.set) {
341 | dst.set(src, dstOffset);
342 | } else {
343 | for (i = 0; i < src.length; i++) {
344 | dst[i + dstOffset] = src[i];
345 | }
346 | }
347 |
348 | return dst;
349 | }
350 |
351 | advance = position => {
352 | let positions = this.attributes.position.array;
353 | const previous = this.attributes.previous.array;
354 | let next = this.attributes.next.array;
355 | const l = positions.length;
356 |
357 | // PREVIOUS
358 | this.memcpy(positions, 0, previous, 0, l);
359 |
360 | // POSITIONS
361 | this.memcpy(positions, 6, positions, 0, l - 6);
362 |
363 | positions[l - 6] = position.x;
364 | positions[l - 5] = position.y;
365 | positions[l - 4] = position.z;
366 | positions[l - 3] = position.x;
367 | positions[l - 2] = position.y;
368 | positions[l - 1] = position.z;
369 |
370 | // NEXT
371 | this.memcpy(positions, 6, next, 0, l - 6);
372 |
373 | next[l - 6] = position.x;
374 | next[l - 5] = position.y;
375 | next[l - 4] = position.z;
376 | next[l - 3] = position.x;
377 | next[l - 2] = position.y;
378 | next[l - 1] = position.z;
379 |
380 | this.attributes.position.needsUpdate = true;
381 | this.attributes.previous.needsUpdate = true;
382 | this.attributes.next.needsUpdate = true;
383 | };
384 | }
385 |
--------------------------------------------------------------------------------
/src/components/Branch/BranchMaterial.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | THREE.ShaderChunk["branch_vert"] = [
4 | "",
5 | "attribute float side;",
6 | "attribute float width;",
7 | "attribute float counters;",
8 | "",
9 | "uniform vec2 resolution;",
10 | "uniform float lineWidth;",
11 | "uniform vec3 diffuse;",
12 | "uniform float opacity;",
13 | "uniform float near;",
14 | "uniform float far;",
15 | "uniform float sizeAttenuation;",
16 | "",
17 | "varying vec2 vUV;",
18 | "varying float vCounters;",
19 |
20 | "varying vec3 vLightFront;",
21 | "varying vec3 vIndirectFront;",
22 | "",
23 | "#ifdef DOUBLE_SIDED",
24 | " varying vec3 vLightBack;",
25 | " varying vec3 vIndirectBack;",
26 | "#endif",
27 | "",
28 | "#include ",
29 | "#include ",
30 | "#include ",
31 | "#include ",
32 | "#include ",
33 | "#include ",
34 | "#include ",
35 | "#include ",
36 | "#include ",
37 | "#include ",
38 | "#include ",
39 |
40 | "",
41 | "void main() {",
42 | "",
43 | " #include ",
44 | " #include ",
45 | " #include ",
46 | "",
47 | " #include ",
48 | " #include ",
49 | "",
50 | " #include ",
51 | "",
52 | " vUV = uv;",
53 | " vCounters = counters;",
54 | "",
55 | " float w = lineWidth * width;",
56 | " transformed = vec3(position.x - side * w * 0.5, 0.0, position.z);",
57 | "",
58 | " #include ",
59 | " #include ",
60 | " #include ",
61 | "",
62 | " #include ",
63 | " #include ",
64 | " #include ",
65 | " #include ",
66 | " #include ",
67 | "",
68 | "}"
69 | ].join("\r\n");
70 |
71 | THREE.ShaderChunk["branch_frag"] = [
72 | "",
73 | "uniform vec3 diffuse;",
74 | "uniform vec3 emissive;",
75 | "uniform float opacity;",
76 | "uniform sampler2D map;",
77 | "uniform sampler2D alphaMap;",
78 | "uniform float useMap;",
79 | "uniform float useAlphaMap;",
80 | "uniform float useDash;",
81 | "uniform float dashArray;",
82 | "uniform float dashOffset;",
83 | "uniform float dashRatio;",
84 | "uniform float visibility;",
85 | "uniform float alphaTest;",
86 | "uniform vec2 repeat;",
87 | "",
88 | "varying vec2 vUV;",
89 | "varying float vCounters;",
90 | "varying vec3 vLightFront;",
91 | "varying vec3 vIndirectFront;",
92 | "",
93 | "#ifdef DOUBLE_SIDED",
94 | " varying vec3 vLightBack;",
95 | " varying vec3 vIndirectBack;",
96 | "#endif",
97 | "",
98 | "",
99 | "#include ",
100 | "#include ",
101 | "#include ",
102 | "#include ",
103 | "#include ",
104 | "#include ",
105 | "#include ",
106 | "#include ",
107 | "#include ",
108 | "#include ",
109 | "#include ",
110 | "#include ",
111 | "#include ",
112 | "#include ",
113 | "#include ",
114 | "#include ",
115 | "#include ",
116 | "#include ",
117 | "#include ",
118 | "#include ",
119 | "#include ",
120 | "",
121 | "void main() {",
122 | "",
123 | " #include ",
124 | "",
125 | " vec4 diffuseColor = vec4( diffuse, opacity );",
126 | // " vec4 diffuseColor = vColor;",
127 |
128 | " ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );",
129 | " vec3 totalEmissiveRadiance = emissive;",
130 | "",
131 | " #include ",
132 | "",
133 | " if( useMap == 1. ) diffuseColor *= texture2D( map, vUV * repeat );",
134 | " if( useAlphaMap == 1. ) diffuseColor.a *= texture2D( alphaMap, vUV * repeat ).a;",
135 | " if( diffuseColor.a < alphaTest ) discard;",
136 | // // " #include ",
137 | // // " #include ",
138 | // // " #include ",
139 | // // " #include ",
140 | " if( useDash == 1. ){",
141 | " diffuseColor.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));",
142 | " }",
143 | "",
144 | " #include ",
145 | " #include ",
146 | "",
147 | " // accumulation",
148 | " reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );",
149 | "",
150 | " #ifdef DOUBLE_SIDED",
151 | " reflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;",
152 | " #else",
153 | " reflectedLight.indirectDiffuse += vIndirectFront;",
154 | " #endif",
155 | "",
156 | " #include ",
157 | "",
158 | " reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );",
159 | "",
160 | " #ifdef DOUBLE_SIDED",
161 | " reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;",
162 | " #else",
163 | " reflectedLight.directDiffuse = vLightFront;",
164 | " #endif",
165 | "",
166 | " reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();",
167 | "",
168 | " // modulation",
169 | " #include ",
170 | "",
171 | " vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;",
172 | "",
173 | " #include ",
174 | "",
175 | " gl_FragColor = vec4(outgoingLight, diffuseColor.a);",
176 | "",
177 | " gl_FragColor.a *= step(vCounters, visibility);",
178 | "",
179 | " #include ",
180 | " #include ",
181 | " #include ",
182 | " #include ",
183 | " #include ",
184 | "",
185 | "}"
186 | ].join("\r\n");
187 |
188 | export default class BranchMaterial extends THREE.ShaderMaterial {
189 | constructor(parameters) {
190 | super({
191 | uniforms: {
192 | ...THREE.UniformsLib.common,
193 | ...THREE.UniformsLib.specularmap,
194 | ...THREE.UniformsLib.envmap,
195 | ...THREE.UniformsLib.aomap,
196 | ...THREE.UniformsLib.lightmap,
197 | ...THREE.UniformsLib.emissivemap,
198 | ...THREE.UniformsLib.fog,
199 | ...THREE.UniformsLib.lights,
200 | lineWidth: { value: 1 },
201 | map: { value: null },
202 | useMap: { value: 0 },
203 | alphaMap: { value: null },
204 | useAlphaMap: { value: 0 },
205 | emissive: { value: new THREE.Color(0x000000) },
206 | opacity: { value: 1 },
207 | resolution: { value: new THREE.Vector2(1, 1) },
208 | sizeAttenuation: { value: 1 },
209 | near: { value: 1 },
210 | far: { value: 1 },
211 | dashArray: { value: 0 },
212 | dashOffset: { value: 0 },
213 | dashRatio: { value: 0.5 },
214 | useDash: { value: 0 },
215 | visibility: { value: 1 },
216 | alphaTest: { value: 0 },
217 | repeat: { value: new THREE.Vector2(1, 1) }
218 | },
219 |
220 | vertexShader: THREE.ShaderChunk.branch_vert,
221 | fragmentShader: THREE.ShaderChunk.branch_frag
222 | });
223 |
224 | this.type = "BranchMaterial";
225 | this.isBranchMaterial = true;
226 | this.lights = true;
227 |
228 | Object.defineProperties(this, {
229 | emissive: {
230 | enumerable: true,
231 | get: () => this.uniforms.emissive.value,
232 | set: value => (this.uniforms.emissive.value = value)
233 | },
234 |
235 | lineWidth: {
236 | enumerable: true,
237 | get: () => this.uniforms.lineWidth.value,
238 | set: value => (this.uniforms.lineWidth.value = value)
239 | },
240 | map: {
241 | enumerable: true,
242 | get: () => this.uniforms.map.value,
243 | set: value => (this.uniforms.map.value = value)
244 | },
245 | useMap: {
246 | enumerable: true,
247 | get: () => this.uniforms.useMap.value,
248 | set: value => (this.uniforms.useMap.value = value)
249 | },
250 | alphaMap: {
251 | enumerable: true,
252 | get: () => this.uniforms.alphaMap.value,
253 | set: value => (this.uniforms.alphaMap.value = value)
254 | },
255 | useAlphaMap: {
256 | enumerable: true,
257 | get: () => this.uniforms.useAlphaMap.value,
258 | set: value => (this.uniforms.useAlphaMap.value = value)
259 | },
260 | color: {
261 | enumerable: true,
262 | get: () => this.uniforms.diffuse.value,
263 | set: value => (this.uniforms.diffuse.value = value)
264 | },
265 | opacity: {
266 | enumerable: true,
267 | get: () => this.uniforms.opacity.value,
268 | set: value => (this.uniforms.opacity.value = value)
269 | },
270 | resolution: {
271 | enumerable: true,
272 | get: () => this.uniforms.resolution.value,
273 | set: value => this.uniforms.resolution.value.copy(value)
274 | },
275 | sizeAttenuation: {
276 | enumerable: true,
277 | get: () => this.uniforms.sizeAttenuation.value,
278 | set: value => (this.uniforms.sizeAttenuation.value = value)
279 | },
280 | near: {
281 | enumerable: true,
282 | get: () => this.uniforms.near.value,
283 | set: value => (this.uniforms.near.value = value)
284 | },
285 | far: {
286 | enumerable: true,
287 | get: () => this.uniforms.far.value,
288 | set: value => (this.uniforms.far.value = value)
289 | },
290 | dashArray: {
291 | enumerable: true,
292 | get: () => this.uniforms.dashArray.value,
293 | set: value => {
294 | this.uniforms.dashArray.value = value;
295 | this.useDash = value !== 0 ? 1 : 0;
296 | }
297 | },
298 | dashOffset: {
299 | enumerable: true,
300 | get: () => this.uniforms.dashOffset.value,
301 | set: value => (this.uniforms.dashOffset.value = value)
302 | },
303 | dashRatio: {
304 | enumerable: true,
305 | get: () => this.uniforms.dashRatio.value,
306 | set: value => (this.uniforms.dashRatio.value = value)
307 | },
308 | useDash: {
309 | enumerable: true,
310 | get: () => this.uniforms.useDash.value,
311 | set: value => (this.uniforms.useDash.value = value)
312 | },
313 | visibility: {
314 | enumerable: true,
315 | get: () => this.uniforms.visibility.value,
316 | set: value => (this.uniforms.visibility.value = value)
317 | },
318 | alphaTest: {
319 | enumerable: true,
320 | get: () => this.uniforms.alphaTest.value,
321 | set: value => (this.uniforms.alphaTest.value = value)
322 | },
323 | repeat: {
324 | enumerable: true,
325 | get: () => this.uniforms.repeat.value,
326 | set: value => this.uniforms.repeat.value.copy(value)
327 | }
328 | });
329 |
330 | this.setValues(parameters);
331 | }
332 |
333 | copy = source => {
334 | super.copy(source);
335 |
336 | this.lineWidth = source.lineWidth;
337 | this.map = source.map;
338 | this.useMap = source.useMap;
339 | this.alphaMap = source.alphaMap;
340 | this.useAlphaMap = source.useAlphaMap;
341 | this.color.copy(source.color);
342 | this.opacity = source.opacity;
343 | this.resolution.copy(source.resolution);
344 | this.sizeAttenuation = source.sizeAttenuation;
345 | this.near = source.near;
346 | this.far = source.far;
347 | this.dashArray.copy(source.dashArray);
348 | this.dashOffset.copy(source.dashOffset);
349 | this.dashRatio.copy(source.dashRatio);
350 | this.useDash = source.useDash;
351 | this.visibility = source.visibility;
352 | this.alphaTest = source.alphaTest;
353 | this.repeat.copy(source.repeat);
354 |
355 | return this;
356 | };
357 | }
358 |
--------------------------------------------------------------------------------
/src/components/Branch/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useRef, useMemo } from "react";
2 | import { useFrame, extend, useThree } from "react-three-fiber";
3 | import { Vector3, FrontSide } from "three";
4 |
5 | import Commit from "components/Commit";
6 | import { useConfig } from "utils/config";
7 |
8 | import BranchGeometry from "./BranchGeometry";
9 | import BranchMaterial from "./BranchMaterial";
10 |
11 | extend({ BranchGeometry, BranchMaterial });
12 |
13 | const Branch = memo(({ color, commits, skipLastCommit, skipFirstCommit }) => {
14 | const material = useRef();
15 |
16 | const speed = useConfig(c => c.speed);
17 | const commitsDistance = useConfig(c => c.commitsDistance);
18 | const waitOnFirstCommit = useConfig(c => c.waitOnFirstCommit);
19 | const { width, color: defaultColor } = useConfig(c => c.track);
20 | const { camera } = useThree();
21 |
22 | const vertices = useMemo(() => {
23 | const vv = [];
24 | let lastIndex = commits[0].index - 1;
25 |
26 | commits.forEach((c, ci) => {
27 | const deltaIndex = lastIndex - c.index;
28 |
29 | if (deltaIndex > 1) {
30 | // if the gap is bigger then 1
31 | for (let i = deltaIndex - 1; i > 0; i--) {
32 | // add missing points
33 | vv.push(
34 | new Vector3(
35 | c.position.x,
36 | c.position.y,
37 | c.position.z + commitsDistance * i
38 | )
39 | );
40 | }
41 | }
42 |
43 | vv.push(c.position);
44 |
45 | lastIndex = c.index;
46 | });
47 |
48 | return vv;
49 | }, [commits, commitsDistance]);
50 |
51 | // commit are in reverse order
52 | const minIndex = commits[commits.length - 1].index;
53 |
54 | useFrame(({ clock }) => {
55 | const waitToBranch = minIndex / speed;
56 | // update dash offset
57 | material.current.uniforms.dashOffset.value = Math.min(
58 | Math.max(
59 | ((clock.elapsedTime - waitOnFirstCommit - waitToBranch) * speed -
60 | 0.66) /
61 | vertices.length,
62 | 0
63 | ),
64 | 1
65 | );
66 | });
67 |
68 | return (
69 | <>
70 |
71 | {/* MeshLine and CMRCurve are a OOP factories, not scene objects */}
72 | (self.parent.geometry = self.geometry)}
74 | >
75 | self.parent.setGeometry(self)}>
76 |
79 | (self.parent.vertices = self.getPoints(vertices.length * 10))
80 | }
81 | />
82 |
83 |
84 |
85 |
100 |
101 |
102 | {commits.map((c, i) => {
103 | if (
104 | (!i && skipLastCommit) ||
105 | (i === commits.length - 1 && skipFirstCommit)
106 | ) {
107 | // do not render fork and merge commits (they have already been rendered their brnach)
108 | return null;
109 | }
110 |
111 | return (
112 |
121 | );
122 | })}
123 | >
124 | );
125 | });
126 |
127 | export default Branch;
128 |
--------------------------------------------------------------------------------
/src/components/CameraOperator/index.js:
--------------------------------------------------------------------------------
1 | import { useRef, useMemo } from "react";
2 | import { useFrame } from "react-three-fiber";
3 | import { useSpring } from "react-spring/three";
4 |
5 | import { useConfig } from "utils/config";
6 |
7 | const DISTANCE_FROM_HEAD_DAMPING = 0.02;
8 | const CAMERA_UP_DAMPING = 0.001;
9 |
10 | const CameraOperator = ({ commitsCount }) => {
11 | const distanceFromHead = useRef(0);
12 | const speed = useConfig(c => c.speed);
13 | const waitOnFirstCommit = useConfig(c => c.waitOnFirstCommit);
14 | const commitsDistance = useConfig(c => c.commitsDistance);
15 | const isOrbitControlsEnabled = useConfig(c => c.isOrbitControlsEnabled);
16 | const {
17 | position: { x, y },
18 | xVariation,
19 | yVariation,
20 | variationDuration,
21 | distanceFromHead: distanceFromHeadOnRun
22 | } = useConfig(c => c.camera);
23 |
24 | const [spring, set] = useSpring(() => ({
25 | position: [0, 10, -0.1],
26 | lookPosition: [0, 0.1, 0],
27 | config: {
28 | mass: 1,
29 | tension: 280,
30 | friction: 200
31 | }
32 | }));
33 |
34 | // {waitOnFirstCommit}s wait + time to reach the second commit
35 | const timeToMove = useMemo(() => waitOnFirstCommit + 1 / speed, [
36 | waitOnFirstCommit,
37 | speed
38 | ]);
39 | // last commit is reached with the camera
40 | const timeToAlmostEnd = useMemo(
41 | () => waitOnFirstCommit + (commitsCount - 1) / speed,
42 | [waitOnFirstCommit, speed, commitsCount]
43 | );
44 | // the camera reach the max elevation on last commit
45 | const timeToEnd = useMemo(() => {
46 | const tte = waitOnFirstCommit + commitsCount / speed;
47 | const ttq = variationDuration - (tte % variationDuration);
48 |
49 | return tte + ttq - variationDuration / 2;
50 | }, [waitOnFirstCommit, speed, commitsCount, variationDuration]);
51 |
52 | useFrame(({ camera, clock }) => {
53 | if (isOrbitControlsEnabled) {
54 | // skip camera update
55 | return;
56 | }
57 |
58 | // angle used for position variation
59 | const alpha =
60 | -Math.PI / 2 +
61 | (Math.max(0, Math.min(clock.elapsedTime - timeToMove, timeToEnd)) /
62 | variationDuration) *
63 | Math.PI *
64 | 2;
65 |
66 | let headZ = 0;
67 | let targetDistanceFromHead = -distanceFromHeadOnRun / 2;
68 | let targetUp = [0, 0, 1];
69 |
70 | // We have four time windows
71 | // 1. Before everithing starts | the camera z is same as first commt z | no
72 | // movement.
73 | // 2. Camera starts moving ahead taking a certain distance from the current
74 | // commit, the last commit is not reached yet.
75 | // 3. The latest commit is reached but the camera still moves to get the
76 | // final position.
77 | // 4. Camera is in it's final position observing the last commit.
78 | if (clock.elapsedTime > timeToMove) {
79 | headZ = (clock.elapsedTime - timeToMove) * speed * commitsDistance;
80 | headZ = Math.min(headZ, (commitsCount - 1) * commitsDistance);
81 | targetDistanceFromHead = -distanceFromHeadOnRun;
82 | }
83 |
84 | if (clock.elapsedTime > timeToMove + 1 / speed) {
85 | targetUp = [0, 1, 0];
86 | }
87 |
88 | if (clock.elapsedTime > timeToAlmostEnd) {
89 | targetDistanceFromHead = -1;
90 | }
91 |
92 | if (clock.elapsedTime > timeToEnd - variationDuration / 2) {
93 | targetUp = [-1, 0, 0];
94 | }
95 |
96 | distanceFromHead.current +=
97 | (targetDistanceFromHead - distanceFromHead.current) *
98 | DISTANCE_FROM_HEAD_DAMPING;
99 |
100 | // calc new position
101 | const newX = x + xVariation * Math.cos(alpha);
102 | const newY = y + yVariation * Math.sin(alpha);
103 | const newZ = headZ + 1 + distanceFromHead.current;
104 |
105 | const headX = 0;
106 |
107 | const [upX, upY, upZ] = camera.up.toArray();
108 | const newUp = [
109 | upX + (targetUp[0] - upX) * CAMERA_UP_DAMPING,
110 | upY + (targetUp[1] - upY) * CAMERA_UP_DAMPING,
111 | upZ + (targetUp[2] - upZ) * CAMERA_UP_DAMPING
112 | ];
113 |
114 | // update spring
115 | set({
116 | position: [newX, newY, newZ],
117 | lookPosition: [headX, 0.1, headZ]
118 | });
119 |
120 | // update position
121 | camera.position.set(...spring.position.getValue());
122 |
123 | // update look position
124 | camera.lookAt(...spring.lookPosition.getValue());
125 | camera.up.set(...newUp);
126 | });
127 |
128 | return null;
129 | };
130 |
131 | export default CameraOperator;
132 |
--------------------------------------------------------------------------------
/src/components/Commit/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect, useContext, useMemo } from "react";
2 | import { useSpring, config } from "react-spring/three";
3 | import { useFrame } from "react-three-fiber";
4 | import { Mesh, CircleBufferGeometry, MeshLambertMaterial } from "three";
5 |
6 | import FlatText from "components/FlatText";
7 | import { SurfContext } from "components/Surf";
8 | import { useConfig } from "utils/config";
9 | import isInView from "utils/isInView";
10 |
11 | const commitsBuffer = [];
12 | const getAvailableCommitFromBuffer = () => {
13 | let commit = commitsBuffer.find(c => c.isAvailable);
14 | if (!commit) {
15 | commit = {
16 | index: commitsBuffer.length,
17 | mesh: makeCommitMesh(),
18 | isAvailable: false
19 | };
20 | commitsBuffer.push(commit);
21 | } else {
22 | commit.isAvailable = false;
23 | }
24 |
25 | return commit;
26 | };
27 | const makeCommitMesh = () => {
28 | // create geometry
29 | const geometry = new CircleBufferGeometry(5, 32);
30 | // create material
31 | const material = new MeshLambertMaterial({
32 | emissive: "#8ACB67",
33 | emissiveIntensity: 1,
34 | depthTest: false,
35 | transparent: true,
36 | opacity: 0
37 | });
38 |
39 | return new Mesh(geometry, material);
40 | };
41 | const freeCommitOnBuffer = commit => {
42 | if (!commit) {
43 | return;
44 | }
45 |
46 | commit.isAvailable = true;
47 | };
48 |
49 | const Commit = props => {
50 | const {
51 | position,
52 | color,
53 | renderOrder = 0,
54 | sha,
55 | message,
56 | isReady,
57 | isActive
58 | } = props;
59 |
60 | const { radius, color: defaultColor, emissiveIntensity } = useConfig(
61 | c => c.commit
62 | );
63 |
64 | const groupRef = useRef();
65 | const commitRef = useRef();
66 | const [isGone, setIsGone] = useState(false);
67 |
68 | const [spring, set] = useSpring(() => ({
69 | scale: [0.01, 0.01, 0.01],
70 | position: [0, 0, 0],
71 | rotation: [0, 0, 0],
72 | config: config.slow
73 | }));
74 |
75 | useEffect(() => {
76 | if (isReady) {
77 | // open it
78 | set({ scale: [1, 1, 1] });
79 | }
80 | }, [isReady, set]);
81 |
82 | useEffect(() => {
83 | if (isActive) {
84 | // open it
85 | set({ scale: [1.2, 1.2, 1.2], position: [0, 1, 0] });
86 | } else {
87 | // close it
88 | set({ scale: [1, 1, 1], position: [0, 0, 0] });
89 | }
90 | }, [isActive, set]);
91 |
92 | useFrame(({ camera }) => {
93 | if (isGone) {
94 | return;
95 | }
96 |
97 | if (!commitRef.current && isReady && isInView(groupRef.current, camera)) {
98 | // the commit needs to be positioned (is in view)
99 | commitRef.current = getAvailableCommitFromBuffer();
100 | groupRef.current.add(commitRef.current.mesh);
101 | } else if (commitRef.current && !isInView(commitRef.current.mesh, camera)) {
102 | // the commit is out of view can be recycled
103 | groupRef.current.remove(commitRef.current.mesh);
104 | freeCommitOnBuffer(commitRef.current);
105 | commitRef.current = null;
106 | setIsGone(true);
107 | }
108 |
109 | if (!commitRef.current) {
110 | return;
111 | }
112 |
113 | // update mesh
114 | commitRef.current.mesh.renderOrder = renderOrder;
115 | const scale = spring.scale.getValue();
116 | commitRef.current.mesh.scale.x = scale[0];
117 | commitRef.current.mesh.scale.y = scale[1];
118 | commitRef.current.mesh.scale.z = scale[2];
119 | const position = spring.position.getValue();
120 | commitRef.current.mesh.position.x = position[0];
121 | commitRef.current.mesh.position.y = position[1];
122 | commitRef.current.mesh.position.z = position[2];
123 | const rotation = spring.rotation.getValue();
124 | commitRef.current.mesh.rotation.x = rotation[0];
125 | commitRef.current.mesh.rotation.y = rotation[1];
126 | commitRef.current.mesh.rotation.z = rotation[2];
127 |
128 | // update geometry radius
129 | // commitRef.current.mesh.geometry.parameters.radius = radius;
130 |
131 | // update material
132 | commitRef.current.mesh.material.emissive.set(color || defaultColor);
133 | commitRef.current.mesh.material.emissiveIntensity = emissiveIntensity;
134 | commitRef.current.mesh.material.opacity = 1;
135 | });
136 |
137 | const textPosition = useMemo(() => [-radius, 0, 5], [radius]);
138 | const textRotation = useMemo(() => [-Math.PI / 2, Math.PI, 0], []);
139 | const text = useMemo(() => [sha.substr(0, 7), "", message], [message, sha]);
140 |
141 | return (
142 |
147 |
153 | {text}
154 |
155 |
156 | );
157 | };
158 |
159 | const CommitWrapper = ({
160 | index,
161 | color,
162 | position,
163 | renderOrder = 0,
164 | sha,
165 | message
166 | }) => {
167 | const { currentCommit } = useContext(SurfContext);
168 | const isReady = index < currentCommit + 1;
169 | const isActive = index > currentCommit - 3 && index <= currentCommit;
170 |
171 | return useMemo(
172 | () => (
173 |
183 | ),
184 | [index, position, renderOrder, sha, message, color, isReady, isActive]
185 | );
186 | };
187 |
188 | export default CommitWrapper;
189 |
--------------------------------------------------------------------------------
/src/components/Debris/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useMemo } from "react";
2 | import { useConfig } from "utils/config";
3 | import { Vector3, Object3D } from "three";
4 |
5 | const dummy = new Object3D();
6 |
7 | const Debris = ({ commitsCount }) => {
8 | const instancedMesh = useRef();
9 | const commitsDistance = useConfig(c => c.commitsDistance);
10 | const debris = useMemo(
11 | () =>
12 | new Array(commitsCount * 35).fill().map((_, i) => {
13 | const t = Math.random();
14 | const radius = commitsDistance * 2;
15 |
16 | const position = new Vector3(
17 | -radius + Math.random() * radius * 2,
18 | -radius + Math.random() * radius * 2,
19 | commitsDistance * (i % commitsCount) -
20 | radius +
21 | Math.random() * radius * 2
22 | );
23 |
24 | const speed = 0.1 + Math.random();
25 |
26 | return {
27 | index: i,
28 | scale: 0.25 + Math.random() * 0.5,
29 | position,
30 | speed,
31 | radius,
32 | t
33 | };
34 | }),
35 | [commitsCount, commitsDistance]
36 | );
37 |
38 | useEffect(() => {
39 | debris.forEach(({ position, scale }, i) => {
40 | dummy.position.copy(position);
41 | dummy.scale.set(scale, scale, scale);
42 | dummy.rotation.set(
43 | Math.sin(Math.random()) * Math.PI,
44 | Math.sin(Math.random()) * Math.PI,
45 | Math.cos(Math.random()) * Math.PI
46 | );
47 | dummy.updateMatrix();
48 | instancedMesh.current.setMatrixAt(i, dummy.matrix);
49 | });
50 | instancedMesh.current.instanceMatrix.needsUpdate = true;
51 | }, []);
52 |
53 | return (
54 |
59 |
60 |
65 |
66 | );
67 | };
68 |
69 | export default Debris;
70 |
--------------------------------------------------------------------------------
/src/components/Effects/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 | import { extend, useThree, useFrame } from "react-three-fiber";
3 | import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
4 | import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
5 | import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass";
6 |
7 | extend({ EffectComposer, RenderPass, UnrealBloomPass });
8 |
9 | const Effects = () => {
10 | const composer = useRef();
11 | const { scene, gl, size, camera } = useThree();
12 |
13 | useEffect(() => composer.current.setSize(size.width, size.height), [size]);
14 |
15 | useFrame(() => composer.current.render(), 2);
16 |
17 | return (
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Effects;
26 |
--------------------------------------------------------------------------------
/src/components/FlatText/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useRef, useEffect, useState, useMemo } from "react";
2 | import { useSpring, a } from "react-spring/three";
3 | import { Color, Texture } from "three";
4 | import createMSDFShader from "three-bmfont-text/shaders/msdf";
5 | import createTextGeometry from "three-bmfont-text";
6 |
7 | import ptMonoFont from "assets/fonts/pt-mono/ptm55ft.fnt";
8 | import ptMonoImage from "assets/fonts/pt-mono/ptm55ft.png";
9 | import loadingManager, { RESOURCE_TYPE_FONT } from "utils/loadingManager";
10 |
11 | const ptMono = loadingManager.registerResource(
12 | {
13 | font: ptMonoFont,
14 | image: ptMonoImage
15 | },
16 | RESOURCE_TYPE_FONT
17 | );
18 |
19 | const DEFAULT_ATLAS = new Texture();
20 | const DEFAULT_COLOR = "#FFFFFF";
21 |
22 | const FlatText = memo(props => {
23 | const { active: isActive, children, color, width, ...meshProps } = props;
24 |
25 | const ref = useRef(null);
26 | const [font, setFont] = useState(null);
27 |
28 | const colorObject = useMemo(() => new Color(color), [color]);
29 | const text = useMemo(() => children.join("\n"), [children]);
30 | const MSDFShader = useMemo(
31 | () =>
32 | createMSDFShader({
33 | transparent: true,
34 | color: "#FFFFFF",
35 | opacity: 0
36 | }),
37 | []
38 | );
39 |
40 | useEffect(() => {
41 | const f = async () => {
42 | const { definition, atlas } = await ptMono.get();
43 |
44 | // create text geometry
45 | const geometry = createTextGeometry({
46 | text,
47 | font: definition, // the bitmap font definition
48 | width, // width for word-wrap
49 | lineHeight: 48
50 | });
51 |
52 | setFont({ definition, atlas, geometry });
53 | };
54 |
55 | f();
56 | }, [text, setFont, width]);
57 |
58 | const [spring, set] = useSpring(() => ({ "uniforms-opacity-value": 0 }));
59 | useEffect(() => {
60 | if (isActive) {
61 | // show
62 | set({ "uniforms-opacity-value": 1 });
63 | } else {
64 | // hide
65 | set({ "uniforms-opacity-value": 0 });
66 | }
67 | }, [isActive, set]);
68 |
69 | if (!font || !font.geometry) {
70 | return null;
71 | }
72 |
73 | return (
74 |
75 |
76 |
83 |
84 |
85 | );
86 | });
87 |
88 | FlatText.defaultProps = {
89 | color: DEFAULT_COLOR,
90 | width: 960
91 | };
92 |
93 | export default FlatText;
94 |
--------------------------------------------------------------------------------
/src/components/FlatText/loadFont.js:
--------------------------------------------------------------------------------
1 | import { TextureLoader } from "three";
2 | import loadBMFont from "load-bmfont";
3 | import loadingManager from "utils/loadingManager";
4 |
5 | const loadFont = ({ font, image }) =>
6 | new Promise((resolve, reject) => {
7 | loadBMFont(font, (e, definition) => {
8 | if (e) {
9 | reject(e);
10 | } else {
11 | const loader = new TextureLoader(loadingManager);
12 | loader.load(
13 | image,
14 | atlas => {
15 | resolve({ definition, atlas });
16 | },
17 | undefined,
18 | e => {
19 | reject(e);
20 | }
21 | );
22 | }
23 | });
24 | });
25 |
26 | export default loadFont;
27 |
--------------------------------------------------------------------------------
/src/components/HUD/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import create from "zustand";
3 |
4 | import "./style.scss";
5 |
6 | export const [useHUD] = create(set => ({
7 | log: set
8 | }));
9 |
10 | export const HUD = ({ filter }) => {
11 | const hud = useHUD(hud => hud);
12 |
13 | const keys = Object.keys(hud).filter(k => k !== "log" && filter(k));
14 |
15 | return (
16 |
17 |
18 | {keys.map(k => (
19 |
20 | {hud[k]} |
21 | {k} |
22 |
23 | ))}
24 |
25 |
26 | );
27 | };
28 | HUD.defaultProps = {
29 | filter: f => true
30 | };
31 |
32 | export default HUD;
33 |
--------------------------------------------------------------------------------
/src/components/HUD/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/z.scss";
2 |
3 | .hud {
4 | max-width: 100%;
5 | max-height: 100%;
6 | padding: 1rem;
7 |
8 | position: fixed;
9 | bottom: 0;
10 | right: 0;
11 | z-index: $z--hud;
12 |
13 | border: 2px solid #030523;
14 | background: #071b36;
15 | color: #6ae7e9;
16 | opacity: 0.3;
17 | transform: scale(0.9);
18 | transform-origin: bottom right;
19 |
20 | font-family: monospace;
21 | font-size: 1.6rem;
22 | text-align: right;
23 |
24 | transition: 1s cubic-bezier(0.23, 1, 0.32, 1);
25 | transition-property: opacity, transform;
26 |
27 | &:hover {
28 | opacity: 1;
29 | transform: scale(1);
30 | }
31 |
32 | th {
33 | font-weight: bold;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/LoadingScreen/index.js:
--------------------------------------------------------------------------------
1 | import classnames from "classnames";
2 | import React, { useRef, useState, useCallback, useEffect } from "react";
3 | import { SwitchTransition, CSSTransition } from "react-transition-group";
4 |
5 | import "./style.scss";
6 |
7 | const MESSAGES = ["status", "fetch", "checkout", "pull", "log"];
8 |
9 | const LoadingScreen = ({ loaded: isLoaded, progress: targetProgress }) => {
10 | const progressContainer = useRef();
11 | const progress = useRef(0);
12 | const animation = useRef(null);
13 | const [messageIndex, setMessageIndex] = useState(0);
14 | const [isVisible, setIsVisible] = useState(true);
15 |
16 | const isFullyLoaded = progress.current === 1 && isLoaded;
17 |
18 | const handleTransitionEnd = useCallback(() => {
19 | if (!isFullyLoaded) {
20 | return;
21 | }
22 |
23 | setIsVisible(false);
24 | }, [isFullyLoaded]);
25 |
26 | const handleProgressAnimation = useCallback(() => {
27 | if (
28 | !animation.current ||
29 | animation.current.targetProgress === progress.current
30 | ) {
31 | // target reached, interrupt animation
32 | animation.current = null;
33 | return;
34 | }
35 |
36 | // update progress
37 | progress.current +=
38 | (animation.current.targetProgress - progress.current) * 0.05;
39 | const newProgressValue = Math.round(progress.current * 100);
40 | if (newProgressValue === 100) {
41 | // if round value is 100, update progress to 1 and interrupt animation
42 | progress.current = 1;
43 | }
44 | progressContainer.current.textContent = newProgressValue;
45 |
46 | // check for new message
47 | const newMessageIndex = Math.floor(
48 | progress.current * (MESSAGES.length - 1)
49 | );
50 |
51 | if (newMessageIndex !== animation.current.messageIndex) {
52 | // if message changed fire new render
53 | setMessageIndex(newMessageIndex);
54 | }
55 |
56 | animation.current.raf = requestAnimationFrame(handleProgressAnimation);
57 | }, []);
58 |
59 | useEffect(() => {
60 | if (!animation.current) {
61 | animation.current = {
62 | raf: requestAnimationFrame(handleProgressAnimation),
63 | targetProgress,
64 | messageIndex
65 | };
66 | }
67 |
68 | return () => {
69 | if (animation.current) {
70 | cancelAnimationFrame(animation.current.raf);
71 | animation.current = null;
72 | }
73 | };
74 | }, [handleProgressAnimation, targetProgress, messageIndex]);
75 |
76 | if (!isVisible) {
77 | return null;
78 | }
79 |
80 | return (
81 |
87 |
88 |
89 | {Math.round(progress.current * 100)}
90 |
91 |
92 |
93 |
98 |
99 | {MESSAGES[messageIndex]}
100 |
101 |
102 |
103 |
104 |
105 |
106 | );
107 | };
108 |
109 | export default LoadingScreen;
110 |
--------------------------------------------------------------------------------
/src/components/LoadingScreen/style.scss:
--------------------------------------------------------------------------------
1 | @import "styles/breakpoints.scss";
2 | @import "styles/colors.scss";
3 | @import "styles/z.scss";
4 |
5 | .LoadingScreen {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: flex-end;
9 |
10 | width: 100%;
11 | height: 100%;
12 |
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | z-index: $z--loading-screen;
17 |
18 | user-select: none;
19 | background: $c--vulcan;
20 |
21 | transition: opacity 1s cubic-bezier(0.23, 1, 0.32, 1);
22 | opacity: 0;
23 |
24 | &--visible {
25 | opacity: 1;
26 | }
27 |
28 | &__inner {
29 | padding: 2.4rem;
30 |
31 | color: white;
32 |
33 | @include media-md() {
34 | padding: 3.2rem;
35 | }
36 | }
37 |
38 | &__progress {
39 | font-size: 12.8rem;
40 |
41 | @include media-md() {
42 | font-size: 16rem;
43 | }
44 |
45 | &::after {
46 | content: "%";
47 | }
48 | }
49 |
50 | &__messages {
51 | }
52 |
53 | &__message {
54 | font-size: 3.2rem;
55 | text-transform: uppercase;
56 |
57 | &--enter {
58 | transform: translateY(100%);
59 | opacity: 0;
60 | }
61 | &--enter-active {
62 | transition: 0.3s cubic-bezier(0.23, 1, 0.32, 1);
63 | transition-property: transform, opacity;
64 | transform: translateY(0);
65 | opacity: 1;
66 | }
67 | &--exit {
68 | transform: translateY(0);
69 | opacity: 1;
70 | }
71 | &--exit-active {
72 | transition: 0.3s cubic-bezier(0.23, 1, 0.32, 1);
73 | transition-property: transform, opacity;
74 | transform: translateY(-100%);
75 | opacity: 0;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/OrbitControls/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from "react";
2 | import { useThree, extend } from "react-three-fiber";
3 | import { OrbitControls as THREEOrbitControls } from "three/examples/jsm/controls/OrbitControls";
4 |
5 | import { useConfig } from "utils/config";
6 |
7 | extend({ OrbitControls: THREEOrbitControls });
8 |
9 | const OrbitControls = () => {
10 | const { camera, gl } = useThree();
11 | const ref = useRef(null);
12 | const isEnabled = useConfig(config => config.isOrbitControlsEnabled);
13 |
14 | return (
15 |
20 | );
21 | };
22 |
23 | export default OrbitControls;
24 |
--------------------------------------------------------------------------------
/src/components/Stats/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useFrame } from "react-three-fiber";
3 | import StatsJS from "stats.js";
4 |
5 | let stats;
6 |
7 | const Stats = () => {
8 | useEffect(() => {
9 | stats = new StatsJS();
10 | stats.showPanel(0);
11 | document.body.appendChild(stats.dom);
12 |
13 | return () => {
14 | stats.dom.remove();
15 | };
16 | }, []);
17 |
18 | useFrame(() => {
19 | stats.update();
20 | });
21 |
22 | return null;
23 | };
24 |
25 | export default Stats;
26 |
--------------------------------------------------------------------------------
/src/components/Surf/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from "react";
2 | import { useFrame } from "react-three-fiber";
3 |
4 | import Branch from "components/Branch";
5 | import CameraOperator from "components/CameraOperator";
6 | import Debris from "components/Debris";
7 | import FlatText from "components/FlatText";
8 | import { useConfig } from "utils/config";
9 |
10 | import useBranches from "./useBranches";
11 |
12 | export const SurfContext = createContext({ currentCommit: -1 });
13 |
14 | const Surf = ({ owner, repo, commits }) => {
15 | const branches = useBranches(commits);
16 |
17 | const [currentCommit, setCurrentCommit] = useState(0);
18 | const [isEnded, setIsEnded] = useState(false);
19 | const speed = useConfig(c => c.speed);
20 | const waitOnFirstCommit = useConfig(c => c.waitOnFirstCommit);
21 |
22 | useFrame(({ clock }) => {
23 | const ti = Math.max(
24 | 0,
25 | Math.round((clock.elapsedTime - waitOnFirstCommit) * speed) - 1
26 | );
27 | const index = Math.min(ti, commits.length - 1);
28 |
29 | if (currentCommit < index) {
30 | setCurrentCommit(index);
31 | }
32 |
33 | if (!isEnded && ti > commits.length + 2) {
34 | setIsEnded(true);
35 | }
36 | });
37 |
38 | return (
39 |
40 | {branches.map((b, i) => (
41 |
50 | ))}
51 |
52 | {commits.length && }
53 |
54 | {/* Repo name at the end of the experience */}
55 | {commits.length && (
56 |
66 | {repo}
67 | {`@${owner}`}
68 |
69 | )}
70 |
71 | {commits.length && }
72 |
73 | );
74 | };
75 |
76 | export default Surf;
77 |
--------------------------------------------------------------------------------
/src/components/Surf/useBranches.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 | import { Vector3 } from "three";
3 |
4 | import { useConfig } from "utils/config";
5 | import getBranchColor from "utils/getBranchColor";
6 |
7 | const branchIndex2Position = index => {
8 | let position = Math.ceil(index / 2);
9 | if (index % 2 === 0) {
10 | position *= -1;
11 | }
12 |
13 | return position;
14 | };
15 |
16 | const getAvailableIndex = (currentBranch, branches) => {
17 | let positionIndex = -1;
18 | let position = null;
19 |
20 | do {
21 | positionIndex += 1;
22 |
23 | position = branchIndex2Position(positionIndex);
24 | console.log("Checking for position", position);
25 | } while (
26 | branches.find(
27 | // eslint-disable-next-line no-loop-func
28 | b => {
29 | const found =
30 | b.position === position &&
31 | ((b.range[0] > currentBranch.range[0] &&
32 | b.range[1] < currentBranch.range[1]) ||
33 | (b.range[0] < currentBranch.range[0] &&
34 | b.range[1] > currentBranch.range[0]) ||
35 | (b.range[0] < currentBranch.range[1] &&
36 | b.range[1] > currentBranch.range[1]));
37 |
38 | if (found) {
39 | console.log(
40 | ` Position ${position} not available: [${currentBranch.range[0]}, ${
41 | currentBranch.range[1]
42 | }] X [${b.range[0]}, ${b.range[1]}]`
43 | );
44 | }
45 |
46 | return found;
47 | }
48 | )
49 | );
50 |
51 | console.log(`%cPosition ${position} available`, "background: yellow;");
52 |
53 | return positionIndex;
54 | };
55 |
56 | const useBranches = commits => {
57 | const commitsDistance = useConfig(c => c.commitsDistance);
58 | const branchesDistance = useConfig(c => c.branchesDistance);
59 |
60 | return useMemo(() => {
61 | const pendingBranches = commits.length
62 | ? commits[0].parents.map((_, i) => [commits[0].sha, i])
63 | : [];
64 | let branches = [];
65 | let currentBranch = {
66 | name: null,
67 | color: null,
68 | commits: [],
69 | skipFirstCommit: false,
70 | skipLastCommit: false,
71 | index: null,
72 | position: null,
73 | range: [Infinity, 0]
74 | };
75 | const evaluatedCommits = [];
76 |
77 | while (pendingBranches.length) {
78 | const [sha, pathIndex] = pendingBranches.shift();
79 | const head = commits.find(c => c.sha === sha);
80 |
81 | if (head.parents.length <= pathIndex) {
82 | continue;
83 | }
84 |
85 | // that's the head/merge-commit
86 | currentBranch.commits.push(head);
87 |
88 | // if this is not the first branch
89 | if (branches.length) {
90 | currentBranch.skipLastCommit = true;
91 | }
92 |
93 | // update range
94 | if (currentBranch.range[0] > head.index) {
95 | currentBranch.range[0] = head.index;
96 | }
97 | if (currentBranch.range[1] < head.index) {
98 | currentBranch.range[1] = head.index;
99 | }
100 |
101 | let commit = commits.find(c => c.sha === head.parents[pathIndex]);
102 |
103 | if (!commit) {
104 | // commit was not found
105 | currentBranch.commits = [];
106 | continue;
107 | }
108 |
109 | currentBranch.name = `${sha}--${pathIndex}`;
110 |
111 | console.groupCollapsed(`Branch ${currentBranch.name}`);
112 | console.log("head", head);
113 | console.groupCollapsed("Commits");
114 |
115 | while (commit && evaluatedCommits.indexOf(commit.sha) < 0) {
116 | currentBranch.commits.push(commit);
117 | evaluatedCommits.push(commit.sha);
118 |
119 | // update range
120 | if (currentBranch.range[0] > commit.index) {
121 | currentBranch.range[0] = commit.index;
122 | }
123 | if (currentBranch.range[1] < commit.index) {
124 | currentBranch.range[1] = commit.index;
125 | }
126 |
127 | console.log(`${commit.sha} #${commit.index}`, commit);
128 |
129 | if (commit.parents.length) {
130 | const nextCommitSha = commit.parents[0];
131 |
132 | for (let i = 1; i < commit.parents.length; i++) {
133 | // console.log(`%cFound new branch "${commit.sha}"`, "color: gold;");
134 | pendingBranches.push([commit.sha, i]);
135 | }
136 |
137 | commit = commits.find(c => c.sha === nextCommitSha);
138 | } else {
139 | commit = null;
140 | }
141 | }
142 |
143 | // Add fork commit if needed
144 | if (commit) {
145 | currentBranch.commits.push(commit);
146 |
147 | currentBranch.skipFirstCommit = true;
148 |
149 | // update range
150 | if (currentBranch.range[0] > commit.index) {
151 | currentBranch.range[0] = commit.index;
152 | }
153 | if (currentBranch.range[1] < commit.index) {
154 | currentBranch.range[1] = commit.index;
155 | }
156 |
157 | if (branches.length && currentBranch.commits.length <= 2) {
158 | // this branch has only fork and merge commits
159 | // set index as the fork commit branch index
160 |
161 | console.log("This branch has only fork and merge commits");
162 | console.log("Searching for fork branch index...");
163 |
164 | const forkBranch = branches.find(b => {
165 | const i = b.commits.findIndex(c => c.sha === commit.sha);
166 |
167 | return b.index ? i > 0 && i < b.commits.length - 1 : i > -1;
168 | });
169 |
170 | if (forkBranch) {
171 | console.log(`... "${forkBranch.name}" found`, forkBranch);
172 | currentBranch.index = forkBranch.index;
173 | }
174 | }
175 | }
176 | console.groupEnd("Commits");
177 |
178 | if (currentBranch.index === null) {
179 | currentBranch.index = getAvailableIndex(currentBranch, branches);
180 | }
181 |
182 | currentBranch.position = branchIndex2Position(currentBranch.index);
183 | currentBranch.color = getBranchColor(currentBranch.index);
184 |
185 | console.log(
186 | `%ccolor ${currentBranch.color}`,
187 | `background-color: ${currentBranch.color}`
188 | );
189 | console.log("range", currentBranch.range);
190 | console.log("position", currentBranch.position);
191 |
192 | // eslint-disable-next-line no-loop-func
193 | currentBranch.commits.forEach(c => {
194 | if (c.position) {
195 | return;
196 | }
197 |
198 | c.position = new Vector3(
199 | currentBranch.position * branchesDistance,
200 | 0,
201 | c.index * commitsDistance
202 | );
203 | });
204 |
205 | branches.push(currentBranch);
206 | console.groupEnd(`Branch ${currentBranch.name}`);
207 |
208 | currentBranch = {
209 | name: null,
210 | color: null,
211 | commits: [],
212 | skipFirstCommit: false,
213 | skipLastCommit: false,
214 | index: null,
215 | position: null,
216 | range: [Infinity, 0]
217 | };
218 | }
219 |
220 | return branches;
221 | }, [commits, branchesDistance, commitsDistance]);
222 | };
223 |
224 | export default useBranches;
225 |
--------------------------------------------------------------------------------
/src/components/Track/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from "react";
2 | import { useFrame } from "react-three-fiber";
3 |
4 | import { useConfig } from "utils/config";
5 |
6 | const Track = () => {
7 | const ref = useRef();
8 | const [isActive, setIsActive] = useState(false);
9 |
10 | const speed = useConfig(c => c.speed);
11 | const waitOnFirstCommit = useConfig(c => c.waitOnFirstCommit);
12 | const commitsDistance = useConfig(c => c.commitsDistance);
13 | const { width, color, emissiveIntensity } = useConfig(c => c.track);
14 |
15 | useEffect(() => {
16 | ref.current.rotation.x = -Math.PI / 2;
17 | }, []);
18 |
19 | useFrame(({ clock }) => {
20 | // update scale
21 | ref.current.scale.y = Math.max(
22 | (clock.elapsedTime - waitOnFirstCommit) * speed - 1,
23 | 0.01
24 | );
25 | // realign to 0
26 | ref.current.position.z = (ref.current.scale.y * commitsDistance) / 2;
27 |
28 | if (!isActive && ref.current.scale.y > 0.01) {
29 | setIsActive(true);
30 | }
31 | });
32 |
33 | return (
34 |
35 |
36 | 0.01 ? 1 : 0}
42 | />
43 |
44 | );
45 | };
46 |
47 | export default Track;
48 |
--------------------------------------------------------------------------------
/src/globals.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 |
3 | window.THREE = THREE;
4 |
--------------------------------------------------------------------------------
/src/hooks/useRepo.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | import loadingManager, { RESOURCE_TYPE_FETCH } from "utils/loadingManager";
4 |
5 | const getRepoParamsFromPath = () => {
6 | const parts = window.location.pathname.replace(/^\/+|\/+$/g, "").split("/");
7 | if (parts.length < 2) {
8 | return {
9 | owner: undefined,
10 | repo: undefined
11 | };
12 | }
13 |
14 | return {
15 | owner: parts[0],
16 | repo: parts[1]
17 | };
18 | };
19 |
20 | const useRepo = () => {
21 | const [commits, setCommits] = useState([]);
22 |
23 | const { owner = "splact", repo = "morbido" } = getRepoParamsFromPath();
24 |
25 | useEffect(() => {
26 | async function fetch() {
27 | const fetchedCommitsResource = loadingManager.registerResource(
28 | `${process.env.REACT_APP_API_BASE_URL}/github/${owner}/${repo}`,
29 | RESOURCE_TYPE_FETCH
30 | );
31 |
32 | // get response as soon as available
33 | const fetchedCommits = await fetchedCommitsResource.get();
34 | // commits = commits.slice(41, 60);
35 | // commits.forEach((c, i) => (c.index = commits.length - i - 1));
36 |
37 | setCommits(fetchedCommits);
38 | }
39 |
40 | if (owner !== "" && repo !== "") {
41 | fetch();
42 | }
43 | }, [owner, repo]);
44 |
45 | return { owner, repo, commits };
46 | };
47 |
48 | export default useRepo;
49 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import "./globals";
5 | import * as serviceWorker from "./serviceWorker";
6 |
7 | import App from "components/App";
8 | import "styles/index.scss";
9 |
10 | ReactDOM.render(, document.getElementById("root"));
11 |
12 | // If you want your app to work offline and load faster, you can change
13 | // unregister() to register() below. Note this comes with some pitfalls.
14 | // Learn more about service workers: https://bit.ly/CRA-PWA
15 | serviceWorker.unregister();
16 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === "localhost" ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === "[::1]" ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener("load", () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | "This web app is being served cache-first by a service " +
46 | "worker. To learn more, visit https://bit.ly/CRA-PWA"
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === "installed") {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | "New content is available and will be used when all " +
74 | "tabs for this page are closed. See https://bit.ly/CRA-PWA."
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log("Content is cached for offline use.");
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error("Error during service worker registration:", error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get("content-type");
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf("javascript") === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | "No internet connection found. App is running in offline mode."
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ("serviceWorker" in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/styles/base.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | width: 100%;
5 | height: 100%;
6 | }
7 |
8 | body {
9 | background: $c--vulcan;
10 | color: $c--tuna;
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/breakpoints.scss:
--------------------------------------------------------------------------------
1 | $b--md: 414px;
2 | $b--lg: 1024px;
3 |
4 | @mixin media($breakpoint) {
5 | @media (min-width: #{$breakpoint}) {
6 | @content;
7 | }
8 | }
9 |
10 | @mixin media-md {
11 | @include media($b--md) {
12 | @content;
13 | }
14 | }
15 | @mixin media-lg {
16 | @include media($b--lg) {
17 | @content;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/colors.scss:
--------------------------------------------------------------------------------
1 | $c--mantis: #8acb67;
2 | $c--oxford-blue: #3f455f;
3 | $c--tuna: #353747;
4 | $c--vulcan: #0c0e16;
5 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | /* import variables */
2 | @import "colors";
3 |
4 | /* normalize */
5 | @import "normalize";
6 |
7 | /* global styles */
8 | @import "breakpoints";
9 | @import "base";
10 | @import "typography";
11 |
--------------------------------------------------------------------------------
/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | -webkit-font-smoothing: antialiased;
4 | -moz-osx-font-smoothing: grayscale;
5 | }
6 |
7 | html,
8 | body {
9 | padding: 0;
10 | margin: 0;
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | font-size: 62.5%;
16 | }
17 |
18 | body {
19 | font-size: 1.6rem;
20 | }
21 |
22 | ul,
23 | ol {
24 | margin: 0;
25 | padding: 0;
26 |
27 | list-style: none;
28 | }
29 |
30 | button,
31 | input {
32 | border: none;
33 | outline: none;
34 | background: none;
35 | }
36 |
37 | button {
38 | color: inherit;
39 | font-size: inherit;
40 |
41 | cursor: pointer;
42 | }
43 |
44 | figure {
45 | display: block;
46 | margin: 0;
47 | }
48 |
49 | img {
50 | max-width: 100%;
51 | object-fit: cover;
52 | }
53 |
--------------------------------------------------------------------------------
/src/styles/typography.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=PT+Mono&display=swap");
2 |
3 | body {
4 | font-family: "PT Mono", monospace;
5 | }
6 |
--------------------------------------------------------------------------------
/src/styles/z.scss:
--------------------------------------------------------------------------------
1 | $z--blinking-message: 100;
2 | $z--loading-screen: 200;
3 | $z--hud: 300;
4 |
--------------------------------------------------------------------------------
/src/utils/clamp.js:
--------------------------------------------------------------------------------
1 | const clamp = (v, { min, max }) => {
2 | let result = v;
3 |
4 | if (min !== undefined && result < min) {
5 | v = min;
6 | } else if (max !== undefined && result > max) {
7 | v = max;
8 | }
9 |
10 | return result;
11 | };
12 |
13 | export default clamp;
14 |
--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDatGui, {
3 | DatFolder,
4 | DatNumber,
5 | DatColor,
6 | DatBoolean
7 | } from "react-dat-gui";
8 | import "react-dat-gui/dist/index.css";
9 | import create from "zustand";
10 |
11 | export const [useConfig] = create(set => ({
12 | commit: {
13 | radius: 5,
14 | color: "#8ACB67",
15 | emissiveIntensity: 1
16 | },
17 | track: {
18 | width: 6,
19 | color: "#8ACB67",
20 | emissiveIntensity: 1
21 | },
22 | camera: {
23 | position: {
24 | x: 0,
25 | y: 25,
26 | z: -150
27 | },
28 | distanceFromHead: 150,
29 | near: 0.01,
30 | far: 1000,
31 | fov: 70,
32 | xVariation: 10,
33 | yVariation: 20,
34 | variationDuration: 15
35 | },
36 | speed: 0.8,
37 | waitOnFirstCommit: 1.5,
38 | commitsDistance: 75,
39 | branchesDistance: 20,
40 | isOrbitControlsEnabled: false,
41 | set
42 | }));
43 |
44 | export const DatGui = () => {
45 | const data = useConfig();
46 | const handleUpdate = useConfig(config => config.set);
47 |
48 | return (
49 |
50 |
57 |
64 |
71 |
78 |
79 |
80 |
81 |
88 |
95 |
102 |
109 |
116 |
123 |
130 |
131 |
132 |
133 |
140 |
141 |
148 |
149 |
150 |
151 |
158 |
159 |
166 |
167 |
168 | );
169 | };
170 |
--------------------------------------------------------------------------------
/src/utils/delay.js:
--------------------------------------------------------------------------------
1 | const delay = time => new Promise(resolve => setTimeout(resolve, time));
2 |
3 | export default delay;
4 |
--------------------------------------------------------------------------------
/src/utils/getBranchColor.js:
--------------------------------------------------------------------------------
1 | import hsb2rgb from "./hsb2rgb";
2 | import rgb2hex from "./rgb2hex";
3 | const COLOR_STEP = 0.2222;
4 | const BASE_COLOR = { h: 0.275766017, s: 0.49, b: 0.8 }; // #8ACB67
5 |
6 | const getBranchColor = index => {
7 | let h = BASE_COLOR.h + COLOR_STEP * index;
8 | let s = BASE_COLOR.s;
9 | const b = BASE_COLOR.b;
10 |
11 | while (h > 1) {
12 | h -= 1;
13 | s -= 0.1;
14 | }
15 |
16 | while (s < 0) {
17 | s += 1;
18 | }
19 |
20 | const rgb = hsb2rgb(h, s, b);
21 | return rgb2hex(rgb.r, rgb.g, rgb.b);
22 | };
23 |
24 | export default getBranchColor;
25 |
--------------------------------------------------------------------------------
/src/utils/hsb2rgb.js:
--------------------------------------------------------------------------------
1 | const hsb2rgb = (h0, s, v) => {
2 | let h = h0 * 360;
3 | var R, G, B, X, C;
4 | h = (h % 360) / 60;
5 | C = v * s;
6 | X = C * (1 - Math.abs((h % 2) - 1));
7 | R = G = B = v - C;
8 |
9 | h = ~~h;
10 | R += [C, X, 0, 0, X, C][h];
11 | G += [X, C, C, X, 0, 0][h];
12 | B += [0, 0, X, C, C, X][h];
13 |
14 | return { r: R, g: G, b: B };
15 | };
16 |
17 | export default hsb2rgb;
18 |
--------------------------------------------------------------------------------
/src/utils/isEnvironment.js:
--------------------------------------------------------------------------------
1 | const urlParams = new URLSearchParams(window.location.search);
2 |
3 | let env = urlParams.get("env");
4 |
5 | if (!env || ["development", "test", "production"].indexOf(env) === -1) {
6 | env = process.env.NODE_ENV;
7 | }
8 |
9 | export const isDevelopment = env === "development";
10 | export const isTest = env === "test";
11 | export const isProduction = env === "production";
12 |
--------------------------------------------------------------------------------
/src/utils/isInView.js:
--------------------------------------------------------------------------------
1 | import { Frustum, Matrix4 } from "three";
2 |
3 | const frustum = new Frustum();
4 | const cameraViewProjectionMatrix = new Matrix4();
5 |
6 | const isInView = (object, camera) => {
7 | // make sure the camera matrix is updated
8 | camera.updateMatrixWorld();
9 | camera.matrixWorldInverse.getInverse(camera.matrixWorld);
10 |
11 | // evaluate camera/view projection matrix
12 | cameraViewProjectionMatrix.multiplyMatrices(
13 | camera.projectionMatrix,
14 | camera.matrixWorldInverse
15 | );
16 |
17 | // evaluate frustum
18 | frustum.setFromMatrix(cameraViewProjectionMatrix);
19 |
20 | return object.type === "Group"
21 | ? frustum.containsPoint(object.position)
22 | : frustum.intersectsObject(object);
23 | };
24 |
25 | export default isInView;
26 |
--------------------------------------------------------------------------------
/src/utils/loadingManager/FetchResource.js:
--------------------------------------------------------------------------------
1 | import clamp from "utils/clamp";
2 | import Resource from "./Resource";
3 |
4 | export default class FetchResource extends Resource {
5 | load(onProgress) {
6 | const url = this.request;
7 |
8 | const xhr = new XMLHttpRequest();
9 | xhr.addEventListener("load", e => {
10 | const json = JSON.parse(xhr.response);
11 |
12 | this.setResponse(json);
13 | onProgress();
14 | });
15 | xhr.addEventListener("progress", ({ loaded, total }) => {
16 | this.progress = !total ? 1 : clamp(loaded / total, { min: 0, max: 1 });
17 | console.log("progress fetch", this.progress);
18 | onProgress();
19 | });
20 | xhr.addEventListener("error", e => {
21 | console.log("Error fetching url.", { url }, e);
22 | throw new Error(e);
23 | });
24 | xhr.addEventListener("abort", e => {
25 | console.log("Fetching url has been aborted.", { url }, e);
26 | throw new Error(e);
27 | });
28 | xhr.open("GET", url);
29 | xhr.send();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/loadingManager/FontResource.js:
--------------------------------------------------------------------------------
1 | import loadFont from "load-bmfont";
2 | import { TextureLoader } from "three";
3 |
4 | import clamp from "utils/clamp";
5 | import Resource from "./Resource";
6 |
7 | let textureLoader;
8 |
9 | export default class FontResource extends Resource {
10 | constructor(...args) {
11 | super(...args);
12 |
13 | if (!textureLoader) {
14 | textureLoader = new TextureLoader(this.manager);
15 | }
16 | this.textureLoader = textureLoader;
17 | }
18 | load(onProgress) {
19 | const { font, image } = this.request;
20 |
21 | loadFont(font, (e, definition) => {
22 | if (e) {
23 | console.log("Error loading font.", { font }, e);
24 | throw new Error(e);
25 | }
26 |
27 | this.textureLoader.load(
28 | image,
29 | atlas => {
30 | this.setResponse({ definition, atlas });
31 | onProgress();
32 | },
33 | ({ loaded, total }) => {
34 | this.progress = !total
35 | ? 1
36 | : clamp(loaded / total, { min: 0, max: 1 });
37 | console.log("progress font", this.progress);
38 |
39 | onProgress();
40 | },
41 | e => {
42 | console.log("Error loading font atlas.", { font, image }, e);
43 | throw new Error(e);
44 | }
45 | );
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/loadingManager/LoadingManager.js:
--------------------------------------------------------------------------------
1 | import { LoadingManager as THREELoadingManager } from "three";
2 |
3 | import clamp from "utils/clamp";
4 |
5 | import FontResource from "./FontResource";
6 | import FetchResource from "./FetchResource";
7 |
8 | export const RESOURCE_TYPE_FONT = 0;
9 | export const RESOURCE_TYPE_FETCH = 1;
10 |
11 | export default class LoadingManager {
12 | constructor(options) {
13 | const { onStart, onProgress, onLoad, onError } = options || {};
14 |
15 | this.manager = new THREELoadingManager();
16 | this.resources = [];
17 |
18 | this.handleStart = onStart || (f => f);
19 | this.handleProgress = onProgress || (f => f);
20 | this.handleLoad = onLoad || (f => f);
21 | this.handleError = onError || (f => f);
22 |
23 | this.manager.onStart = this.handleStart;
24 | this.manager.onLoad = this.handleLoad;
25 | this.manager.onError = this.handleError;
26 | }
27 |
28 | init(options) {
29 | const { onStart, onProgress, onLoad, onError } = options || {};
30 |
31 | this.handleStart = onStart || (f => f);
32 | this.handleProgress = onProgress || (f => f);
33 | this.handleLoad = onLoad || (f => f);
34 | this.handleError = onError || (f => f);
35 |
36 | this.manager.onStart = this.handleStart;
37 | this.manager.onLoad = this.handleLoad;
38 | this.manager.onError = this.handleError;
39 | }
40 |
41 | registerResource = (data, type) => {
42 | let res;
43 |
44 | switch (type) {
45 | case RESOURCE_TYPE_FONT:
46 | res = new FontResource(data, { manager: this.manager });
47 | break;
48 | case RESOURCE_TYPE_FETCH:
49 | res = new FetchResource(data, { manager: this.manager });
50 | break;
51 | default:
52 | return;
53 | }
54 |
55 | this.resources.push(res);
56 |
57 | return res;
58 | };
59 |
60 | progress = () => {
61 | const progress = clamp(
62 | this.resources.length
63 | ? this.resources.reduce((sum, resource) => sum + resource.progress, 0) /
64 | this.resources.length
65 | : 1,
66 | {
67 | min: 0,
68 | max: 1
69 | }
70 | );
71 |
72 | this.handleProgress(progress);
73 | };
74 |
75 | load() {
76 | console.log(
77 | `loading ${this.resources.length} resources...`,
78 | this.resources
79 | );
80 |
81 | this.resources.forEach(resource => {
82 | resource.load(this.progress);
83 | });
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/utils/loadingManager/Resource.js:
--------------------------------------------------------------------------------
1 | export default class Resource {
2 | constructor(data, { manager }) {
3 | this.request = data;
4 | this.progress = 0;
5 | this.response = {};
6 | this.manager = manager;
7 | this.isLoaded = false;
8 | this.onLoaded = f => f;
9 | }
10 |
11 | setResponse(response) {
12 | this.response = response;
13 | this.isLoaded = true;
14 | this.progress = 1;
15 |
16 | this.onLoaded(this.response);
17 | }
18 |
19 | async get() {
20 | if (!this.isLoaded) {
21 | return new Promise(resolve => {
22 | this.onLoaded = resolve;
23 | });
24 | }
25 |
26 | return this.response;
27 | }
28 |
29 | load() {}
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/loadingManager/index.js:
--------------------------------------------------------------------------------
1 | import { useState, useCallback, useEffect } from "react";
2 |
3 | import LoadingManager from "./LoadingManager";
4 | export { RESOURCE_TYPE_FETCH, RESOURCE_TYPE_FONT } from "./LoadingManager";
5 |
6 | const loadingManager = new LoadingManager();
7 |
8 | export const useLoadingState = () => {
9 | const [progress, setProgress] = useState(0);
10 | const [error, setError] = useState(false);
11 |
12 | const handleStart = useCallback(() => {
13 | console.log("Start loading...");
14 | }, []);
15 | const handleProgress = useCallback(
16 | progress => {
17 | setProgress(progress);
18 | },
19 | [setProgress]
20 | );
21 | const handleLoad = useCallback(() => {
22 | console.log("Loading complete! 👌");
23 | setProgress(1);
24 | }, [setProgress]);
25 | const handleError = useCallback(
26 | url => {
27 | console.log(`Error loading "${url}" 👌`);
28 | setError(true);
29 | },
30 | [setError]
31 | );
32 |
33 | useEffect(() => {
34 | loadingManager.init({
35 | onStart: handleStart,
36 | onProgress: handleProgress,
37 | onLoad: handleLoad,
38 | onError: handleError
39 | });
40 | }, [handleStart, handleProgress, handleLoad, handleError]);
41 |
42 | return { progress, error };
43 | };
44 |
45 | export default loadingManager;
46 |
--------------------------------------------------------------------------------
/src/utils/rgb2hex.js:
--------------------------------------------------------------------------------
1 | const round = x => (x + 0.5) | 0;
2 |
3 | const rgb2hex = (r, g, b) =>
4 | "#" +
5 | (16777216 | round(b * 255) | (round(g * 255) << 8) | (round(r * 255) << 16))
6 | .toString(16)
7 | .slice(1);
8 |
9 | export default rgb2hex;
10 |
--------------------------------------------------------------------------------