Browse Source

feat: new style

main
nicolas.marsal 3 years ago
parent
commit
5a7337f378
No known key found for this signature in database
GPG Key ID: 268AB819B6453541
  1. 41
      src/App.vue
  2. 2
      src/components/common/LeftSection.vue
  3. 27
      src/components/common/RightSection.vue
  4. 72
      src/components/common/WordCloud.vue
  5. 91
      src/components/experiences/MyExperiences.vue
  6. 25
      src/components/skills/MySkills.vue
  7. 32
      src/components/skills/ProgressBar.vue
  8. 37
      src/data/experiences.ts
  9. 5
      src/data/skills.ts
  10. 1
      src/data/types.ts

41
src/App.vue

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import FirstNameAndLastName from "@/components/informations/FirstNameAndLastName.vue";
import MyInformations from "@/components/informations/MyInformations.vue"; import MyInformations from "@/components/informations/MyInformations.vue";
import MyEducation from "@/components/education/MyEducation.vue"; import MyEducation from "@/components/education/MyEducation.vue";
import MyLanguages from "@/components/informations/MyLanguages.vue"; import MyLanguages from "@/components/informations/MyLanguages.vue";
@ -20,7 +19,7 @@ import WordCloud from "@/components/common/WordCloud.vue";
width: 100%; width: 100%;
" "
> >
<WordCloud :width="290" :height="200"></WordCloud> <WordCloud :width="250" :height="200"></WordCloud>
</div> </div>
<MyInformations /> <MyInformations />
<MyEducation /> <MyEducation />
@ -35,39 +34,8 @@ import WordCloud from "@/components/common/WordCloud.vue";
</template> </template>
<script lang="ts"> <script lang="ts">
import {
Ansible,
Git,
Helm,
Javascript,
Jenkins,
Kubernetes,
Skaffold,
Typescript,
} from "@/data/skills";
export default { export default {
name: "app", name: "app",
methods: {
wordClickHandler(name, value, vm) {
console.log("wordClickHandler", name, value, vm);
},
},
data() {
return {
myColors: ["#1f77b4", "#629fc9", "#94bedb", "#c9e0ef"],
defaultWords: [
Kubernetes,
Skaffold,
Ansible,
Helm,
Javascript,
Typescript,
Git,
Jenkins,
].map((s) => ({ name: s.name, value: s.score })),
};
},
}; };
</script> </script>
<style scoped> <style scoped>
@ -79,10 +47,10 @@ export default {
} }
.left { .left {
margin: 8px; margin: 0;
width: 320px; width: 250px;
flex-shrink: 0; flex-shrink: 0;
border-right: 1px solid gray; border-right: 3px solid gray;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -90,7 +58,6 @@ export default {
.right { .right {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
margin: 8px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }

2
src/components/common/LeftSection.vue

@ -39,7 +39,7 @@ defineProps<{
.title::before, .title::before,
.title::after { .title::after {
content: ""; content: "";
border-top: 1px solid gray; border-top: 3px solid gray;
width: 100%; width: 100%;
} }

27
src/components/common/RightSection.vue

@ -21,10 +21,33 @@ defineProps<{
flex-direction: column; flex-direction: column;
} }
.title { .title {
font-size: 18px; font-size: 24px;
font-weight: bolder; font-weight: bolder;
font-variant: titling-caps; font-variant: small-caps;
text-transform: capitalize; text-transform: capitalize;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding-top: 8px;
padding-bottom: 8px;
padding-right: 8px;
text-align: center;
width: auto;
}
.title::before,
.title::after {
content: "";
border-top: 3px solid gray;
width: 100%;
}
.title::before {
margin-right: 12px;
}
.title::after {
margin-left: 12px;
} }
.body { .body {
flex: 1; flex: 1;

72
src/components/common/WordCloud.vue

@ -1,5 +1,11 @@
<template> <template>
<svg :width="width" :height="height"> <svg
:width="width"
:height="height"
@mousemove="onMousemove"
@mouseover="(e) => (this.hover = true)"
@mouseout="this.hover = false"
>
<g :transform="`translate(${width / 2}, ${height / 2})`"> <g :transform="`translate(${width / 2}, ${height / 2})`">
<text <text
v-for="word in words" v-for="word in words"
@ -7,10 +13,12 @@
:style="{ :style="{
fontSize: word.size + 'px', fontSize: word.size + 'px',
fontFamily: word.font, fontFamily: word.font,
cursor: 'pointer',
}" }"
:transform="`translate(${word.x}, ${word.y})rotate(${word.rotate})`" :transform="`translate(${word.x}, ${word.y})rotate(${word.rotate})`"
text-anchor="middle" text-anchor="middle"
@click="highlightText(word.text)" @click="highlightText(word.text)"
class="word"
> >
{{ word.text }} {{ word.text }}
</text> </text>
@ -18,6 +26,12 @@
</svg> </svg>
</template> </template>
<style scoped>
.word {
transition: 1s transform ease-in-out;
}
</style>
<script lang="ts"> <script lang="ts">
import cloud from "d3-cloud"; import cloud from "d3-cloud";
import * as skills from "@/data/skills"; import * as skills from "@/data/skills";
@ -30,24 +44,37 @@ export default defineComponent({
}, },
data() { data() {
return { return {
loading: false, animate: true,
hover: false,
words: [] as cloud.Word[], words: [] as cloud.Word[],
minFontSize: 8, minFontSize: 8,
maxFontSize: 40, maxFontSize: 40,
mouseX: undefined,
mouseY: undefined,
}; };
}, },
created() { created() {
this.computeWords(); this.computeWords();
// setInterval(() => {
// if (!this.animate || this.hover) {
// return;
// }
// this.computeWords();
// }, 1500);
}, },
methods: { methods: {
onMousemove(e: any) {
this.mouseX = e.clientX;
this.mouseY = e.clientY;
},
highlightText(text: string) { highlightText(text: string) {
console.log(text); console.log(text);
}, },
computeWords() { computeWords() {
this.loading = true; let words: (cloud.Word & { size: number; fixed?: boolean })[] =
let words: (cloud.Word & { size: number })[] = Object.keys(skills) Object.keys(skills)
.map((p) => skills[p]) .map((p) => skills[p])
.map((s) => ({ text: s.name, size: s.score })); .map((s) => ({ text: s.name, size: s.score }));
const min = words const min = words
.map((s) => s.size) .map((s) => s.size)
@ -55,11 +82,18 @@ export default defineComponent({
const max = words const max = words
.map((s) => s.size) .map((s) => s.size)
.reduce((prev, current) => Math.max(prev, current)); .reduce((prev, current) => Math.max(prev, current));
const scaleFontSize = (size: number) => { const scaleFontSize = ({
const number = size,
((size - min) / (max - min)) * (this.maxFontSize - this.minFontSize) + fixed,
this.minFontSize; }: {
return number; size: number;
fixed: boolean;
}) => {
return fixed
? size
: ((size - min) / (max - min)) *
(this.maxFontSize - this.minFontSize) +
this.minFontSize;
}; };
const randomRotate = (s: number) => { const randomRotate = (s: number) => {
const range = 120 * (1 - ((s - min) / (max - min)) * 0.7); const range = 120 * (1 - ((s - min) / (max - min)) * 0.7);
@ -67,7 +101,8 @@ export default defineComponent({
}; };
words = [ words = [
{ {
size: max * 1.6, size: 30,
fixed: true,
text: "Nicolas Marsal", text: "Nicolas Marsal",
rotate: -30, rotate: -30,
padding: 6, padding: 6,
@ -81,19 +116,22 @@ export default defineComponent({
.words(words) .words(words)
.padding((w) => w.padding ?? 1) .padding((w) => w.padding ?? 1)
.rotate((w) => w.rotate ?? randomRotate(w.size)) .rotate((w) => w.rotate ?? randomRotate(w.size))
.spiral("archimedean") .spiral("rectangular")
.fontSize((d) => scaleFontSize(d.size)) .fontSize((d) => scaleFontSize(d))
.on("end", (d: cloud.Word[]) => { .on("end", (d: cloud.Word[]) => {
console.log( console.log(
`d: ${d.length} - w: ${words.length} - f: ${this.maxFontSize}` `d: ${d.length} - w: ${words.length} - f: ${this.maxFontSize}`
); );
if (d.length < words.length) { if (d.length < words.length) {
console.log("On recommence !"); // console.log("On recommence !");
this.maxFontSize = this.maxFontSize - 2; this.maxFontSize = this.maxFontSize - 2;
this.computeWords(); if (this.maxFontSize > 2 * this.minFontSize) {
this.computeWords();
} else {
this.words = d;
}
} else { } else {
this.words = d; this.words = d;
this.loading = false;
} }
}) })
.start(); .start();

91
src/components/experiences/MyExperiences.vue

@ -5,16 +5,31 @@ import RightSection from "@/components/common/RightSection.vue";
<template> <template>
<RightSection :title="$t('section.experiences')"> <RightSection :title="$t('section.experiences')">
<div class="experiences"> <div class="experiences">
<div class="experience" v-for="experience in experiences"> <div
<div class="header">{{ experience.company }}</div> class="experience"
<div class="body-wrapper"> v-for="experience in experiences"
<div class="border"> :key="experience.company"
<div class="end">{{ experience.endAt }}</div> >
<div style="flex-grow: 1"> </div> <div class="header">
<div class="start">{{ experience.startAt }}</div> <div class="role">{{ experience.role }}</div>
<div class="company">
{{ experience.company }}
<div class="date">
{{ experience.startAt }} - {{ experience.endAt }}
</div>
</div> </div>
<div class="body"> </div>
<div v-for="mission in experience.missions"> <div class="missions">
<div
class="mission"
v-for="mission in experience.missions"
:key="mission.description"
style="display: flex; flex-direction: row; align-items: center"
>
<div style="flex: 1 1; font-size: 12px">
{{ mission.skills.map((s) => s.name).join(", ") }}
</div>
<div style="flex: 3 1">
{{ mission.description }} {{ mission.description }}
</div> </div>
</div> </div>
@ -39,50 +54,36 @@ export default defineComponent({
</script> </script>
<style scoped> <style scoped>
.experiences { .experience {
padding-top: 14px; margin-left: 20px;
display: flex;
flex-direction: column;
}
.header {
}
.border {
display: flex;
flex-direction: row;
justify-content: space-between;
writing-mode: tb-rl;
}
.experiences > div:nth-child(even) .start {
transform: none;
}
.experiences > div:nth-child(odd) .start {
transform: rotate(180deg);
}
.experiences > div:nth-child(even) .end {
transform: none;
} }
.experiences > div:nth-child(odd) .end { .experience:nth-child(n + 2) {
transform: rotate(180deg); padding-top: 20px;
} }
.experiences > div:nth-child(even) .header { .missions {
text-align: right; margin: 8px;
margin-right: 20px;
} }
.experiences > div:nth-child(odd) .header { .mission:nth-child(n + 2) {
text-align: left; padding-top: 8px;
margin-left: 20px;
} }
.experiences > div:nth-child(even) .border { .header {
order: 2; display: flex;
flex-direction: column;
justify-content: space-between;
align-items: baseline;
} }
.experiences > div:nth-child(odd) .border { .role {
order: 0; font-size: larger;
} }
.body-wrapper { .company {
font-size: large;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: baseline;
} }
.body { .date {
margin: 16px; font-style: italic;
font-size: small;
padding-left: 20px;
} }
</style> </style>

25
src/components/skills/MySkills.vue

@ -1,10 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import RightSection from "@/components/common/RightSection.vue"; import RightSection from "@/components/common/RightSection.vue";
import ProgressBar from "@/components/skills/ProgressBar.vue";
</script> </script>
<template> <template>
<RightSection :title="$t('section.skills')"> <RightSection :title="$t('section.skills')">
Here are my skills <div class="skill">
<div class="skill-name">Test</div>
<ProgressBar style="width: 70%" percent="60" />
</div>
<div class="skill">
<div class="skill-name">Java</div>
<ProgressBar style="width: 70%" percent="70" />
</div>
<div class="skill">
<div class="skill-name">Docker</div>
<ProgressBar style="width: 70%" percent="80" />
</div>
</RightSection> </RightSection>
</template> </template>
@ -14,4 +26,13 @@ export default {
}; };
</script> </script>
<style scoped></style> <style scoped>
.skill {
width: 300px;
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 8px;
margin-left: 8px;
}
</style>

32
src/components/skills/ProgressBar.vue

@ -0,0 +1,32 @@
<template>
<div class="progress-bar">
<div class="fill" :style="{ width: `${percent}%` }"></div>
<div class="empty" :style="{ width: `${100 - percent}%` }"></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "ProgressBar",
props: {
percent: Number,
},
});
</script>
<style>
.progress-bar {
display: flex;
flex-direction: row;
align-items: center;
}
.progress-bar .fill {
border: 1px solid gray;
height: 0.9em;
}
.progress-bar .empty {
border-top: 1px solid gray;
}
</style>

37
src/data/experiences.ts

@ -21,6 +21,7 @@ import {
Kubernetes, Kubernetes,
Linux, Linux,
Pencil, Pencil,
React,
Scrum, Scrum,
Skaffold, Skaffold,
SpringBoot, SpringBoot,
@ -31,10 +32,34 @@ import {
} from "@/data/skills"; } from "@/data/skills";
const experience = (): Experience[] => { const experience = (): Experience[] => {
return [ return [
{
company: "Sunday",
startAt: 2021,
endAt: 2022,
role: "Ingénieur Logiciel Senior",
missions: [
{
description:
"Conception/Développement/DevOps pour un logiciel de déploiement d'un ERP, avec la création d'une stack dev entièrement sur cluster",
skills: [SpringBoot, React, Kubernetes],
},
{
description:
"Développement d'une application de gestion de compatibilités de versions entre les modules d'un ERP",
skills: [SpringBoot, Angular, Docker, Scrum],
},
{
description:
"Diffuseur technique : conception, développement et utilisation d'outils de livraison en production",
skills: [Bash, Ansible, Linux],
},
],
},
{ {
company: "Mipih", company: "Mipih",
startAt: 2017, startAt: 2017,
endAt: 2021, endAt: 2021,
role: "Ingénieur Logiciel Senior",
missions: [ missions: [
{ {
description: description:
@ -57,10 +82,11 @@ const experience = (): Experience[] => {
company: "Lyra Network", company: "Lyra Network",
startAt: 2012, startAt: 2012,
endAt: 2017, endAt: 2017,
role: "Ingénieur Logiciel",
missions: [ missions: [
{ {
description: description:
"Acteur et animateur de la cellule Industrialisa5on : standardisation et mise en place d'une chaîne CI", "Acteur et animateur de la cellule Industrialisation : standardisation et mise en place d'une chaîne CI",
skills: [Agility, DevOps, Jenkins], skills: [Agility, DevOps, Jenkins],
}, },
{ {
@ -72,15 +98,10 @@ const experience = (): Experience[] => {
description: "Responsable Gestion de configuration du service", description: "Responsable Gestion de configuration du service",
skills: [Svn, Git], skills: [Svn, Git],
}, },
{
description:
"Concepteur et développeur API Rest / UX WEB pour application web sur tablette",
skills: [Jee, HtmlCss, ExtJS],
},
{ {
description: description:
"Concepteur et développeur UX pour la plateforme de paiement", "Concepteur et développeur UX pour la plateforme de paiement",
skills: [Jee, HtmlCss, ExtJS], skills: [Jee, HtmlCss, ExtJS, UX],
}, },
], ],
}, },
@ -88,6 +109,7 @@ const experience = (): Experience[] => {
company: "Studec", company: "Studec",
startAt: 2010, startAt: 2010,
endAt: 2012, endAt: 2012,
role: "Ingénieur Logiciel",
missions: [ missions: [
{ {
description: description:
@ -115,6 +137,7 @@ const experience = (): Experience[] => {
company: "SOGETI High Tech", company: "SOGETI High Tech",
startAt: 2006, startAt: 2006,
endAt: 2010, endAt: 2010,
role: "Ingénieur Logiciel Débutant",
missions: [ missions: [
{ {
description: description:

5
src/data/skills.ts

@ -46,6 +46,11 @@ export const Angular: Skill = {
score: 17, score: 17,
parent: [Typescript, UX], parent: [Typescript, UX],
}; };
export const React: Skill = {
name: "Angular",
score: 17,
parent: [Typescript, UX],
};
export const ExtJS: Skill = { export const ExtJS: Skill = {
name: "ExtJS", name: "ExtJS",
score: 14, score: 14,

1
src/data/types.ts

@ -8,6 +8,7 @@ export interface Experience {
startAt: number; startAt: number;
endAt: number; endAt: number;
missions: Mission[]; missions: Mission[];
role: string;
} }
export type Skill = { export type Skill = {

Loading…
Cancel
Save