Browse Source

desktop version production ready, need to add CV

master
gbrochar 2 years ago
parent
commit
a5c7f120a8
24 changed files with 320 additions and 61 deletions
  1. +0
    -6
      nodemon.json
  2. +31
    -3
      package-lock.json
  3. +4
    -1
      package.json
  4. +39
    -0
      public/css/style.css
  5. BIN
      public/images/1.jpg
  6. BIN
      public/images/10.jpg
  7. BIN
      public/images/11.jpg
  8. BIN
      public/images/12.jpg
  9. BIN
      public/images/2.jpg
  10. BIN
      public/images/3.jpg
  11. BIN
      public/images/4.jpg
  12. BIN
      public/images/5.jpg
  13. BIN
      public/images/6.jpg
  14. BIN
      public/images/7.jpg
  15. BIN
      public/images/8.jpg
  16. BIN
      public/images/9.jpg
  17. BIN
      public/images/article.jpg
  18. BIN
      public/images/carte.png
  19. +7
    -4
      src/client/components/ClickMe.tsx
  20. +5
    -3
      src/client/components/DownloadCV.tsx
  21. +46
    -0
      src/client/components/Player.tsx
  22. +185
    -39
      src/client/components/Root.tsx
  23. +3
    -3
      src/server/main.ts
  24. +0
    -2
      tsconfig.json

+ 0
- 6
nodemon.json View File

@@ -1,6 +0,0 @@
{
"watch": ["src/server"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node ./src/server/main.ts"
}

+ 31
- 3
package-lock.json View File

@@ -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",


+ 4
- 1
package.json View File

@@ -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"
}
}

+ 39
- 0
public/css/style.css View File

@@ -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;}
}

BIN
public/images/1.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.4 MiB Width: 1920  |  Height: 1080  |  Size: 544 KiB

BIN
public/images/10.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.6 MiB Width: 1920  |  Height: 1080  |  Size: 781 KiB

BIN
public/images/11.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.4 MiB Width: 1920  |  Height: 1080  |  Size: 778 KiB

BIN
public/images/12.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.2 MiB Width: 1920  |  Height: 1080  |  Size: 724 KiB

BIN
public/images/2.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.3 MiB Width: 1920  |  Height: 1080  |  Size: 700 KiB

BIN
public/images/3.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.3 MiB Width: 1920  |  Height: 1080  |  Size: 735 KiB

BIN
public/images/4.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.6 MiB Width: 1920  |  Height: 1080  |  Size: 874 KiB

BIN
public/images/5.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 2.8 MiB Width: 1920  |  Height: 1080  |  Size: 850 KiB

BIN
public/images/6.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 3.3 MiB Width: 1920  |  Height: 1080  |  Size: 1012 KiB

BIN
public/images/7.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 1.8 MiB Width: 1920  |  Height: 1080  |  Size: 556 KiB

BIN
public/images/8.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 1.2 MiB Width: 1920  |  Height: 1080  |  Size: 366 KiB

BIN
public/images/9.jpg View File

Before After
Width: 3840  |  Height: 2160  |  Size: 1.4 MiB Width: 1920  |  Height: 1080  |  Size: 487 KiB

BIN
public/images/article.jpg View File

Before After
Width: 2336  |  Height: 1259  |  Size: 1.0 MiB

BIN
public/images/carte.png View File

Before After
Width: 1019  |  Height: 720  |  Size: 242 KiB

+ 7
- 4
src/client/components/ClickMe.tsx View File

@@ -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>
);
};
};

+ 5
- 3
src/client/components/DownloadCV.tsx View File

@@ -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>
);
};
};

+ 46
- 0
src/client/components/Player.tsx View File

@@ -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>
);
}

+ 185
- 39
src/client/components/Root.tsx View File

@@ -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
- 3
src/server/main.ts View File

@@ -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);

+ 0
- 2
tsconfig.json View File

@@ -16,8 +16,6 @@
"node_modules",
"config",
"views",
"build",
"dev",
"dist"
]
}

Loading…
Cancel
Save