├── .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 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 30 | 31 | 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 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {isDevelopment && } 70 | 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 | 21 | 22 | 23 | ))} 24 | 25 |
{hud[k]}{k}
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 | --------------------------------------------------------------------------------