Browse Source

feat: dynamic UI

nicolas.marsal 3 years ago
parent
commit
2f27ef7c6f
No known key found for this signature in database
GPG Key ID: 268AB819B6453541
  1. 29
      src/App.vue
  2. 25
      src/assets/main.css
  3. 57
      src/components/common/WordCloud.vue
  4. 36
      src/components/experiences/MyExperiences.vue
  5. 8
      src/components/informations/MyInformations.vue
  6. 4
      src/components/informations/MyPresentation.vue
  7. 12
      src/data/informations.ts
  8. 137
      src/data/skills.ts
  9. 3
      src/data/types.ts
  10. 16
      src/main.ts
  11. 0
      src/stores/selectedSkill.ts

29
src/App.vue

@ -1,12 +1,7 @@
<script setup lang="ts">
import MyCv from "@/components/cv/MyCv.vue";
import DownloadIcon from "@/components/icons/DownloadIcon.vue";
</script>
<template> <template>
<MyCv /> <MyCv />
<div class="menu"> <div class="menu">
<button onclick="window.downloadPdf()"> <button @click="downloadPdf()">
<DownloadIcon width="32" height="32" /> <DownloadIcon width="32" height="32" />
</button> </button>
</div> </div>
@ -14,9 +9,31 @@ import DownloadIcon from "@/components/icons/DownloadIcon.vue";
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { useSelectedSkill } from "@/stores/selectedSkill";
import MyCv from "@/components/cv/MyCv.vue";
import DownloadIcon from "@/components/icons/DownloadIcon.vue";
export default defineComponent({ export default defineComponent({
name: "app", name: "app",
components: {
MyCv,
DownloadIcon,
},
setup() {
const store = useSelectedSkill();
return {
unselect: store.unselect,
};
},
methods: {
downloadPdf() {
this.unselect();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.downloadPdf();
},
},
}); });
</script> </script>

25
src/assets/main.css

@ -195,8 +195,33 @@
margin-right: 10px; margin-right: 10px;
} }
.my-experiences-mission-skill {
color:black;
}
.my-experiences-mission-skill.unselected{
color: #b7b7b7;
filter: none;
}
.my-experiences-mission-skill.selected{
color:black;
/*filter: drop-shadow( 0px 0px 7px rgba(0, 255, 0, 1));*/
}
.my-experiences-mission-skill:nth-child(n+2)::before{
content: ", "
}
.my-experiences-mission-description{ .my-experiences-mission-description{
flex: 3 1; flex: 3 1;
color: black;
}
.my-experiences-mission-description.unselected{
color: #b7b7b7;
filter: none;
}
.my-experiences-mission-description.selected{
color: black;
/*filter: drop-shadow( 0px 0px 7px rgba(0, 255, 0, 1));*/
} }
.first-name { .first-name {
font-size: 36px; font-size: 36px;

57
src/components/common/WordCloud.vue

@ -10,14 +10,15 @@
<text <text
v-for="word in words" v-for="word in words"
:key="word.text" :key="word.text"
:id="word.text"
:style="{ :style="{
fontSize: word.size + 'px', fontSize: word.size + 'px',
fontFamily: word.font, fontFamily: word.font,
cursor: 'pointer', cursor: 'pointer',
fill: filter:
selectedSkillStore.skill?.name === word.text selectedSkillStore.skill?.name === word.text
? '#FF0000' ? 'drop-shadow( 0px 0px 3px rgba(0, 0, 0, 0.5))'
: '#000000', : 'none',
userSelect: 'none', userSelect: 'none',
}" }"
:transform="`translate(${word.x}, ${word.y})rotate(${word.rotate})`" :transform="`translate(${word.x}, ${word.y})rotate(${word.rotate})`"
@ -33,7 +34,10 @@
<style scoped> <style scoped>
.word { .word {
transition: 1s transform ease-in-out; transition: all 1s ease-in-out;
-webkit-transition: all 1s ease-in-out;
/*transition: 1s transform, 1s font-size;*/
/*-webkit-transition: 1s -webkit-transform, 1s transform, 1s font-size;*/
} }
</style> </style>
@ -41,7 +45,7 @@
import cloud from "d3-cloud"; import cloud from "d3-cloud";
import skills from "@/data/skills"; import skills from "@/data/skills";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { useSelectedSkill } from "@/stores/counter"; import { useSelectedSkill } from "@/stores/selectedSkill";
export default defineComponent({ export default defineComponent({
props: { props: {
@ -61,10 +65,18 @@ export default defineComponent({
}, },
setup() { setup() {
const selectedSkillStore = useSelectedSkill(); const selectedSkillStore = useSelectedSkill();
return { return {
selectedSkillStore, selectedSkillStore,
}; };
}, },
mounted() {
this.selectedSkillStore.$subscribe((mutation, state) => {
console.log(mutation);
console.log(state);
this.computeWords();
});
},
created() { created() {
this.computeWords(); this.computeWords();
// setInterval(() => { // setInterval(() => {
@ -91,12 +103,13 @@ export default defineComponent({
} else { } else {
this.selectedSkillStore.unselect(); this.selectedSkillStore.unselect();
} }
this.computeWords();
}, },
computeWords() { computeWords() {
const words: (cloud.Word & { size: number; fixed?: boolean })[] = const words: (cloud.Word & { size: number; fixed?: boolean })[] =
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 / (s.parent ? 2 : 1) }));
const min = words const min = words
.map((s) => s.size) .map((s) => s.size)
@ -107,18 +120,19 @@ export default defineComponent({
const scaleFontSize = ({ const scaleFontSize = ({
size, size,
fixed, fixed,
}: { text,
size: number; }: cloud.Word & { size: number; fixed?: boolean }) => {
fixed?: boolean; if (fixed) return size;
}) => { if (text === this.selectedSkillStore?.skill?.name)
return fixed return this.maxFontSize * 1.5;
? size
: ((size - min) / (max - min)) * return (
(this.maxFontSize - this.minFontSize) + ((size - min) / (max - min)) * (this.maxFontSize - this.minFontSize) +
this.minFontSize; this.minFontSize
);
}; };
const randomRotate = (s: number) => { const randomRotate = (s: number) => {
const range = 120 * (1 - ((s - min) / (max - min)) * 0.7); const range = 80 * (1 - ((s - min) / (max - min)) * 0.7);
return Math.random() * range - range / 2; return Math.random() * range - range / 2;
}; };
// words = [ // words = [
@ -145,15 +159,14 @@ export default defineComponent({
`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 !");
this.maxFontSize = this.maxFontSize - 2; this.maxFontSize = this.maxFontSize - 2;
if (this.maxFontSize > 2 * this.minFontSize) { // if (this.maxFontSize > 2 * this.minFontSize) {
this.computeWords(); this.computeWords();
// } else {
// this.words = d.sort((a, b) => a.text!.localeCompare(b.text!));
// }
} else { } else {
this.words = d; this.words = d.sort((a, b) => a.text!.localeCompare(b.text!));
}
} else {
this.words = d;
} }
}) })
.start(); .start();

36
src/components/experiences/MyExperiences.vue

@ -24,14 +24,26 @@ import RightSection from "@/components/common/RightSection.vue";
class="my-experiences-mission" class="my-experiences-mission"
v-for="mission in experience.missions" v-for="mission in experience.missions"
:key="mission.description" :key="mission.description"
:style="{
color: isSelected(mission) ? 'red' : 'black',
}"
> >
<div class="my-experiences-mission-skills"> <div class="my-experiences-mission-skills">
{{ mission.skills.map((s) => s.name).join(", ") }} <span
v-for="skill in mission.skills"
:key="skill.name"
class="my-experiences-mission-skill"
:class="{
selected: isSelectedSkill(skill),
unselected: !noSelectionAtAll() && !isSelectedSkill(skill),
}"
>{{ skill.name }}</span
>
</div> </div>
<div class="my-experiences-mission-description" style=""> <div
class="my-experiences-mission-description"
:class="{
selected: isSelectedMission(mission),
unselected: !noSelectionAtAll() && !isSelectedMission(mission),
}"
>
{{ mission.description }} {{ mission.description }}
</div> </div>
</div> </div>
@ -44,9 +56,9 @@ import RightSection from "@/components/common/RightSection.vue";
<script lang="ts"> <script lang="ts">
import experiences from "@/data/experiences"; import experiences from "@/data/experiences";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { useSelectedSkill } from "@/stores/counter"; import { useSelectedSkill } from "@/stores/selectedSkill";
import { mapState } from "pinia"; import { mapState } from "pinia";
import type { Mission } from "@/data/types"; import type { Mission, Skill } from "@/data/types";
export default defineComponent({ export default defineComponent({
name: "MyExperiences", name: "MyExperiences",
@ -61,11 +73,19 @@ export default defineComponent({
}), }),
}, },
methods: { methods: {
isSelected(mission: Mission) { isSelectedMission(mission: Mission) {
return mission.skills return mission.skills
.flatMap((s) => [...(s.parent ?? []), s]) .flatMap((s) => [...(s.parent ?? []), s])
.some((s) => s?.name === this.selectedSkill?.name); .some((s) => s?.name === this.selectedSkill?.name);
}, },
isSelectedSkill(skill: Skill) {
return [...(skill.parent ?? []), skill].some(
(s) => s?.name === this.selectedSkill?.name
);
},
noSelectionAtAll() {
return this.selectedSkill === undefined;
},
}, },
}); });
</script> </script>

8
src/components/informations/MyInformations.vue

@ -3,6 +3,7 @@ import LeftSection from "@/components/common/LeftSection.vue";
import EmailIcon from "@/components/icons/EmailIcon.vue"; import EmailIcon from "@/components/icons/EmailIcon.vue";
import HouseIcon from "@/components/icons/HouseIcon.vue"; import HouseIcon from "@/components/icons/HouseIcon.vue";
import PhoneIcon from "@/components/icons/PhoneIcon.vue"; import PhoneIcon from "@/components/icons/PhoneIcon.vue";
import LinkedInIcon from "@/components/icons/LinkedInIcon.vue";
</script> </script>
<template> <template>
@ -22,10 +23,11 @@ import PhoneIcon from "@/components/icons/PhoneIcon.vue";
<div>{{ infos.phoneNumber }}</div> <div>{{ infos.phoneNumber }}</div>
</div> </div>
<div class="my-informations-line"> <div class="my-informations-line">
<div></div> <LinkedInIcon width="18" height="18" color="#000000" />
<div>{{ $t("age", { age: age() }) }}</div> <a :href="`https://www.linkedin.com/in/${infos.linkedin}`"
>linkedin.com/in/{{ infos.linkedin }}</a
>
</div> </div>
<div class="my-informations-line"></div>
</LeftSection> </LeftSection>
</template> </template>

4
src/components/informations/MyPresentation.vue

@ -1,7 +1,5 @@
<template> <template>
<div class="my-presentation"> <div class="my-presentation" v-html="presentation"></div>
{{ presentation }}
</div>
</template> </template>
<script lang="ts"> <script lang="ts">

12
src/data/informations.ts

@ -7,11 +7,13 @@ const informations = (): Information => {
title: "Développeur Sénior", title: "Développeur Sénior",
presentation: `Développeur avec 16 années d'expériences dans la réalisation de clients lourds ou web, initialement presentation: `Développeur avec 16 années d'expériences dans la réalisation de clients lourds ou web, initialement
développeur UI, j'ai ensuite ajouter à mes compétences le développement de backend, et plus particulièrement le DevOps ces dernières annnées. développeur UI, j'ai ensuite ajouter à mes compétences le développement de backend, et plus particulièrement le DevOps ces dernières annnées.
Adepte du software craftsmanship, j'ai à coeur de produire et maintenir un code facile à comprendre, modifier ou tester. Adepte du software craftsmanship, j'ai à coeur de produire et maintenir un code facile à comprendre, modifier ou tester. <br /><br />
La communication est clé dans mes échanges avec mes pairs. Au sein d'une équipe, j'aime être force de proposition, et travailler en pair programming pour faire émerger de meilleures solutions
et arriver à un consensus.
`, `,
linkedin: "nmars",
email: "nicolas.marsal@gmail.com", email: "nicolas.marsal@gmail.com",
address: ["25 rue de la Moselle", "31100 Toulouse"], address: ["31100 Toulouse"],
birthDay: new Date(1982, 11, 3), birthDay: new Date(1982, 11, 3),
education: [ education: [
{ {
@ -37,7 +39,7 @@ const informations = (): Information => {
}, },
], ],
hobbies: [ hobbies: [
"Rhétorique, Biais Congnitifs", "Rhétorique, Biais Cognitifs",
"Domotique, Électronique", "Domotique, Électronique",
"Conception/Impression 3D", "Conception/Impression 3D",
"Roller", "Roller",
@ -45,7 +47,7 @@ const informations = (): Information => {
phoneNumber: "06 16 92 01 17", phoneNumber: "06 16 92 01 17",
languages: [ languages: [
{ name: "Français", level: "langue maternelle" }, { name: "Français", level: "langue maternelle" },
{ name: "English", level: "good level" }, { name: "English", level: "good level (B2)" },
], ],
}; };
}; };

137
src/data/skills.ts

@ -1,49 +1,93 @@
import type { Skill } from "@/data/types"; import type { Skill as DomainSkill } from "@/data/types";
const DevOps: Skill = { name: "DevOps", score: 0 }; type Skill = Partial<DomainSkill> & { name: string };
const Docker: Skill = { name: "Docker", score: 0, parent: [DevOps] };
const Kubernetes: Skill = { name: "Kubernetes", score: 28, parent: [DevOps] }; const DevOps: Skill = { name: "DevOps" };
const Skaffold: Skill = { name: "Skaffold", score: 24, parent: [Kubernetes] }; const Docker: Skill = { name: "Docker", parent: [DevOps] };
const Ansible: Skill = { name: "Ansible", score: 17, parent: [DevOps] }; const Kubernetes: Skill = { name: "Kubernetes", parent: [DevOps] };
const Helm: Skill = { name: "Helm", score: 14, parent: [Kubernetes] }; const Skaffold: Skill = { name: "Skaffold", parent: [Kubernetes] };
const UX: Skill = { name: "UX", score: 15 }; const Ansible: Skill = { name: "Ansible", parent: [DevOps] };
const CI_CD: Skill = { name: "CI/CD", score: 16 }; const Helm: Skill = { name: "Helm", parent: [Kubernetes] };
const Maven: Skill = { name: "Maven", score: 3 }; const UX: Skill = { name: "UX" };
const Npm: Skill = { name: "Npm", score: 4 }; const CI_CD: Skill = { name: "CI/CD" };
const Jenkins: Skill = { name: "Jenkins", score: 12, parent: [CI_CD, DevOps] }; const Maven: Skill = { name: "Maven" };
const Linux: Skill = { name: "Linux", score: 23 }; const Npm: Skill = { name: "Npm" };
const Git: Skill = { name: "Git", score: 12 }; const Jenkins: Skill = { name: "Jenkins", parent: [CI_CD, DevOps] };
const Svn: Skill = { name: "SVN", score: 1 }; const Linux: Skill = { name: "Linux" };
const Java: Skill = { name: "Java", score: 6 }; const Git: Skill = { name: "Git" };
const SpringBoot: Skill = { name: "SpringBoot", score: 0, parent: [Java] }; const Svn: Skill = { name: "SVN" };
const Jee: Skill = { name: "JEE", score: 26, parent: [Java] }; const Java: Skill = { name: "Java" };
const Gwt: Skill = { name: "GWT", score: 4, parent: [Java, UX] }; const SpringBoot: Skill = { name: "SpringBoot", parent: [Java] };
const Typescript: Skill = { name: "Typescript", score: 25 }; const Jee: Skill = { name: "JEE", parent: [Java] };
const Javascript: Skill = { name: "Javascript", score: 19 }; const Gwt: Skill = { name: "GWT", parent: [Java, UX] };
const Angular: Skill = { name: "Angular", score: 17, parent: [Typescript, UX] }; const Typescript: Skill = { name: "Typescript" };
const React: Skill = { name: "React", score: 17, parent: [Typescript, UX] }; const Javascript: Skill = { name: "Javascript" };
const ReactNative: Skill = { const Angular: Skill = { name: "Angular", parent: [Typescript, UX] };
name: "React Native", const React: Skill = { name: "React", parent: [Typescript, UX] };
score: 17, const ReactNative: Skill = { name: "React Native", parent: [Typescript, UX] };
parent: [Typescript, UX], const ExtJS: Skill = { name: "ExtJS", parent: [Javascript, UX] };
}; const Bash: Skill = { name: "Bash" };
const ExtJS: Skill = { name: "ExtJS", score: 14, parent: [Javascript, UX] }; const HtmlCss: Skill = { name: "HTML/CSS" };
const Bash: Skill = { name: "Bash", score: 0 }; const Kotlin: Skill = { name: "Kotlin" };
const HtmlCss: Skill = { name: "HTML/CSS", score: 0 }; const Groovy: Skill = { name: "Groovy" };
const Kotlin: Skill = { name: "Kotlin", score: 28 }; const DotNET: Skill = { name: ".NET" };
const Groovy: Skill = { name: "Groovy", score: 0 }; const CSHARP: Skill = { name: "C#", parent: [DotNET] };
const DotNET: Skill = { name: ".NET", score: 0 }; const WPF: Skill = { name: "WPF", parent: [DotNET, UX] };
const CSHARP: Skill = { name: "C#", score: 0, parent: [DotNET] }; const Blend: Skill = { name: "Blend", parent: [DotNET, UX] };
const WPF: Skill = { name: "WPF", score: 0, parent: [DotNET, UX] }; const Pencil: Skill = { name: "Pencil", parent: [UX] };
const Blend: Skill = { name: "Blend", score: 0, parent: [DotNET, UX] }; const Agility: Skill = { name: "Agility" };
const Pencil: Skill = { name: "Pencil", score: 0, parent: [UX] }; const Scrum: Skill = { name: "Scrum", parent: [Agility] };
const Agility: Skill = { name: "Agility", score: 0 }; const Tests: Skill = { name: "Tests" };
const Scrum: Skill = { name: "Scrum", score: 0, parent: [Agility] }; const Codecept: Skill = { name: "CodeceptJS", parent: [Tests] };
const Tests: Skill = { name: "Tests", score: 0 }; const UIAutomation: Skill = { name: "UIAutomation", parent: [Tests] };
const Codecept: Skill = { name: "CodeceptJS", score: 25, parent: [Tests] }; const JUnit: Skill = { name: "JUnit", parent: [Tests, Java] };
const UIAutomation: Skill = { name: "UIAutomation", score: 0, parent: [Tests] };
const JUnit: Skill = { name: "JUnit", score: 0, parent: [Tests, Java] }; DevOps.score = 100;
const Communication: Skill = { name: "Communication", score: 0 }; Docker.score = 50;
Kubernetes.score = 50;
Skaffold.score = 50;
Ansible.score = 50;
Helm.score = 20;
CI_CD.score = 50;
Maven.score = 50;
Npm.score = 50;
Linux.score = 50;
Jenkins.score = 50;
Git.score = 70;
Svn.score = 40;
Java.score = 100;
SpringBoot.score = 50;
Jee.score = 50;
UX.score = 100;
Gwt.score = 20;
Typescript.score = 100;
Javascript.score = 50;
Angular.score = 70;
React.score = 70;
ReactNative.score = 50;
ExtJS.score = 20;
HtmlCss.score = 50;
Bash.score = 10;
Kotlin.score = 70;
Groovy.score = 60;
DotNET.score = 30;
CSHARP.score = 30;
WPF.score = 30;
Blend.score = 20;
Pencil.score = 10;
Agility.score = 80;
Scrum.score = 60;
Tests.score = 70;
Codecept.score = 60;
UIAutomation.score = 10;
JUnit.score = 70;
export default { export default {
DevOps, DevOps,
@ -85,5 +129,4 @@ export default {
Tests, Tests,
UIAutomation, UIAutomation,
JUnit, JUnit,
Communication, } as { [key: string]: DomainSkill };
} as { [key: string]: Skill };

3
src/data/types.ts

@ -14,7 +14,7 @@ export interface Experience {
export type Skill = { export type Skill = {
name: string; name: string;
score: number; score: number;
parent?: Skill[]; parent?: { name: string }[];
}; };
export interface Diploma { export interface Diploma {
@ -35,6 +35,7 @@ export type Hobby = string;
export interface Information { export interface Information {
firstName: string; firstName: string;
lastName: string; lastName: string;
linkedin: string;
presentation: string; presentation: string;
title: string; title: string;
address: string[]; address: string[];

16
src/main.ts

@ -23,20 +23,30 @@ import {
library.add(faAt, faHouseChimney, faPhone); library.add(faAt, faHouseChimney, faPhone);
const i18n = createI18n({ const i18n = createI18n({
locale: "fr", // locale: "fr",
messages: { messages: {
fr: { fr: {
section: { section: {
information: "informations", information: "informations",
education: "formation", education: "formation",
languages: "langues", languages: "langues",
hobbies: "hobbies", hobbies: "loisirs",
experiences: "expériences", experiences: "expériences",
skills: "compétences", skills: "compétences",
}, },
age: "{age} ans", age: "{age} ans",
}, },
en: {}, en: {
section: {
information: "information",
education: "education",
languages: "languages",
hobbies: "hobbies",
experiences: "experiences",
skills: "skills",
},
age: "{age} years",
},
}, },
}); });

0
src/stores/counter.ts → src/stores/selectedSkill.ts

Loading…
Cancel
Save