@@ -1,6 +0,0 @@ | |||
{ | |||
"watch": ["src/server"], | |||
"ext": "ts,json", | |||
"ignore": ["src/**/*.spec.ts"], | |||
"exec": "ts-node ./src/server/main.ts" | |||
} |
@@ -1292,6 +1292,12 @@ | |||
"@types/range-parser": "*" | |||
} | |||
}, | |||
"@types/howler": { | |||
"version": "2.2.1", | |||
"resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.1.tgz", | |||
"integrity": "sha512-1MiSldngr+eAO4lDPtjzl4Nf2GmRh8VDHIpNBIkyd25L22JExVlI6w3fjSM7+FNc1e1WZAPNq7/flkw685byfg==", | |||
"dev": true | |||
}, | |||
"@types/json-schema": { | |||
"version": "7.0.6", | |||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", | |||
@@ -1347,6 +1353,16 @@ | |||
"@types/react": "*" | |||
} | |||
}, | |||
"@types/react-howler": { | |||
"version": "3.7.2", | |||
"resolved": "https://registry.npmjs.org/@types/react-howler/-/react-howler-3.7.2.tgz", | |||
"integrity": "sha512-2R2nypBTD8L1vHL7Sz5Pn7L5I0v5+UIp5y4ZnZ3r7uggqghVf1DLo/pJ9e7g2A+PWq/xZJXvzrF9ZQSdwrQyEQ==", | |||
"dev": true, | |||
"requires": { | |||
"@types/howler": "*", | |||
"@types/react": "*" | |||
} | |||
}, | |||
"@types/serve-static": { | |||
"version": "1.13.8", | |||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", | |||
@@ -3327,6 +3343,11 @@ | |||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", | |||
"dev": true | |||
}, | |||
"howler": { | |||
"version": "2.2.1", | |||
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.1.tgz", | |||
"integrity": "sha512-0iIXvuBO/81CcrQ/HSSweYmbT50fT2mIc9XMFb+kxIfk2pW/iKzDbX1n3fZmDXMEIpYvyyfrB+gXwPYSDqUxIQ==" | |||
}, | |||
"http-cache-semantics": { | |||
"version": "4.1.0", | |||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", | |||
@@ -4331,7 +4352,6 @@ | |||
"version": "15.7.2", | |||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", | |||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", | |||
"dev": true, | |||
"requires": { | |||
"loose-envify": "^1.4.0", | |||
"object-assign": "^4.1.1", | |||
@@ -4447,11 +4467,19 @@ | |||
"scheduler": "^0.20.1" | |||
} | |||
}, | |||
"react-howler": { | |||
"version": "5.0.0", | |||
"resolved": "https://registry.npmjs.org/react-howler/-/react-howler-5.0.0.tgz", | |||
"integrity": "sha512-0JJapjFv0rd7sAZ9l9TuKVN2m4aRN3/yQYzRKQpaoK0FXldzM6LuP2XN/QetlubAqHF5F3wrk29QF0gngj+gRQ==", | |||
"requires": { | |||
"howler": "^2.2.0", | |||
"prop-types": "^15.5.6" | |||
} | |||
}, | |||
"react-is": { | |||
"version": "16.13.1", | |||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | |||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", | |||
"dev": true | |||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" | |||
}, | |||
"read-pkg": { | |||
"version": "4.0.1", | |||
@@ -22,7 +22,7 @@ | |||
"dev:client:types": "tsc --emitDeclarationOnly --watch --preserveWatchOutput --pretty --project config/tsconfig.client.json", | |||
"dev:client:js": "webpack --config config/webpack.client.dev.js", | |||
"dev:client": "concurrently \"npm:dev:client:types\" \"npm:dev:client:js\"", | |||
"dev:start": "mkdir -p dev/server && touch dev/server/bundle.js && nodemon dev/server/bundle.js", | |||
"dev:start": "mkdir -p dist/server && touch dist/server/bundle.js && nodemon dist/server/bundle.js", | |||
"dev": "rm -rf dist && concurrently \"npm:dev:server\" \"npm:dev:client\" \"npm:dev:start\"", | |||
"start": "node dist/server/bundle.js" | |||
}, | |||
@@ -43,6 +43,7 @@ | |||
"@types/express": "^4.17.9", | |||
"@types/react": "^16.14.0", | |||
"@types/react-dom": "^16.9.9", | |||
"@types/react-howler": "^3.7.2", | |||
"@typescript-eslint/eslint-plugin": "^4.8.1", | |||
"@typescript-eslint/parser": "^4.8.1", | |||
"babel-loader": "^8.2.1", | |||
@@ -52,6 +53,7 @@ | |||
"eslint-config-google": "^0.14.0", | |||
"eslint-plugin-react": "^7.21.5", | |||
"eslint-webpack-plugin": "^2.4.0", | |||
"howler": "^2.2.1", | |||
"nodemon": "^2.0.6", | |||
"ts-node": "^9.0.0", | |||
"typescript": "^4.1.2", | |||
@@ -65,6 +67,7 @@ | |||
"express": "^4.17.1", | |||
"react": "^17.0.1", | |||
"react-dom": "^17.0.1", | |||
"react-howler": "^5.0.0", | |||
"shadertoy-react": "^1.1.1" | |||
} | |||
} |
@@ -13,6 +13,11 @@ body { | |||
overflow: hidden; | |||
} | |||
img { | |||
width: 100%; | |||
height: auto; | |||
} | |||
.shader { | |||
padding: 0; | |||
margin: 0; | |||
@@ -21,6 +26,7 @@ body { | |||
} | |||
.tile { | |||
background-color: #000000; | |||
display: inline-block; | |||
vertical-align: top; | |||
padding: 0; | |||
@@ -38,6 +44,28 @@ body { | |||
width: 100%; | |||
} | |||
.tile-player { | |||
animation: player 2s infinite; | |||
} | |||
.player-text { | |||
color: #ebdbb2; | |||
} | |||
.loader { | |||
border: 12px solid #ebdbb2; | |||
border-top: 12px solid #458588; | |||
border-radius: 50%; | |||
height: 120px; | |||
width: 120px; | |||
animation: spin 2s linear infinite; | |||
} | |||
@keyframes spin { | |||
0% { transform: rotate(0deg); } | |||
100% { transform: rotate(360deg); } | |||
} | |||
#whoami { | |||
background-color: #1d2021; | |||
color: #ebdbb2; | |||
@@ -118,4 +146,15 @@ body { | |||
71.42% {background-color: #d65d0e;} | |||
85.71% {background-color: #cc241d;} | |||
100% {background-color: #b16286;} | |||
} | |||
@keyframes player { | |||
0% {background-color: #b1628633;} | |||
14.28% {background-color: #45858833;} | |||
28.57% {background-color: #689d6a33;} | |||
42.85% {background-color: #98971a33;} | |||
57.14% {background-color: #d7992133;} | |||
71.42% {background-color: #d65d0e33;} | |||
85.71% {background-color: #cc241d33;} | |||
100% {background-color: #b1628633;} | |||
} |
@@ -3,12 +3,13 @@ import React, {useState, useEffect} from 'react'; | |||
interface ClickMeProps { | |||
windowSize: number; | |||
setTrigger: any; | |||
setBusy: any; | |||
} | |||
/** | |||
* @return {jsx} a presentation of me | |||
*/ | |||
export default function ClickMe({windowSize, setTrigger}: ClickMeProps) { | |||
export default function ClickMe({windowSize, setTrigger, setBusy}: ClickMeProps) { | |||
const [buttonClass, setButtonClass] = useState(''); | |||
useEffect(() => { | |||
@@ -29,11 +30,13 @@ export default function ClickMe({windowSize, setTrigger}: ClickMeProps) { | |||
id='clickme'> | |||
<button | |||
type='button' | |||
onClick={() => setTrigger(Math.random())} | |||
onClick={() => { | |||
setTrigger(Math.random()); | |||
setBusy(true); | |||
}} | |||
className={'d-inline btn btn-clickme ' + buttonClass}> | |||
Click me ! | |||
</button> | |||
<br /> | |||
</div> | |||
); | |||
}; | |||
}; |
@@ -27,11 +27,13 @@ export default function DownloadCV({windowSize}: DownloadCVProps) { | |||
justify-content-center' | |||
id='downloadcv'> | |||
<a | |||
href='https://git.gaetanbrochard.dev/gbrochar/expo-web' | |||
href='/static/pdfs/CV_Gaetan_Brochard_2020.pdf' | |||
download | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
className={'d-inline btn btn-mine ' + buttonClass}> | |||
Download my CV | |||
</a> | |||
<br /> | |||
</div> | |||
); | |||
}; | |||
}; |
@@ -0,0 +1,46 @@ | |||
import React, {useState} from 'react'; | |||
import ReactHowler from 'react-howler'; | |||
interface PlayerProps { | |||
src: string; | |||
} | |||
/** | |||
* @return {jsx} The root component | |||
*/ | |||
export default function Player({src}: PlayerProps) { | |||
const [play, setPlay] = useState(false); | |||
const [playText, setPlayText] = useState('Play'); | |||
function handlePlay() { | |||
setPlay(!play); | |||
if (play) { | |||
setPlayText('Play'); | |||
} else { | |||
setPlayText('Pause'); | |||
} | |||
} | |||
return ( | |||
<React.Fragment> | |||
<ReactHowler | |||
src={[src]} | |||
playing={play}/> | |||
<div | |||
className=' | |||
tile-child tile-player d-flex | |||
flex-column | |||
align-items-center | |||
justify-content-center' | |||
id='clickme'> | |||
<div className='lead player-text mb-4'>{src.split('/')[3].split('.')[0]}</div> | |||
<button | |||
type='button' | |||
onClick={() => handlePlay()} | |||
className={'d-inline btn btn-clickme btn-lg'}> | |||
{playText} | |||
</button> | |||
</div> | |||
</React.Fragment> | |||
); | |||
} |
@@ -4,6 +4,7 @@ import WhoAmI from './WhoAmI'; | |||
import Git from './Git'; | |||
import DownloadCV from './DownloadCV'; | |||
import ClickMe from './ClickMe'; | |||
import Player from './Player'; | |||
/** | |||
* @return {jsx} The root component | |||
@@ -12,7 +13,67 @@ export default function Root() { | |||
const [windowSize, setWindowSize] = useState(0); | |||
const [trigger, setTrigger] = useState(0); | |||
const [content, setContent]: any = useState([]); | |||
const [busy, setBusy]: any = useState(true); | |||
const loader: Array<any> = []; | |||
for (let i = 0; i < 5; i++) { | |||
loader.push( | |||
<div className='tile'> | |||
<div | |||
className=' | |||
tile-child | |||
d-flex | |||
flex-column | |||
align-items-center | |||
justify-content-center'> | |||
<div className='d-inline loader'></div> | |||
</div> | |||
</div>); | |||
} | |||
loader.push(<div className='tile'><WhoAmI /></div>); | |||
loader.push( | |||
<div className='tile'> | |||
<Git windowSize={windowSize} /> | |||
</div>); | |||
for (let i = 0; i < 2; i++) { | |||
loader.push( | |||
<div className='tile'> | |||
<div | |||
className=' | |||
tile-child | |||
d-flex | |||
flex-column | |||
align-items-center | |||
justify-content-center'> | |||
<div className='d-inline loader'></div> | |||
</div> | |||
</div>); | |||
} | |||
loader.push( | |||
<div className='tile'> | |||
<DownloadCV windowSize={windowSize} /> | |||
</div>); | |||
loader.push( | |||
<div className='tile'> | |||
<ClickMe | |||
windowSize={windowSize} | |||
setTrigger={setTrigger} | |||
setBusy={setBusy} /> | |||
</div>); | |||
for (let i = 0; i < 5; i++) { | |||
loader.push( | |||
<div className='tile'> | |||
<div | |||
className=' | |||
tile-child | |||
d-flex | |||
flex-column | |||
align-items-center | |||
justify-content-center'> | |||
<div className='d-inline loader'></div> | |||
</div> | |||
</div>); | |||
} | |||
const Shaders = [ | |||
'BaseRaymarching', | |||
'Collapse', | |||
@@ -24,6 +85,16 @@ export default function Root() { | |||
'BaseLattice', | |||
]; | |||
const Sounds = [ | |||
'Collapse.mp3', | |||
'Droplets.mp3', | |||
'Heavy Ropes.mp3', | |||
'Inception.mp3', | |||
'Menstom.mp3', | |||
'Speechless.mp3', | |||
'Triangle.mp3', | |||
]; | |||
const handleWindowResize = useCallback((_event) => { | |||
setWindowSize(window.innerWidth); | |||
}, []); | |||
@@ -35,52 +106,127 @@ export default function Root() { | |||
}, [handleWindowResize]); | |||
useEffect(() => { | |||
let newContent = []; | |||
let randomContent = []; | |||
let shadersRandom = []; | |||
let imagesRandom = []; | |||
const newContent = []; | |||
const randomContent = []; | |||
const shadersRandom: Array<any> = []; | |||
const imagesRandom: Array<any> = []; | |||
const soundsRandom: Array<any> = []; | |||
for (let i = 0; i < 3; i++) { | |||
shadersRandom.push(Math.floor(Math.random() * 8)); | |||
while (shadersRandom.indexOf( | |||
shadersRandom[shadersRandom.length - 1]) != | |||
shadersRandom.length - 1) { | |||
shadersRandom[shadersRandom.length - 1] += 1; | |||
if (shadersRandom[shadersRandom.length - 1] == 8) { | |||
shadersRandom[shadersRandom.length - 1] = 1; | |||
} | |||
} | |||
} | |||
for (let i = 0; i < 3; i++) { | |||
shadersRandom[i] = | |||
<div className='tile'> | |||
<Shader name={Shaders[shadersRandom[i]]} /> | |||
</div>; | |||
} | |||
for (let i = 0; i < 7; i++) { | |||
imagesRandom.push(Math.floor(Math.random() * 12) + 1); | |||
while (imagesRandom.indexOf( | |||
imagesRandom[imagesRandom.length - 1]) != | |||
imagesRandom.length - 1) { | |||
imagesRandom[imagesRandom.length - 1] += 1; | |||
if (imagesRandom[imagesRandom.length - 1] == 13) { | |||
imagesRandom[imagesRandom.length - 1] = 1; | |||
} | |||
} | |||
} | |||
for (let i = 0; i < 7; i++) { | |||
imagesRandom[i] = | |||
<div className='tile'> | |||
<img | |||
src={'/static/images/' + | |||
imagesRandom[i].toString() + | |||
'.jpg'}> | |||
</img> | |||
</div>; | |||
} | |||
for (let i = 0; i < 2; i++) { | |||
soundsRandom.push(Math.floor(Math.random() * 7)); | |||
} | |||
if (soundsRandom[0] == soundsRandom[1]) { | |||
soundsRandom[1] += 1; | |||
if (soundsRandom[1] == 7) { | |||
soundsRandom[1] = 0; | |||
} | |||
} | |||
soundsRandom[0] = Sounds[soundsRandom[0]]; | |||
soundsRandom[1] = Sounds[soundsRandom[1]]; | |||
for (let i = 0; i < soundsRandom.length; i++) { | |||
soundsRandom[i] = | |||
<div className='tile'> | |||
<Player src={'/static/sounds/' + soundsRandom[i]}/> | |||
</div>; | |||
} | |||
shadersRandom.push(Math.floor(Math.random() * 8)); | |||
shadersRandom.push(Math.floor(Math.random() * 8)); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[0]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[1]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[2]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[3]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[4]]} /></div>); | |||
while (shadersRandom.length || | |||
imagesRandom.length || | |||
soundsRandom.length) { | |||
const random = Math.floor(Math.random() * 4); | |||
if (random == 0) { | |||
if (shadersRandom.length > 0) { | |||
randomContent.push(shadersRandom[0]); | |||
shadersRandom.shift(); | |||
} | |||
} else if (random == 1) { | |||
if (soundsRandom.length > 0) { | |||
randomContent.push(soundsRandom[0]); | |||
soundsRandom.shift(); | |||
} | |||
} else { | |||
if (imagesRandom.length > 0) { | |||
randomContent.push(imagesRandom[0]); | |||
imagesRandom.shift(); | |||
} | |||
} | |||
} | |||
newContent.push(randomContent[0]); | |||
newContent.push(randomContent[1]); | |||
newContent.push(randomContent[2]); | |||
newContent.push(randomContent[3]); | |||
newContent.push(randomContent[4]); | |||
newContent.push(<div className='tile'><WhoAmI /></div>); | |||
newContent.push(<div className='tile'><Git windowSize={windowSize} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[5]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[6]]} /></div>); | |||
newContent.push(<div className='tile'><DownloadCV windowSize={windowSize} /></div>); | |||
newContent.push(<div className='tile'><ClickMe windowSize={windowSize} setTrigger={setTrigger} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[7]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[8]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[9]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[10]]} /></div>); | |||
newContent.push(<div className='tile'><Shader name={Shaders[shadersRandom[11]]} /></div>); | |||
newContent.push( | |||
<div className='tile'> | |||
<Git windowSize={windowSize} /> | |||
</div>); | |||
newContent.push(randomContent[5]); | |||
newContent.push(randomContent[6]); | |||
newContent.push( | |||
<div className='tile'> | |||
<DownloadCV windowSize={windowSize} /> | |||
</div>); | |||
newContent.push( | |||
<div className='tile'> | |||
<ClickMe | |||
windowSize={windowSize} | |||
setTrigger={setTrigger} | |||
setBusy={setBusy} /> | |||
</div>); | |||
newContent.push(randomContent[7]); | |||
newContent.push(randomContent[8]); | |||
newContent.push(randomContent[9]); | |||
newContent.push(randomContent[10]); | |||
newContent.push(randomContent[11]); | |||
setContent(newContent); | |||
setBusy(false); | |||
}, [trigger]); | |||
return ( | |||
<div id='root'> | |||
{content} | |||
{/* <div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><WhoAmI /></div> | |||
<div className='tile'><Git windowSize={windowSize} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><DownloadCV windowSize={windowSize} /></div> | |||
<div className='tile'><ClickMe windowSize={windowSize} setTrigger={setTrigger} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> | |||
<div className='tile'><Shader name={Shaders[0]} /></div> */} | |||
{busy ? loader : content} | |||
</div> | |||
); | |||
} |
@@ -3,13 +3,13 @@ import path from 'path'; | |||
const app = express(); | |||
app.set('view engine', 'ejs') | |||
app.set('view engine', 'ejs'); | |||
app.use('/static', express.static(path.join(__dirname, '../../public'))); | |||
app.use('/js', express.static(path.join(__dirname, '../../dist/client'))); | |||
app.get('/', (_req, res) => { | |||
res.render('index'); | |||
}) | |||
}); | |||
app.listen(3000); | |||
app.listen(3000); |
@@ -16,8 +16,6 @@ | |||
"node_modules", | |||
"config", | |||
"views", | |||
"build", | |||
"dev", | |||
"dist" | |||
] | |||
} |