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>
<MyCv />
<div class="menu">
<button onclick="window.downloadPdf()">
<button @click="downloadPdf()">
<DownloadIcon width="32" height="32" />
</button>
</div>
@ -14,9 +9,31 @@ import DownloadIcon from "@/components/icons/DownloadIcon.vue";
<script lang="ts">
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({
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>

25
src/assets/main.css

@ -195,8 +195,33 @@
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{
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 {
font-size: 36px;

57
src/components/common/WordCloud.vue

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

36
src/components/experiences/MyExperiences.vue

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

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

4
src/components/informations/MyPresentation.vue

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

12
src/data/informations.ts

@ -7,11 +7,13 @@ const informations = (): Information => {
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
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.
La communication est clé dans mes échanges avec mes pairs.
Adepte du software craftsmanship, j'ai à coeur de produire et maintenir un code facile à comprendre, modifier ou tester. <br /><br />
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",
address: ["25 rue de la Moselle", "31100 Toulouse"],
address: ["31100 Toulouse"],
birthDay: new Date(1982, 11, 3),
education: [
{
@ -37,7 +39,7 @@ const informations = (): Information => {
},
],
hobbies: [
"Rhétorique, Biais Congnitifs",
"Rhétorique, Biais Cognitifs",
"Domotique, Électronique",
"Conception/Impression 3D",
"Roller",
@ -45,7 +47,7 @@ const informations = (): Information => {
phoneNumber: "06 16 92 01 17",
languages: [
{ 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 };
const Docker: Skill = { name: "Docker", score: 0, parent: [DevOps] };
const Kubernetes: Skill = { name: "Kubernetes", score: 28, parent: [DevOps] };
const Skaffold: Skill = { name: "Skaffold", score: 24, parent: [Kubernetes] };
const Ansible: Skill = { name: "Ansible", score: 17, parent: [DevOps] };
const Helm: Skill = { name: "Helm", score: 14, parent: [Kubernetes] };
const UX: Skill = { name: "UX", score: 15 };
const CI_CD: Skill = { name: "CI/CD", score: 16 };
const Maven: Skill = { name: "Maven", score: 3 };
const Npm: Skill = { name: "Npm", score: 4 };
const Jenkins: Skill = { name: "Jenkins", score: 12, parent: [CI_CD, DevOps] };
const Linux: Skill = { name: "Linux", score: 23 };
const Git: Skill = { name: "Git", score: 12 };
const Svn: Skill = { name: "SVN", score: 1 };
const Java: Skill = { name: "Java", score: 6 };
const SpringBoot: Skill = { name: "SpringBoot", score: 0, parent: [Java] };
const Jee: Skill = { name: "JEE", score: 26, parent: [Java] };
const Gwt: Skill = { name: "GWT", score: 4, parent: [Java, UX] };
const Typescript: Skill = { name: "Typescript", score: 25 };
const Javascript: Skill = { name: "Javascript", score: 19 };
const Angular: Skill = { name: "Angular", score: 17, parent: [Typescript, UX] };
const React: Skill = { name: "React", score: 17, parent: [Typescript, UX] };
const ReactNative: Skill = {
name: "React Native",
score: 17,
parent: [Typescript, UX],
};
const ExtJS: Skill = { name: "ExtJS", score: 14, parent: [Javascript, UX] };
const Bash: Skill = { name: "Bash", score: 0 };
const HtmlCss: Skill = { name: "HTML/CSS", score: 0 };
const Kotlin: Skill = { name: "Kotlin", score: 28 };
const Groovy: Skill = { name: "Groovy", score: 0 };
const DotNET: Skill = { name: ".NET", score: 0 };
const CSHARP: Skill = { name: "C#", score: 0, parent: [DotNET] };
const WPF: Skill = { name: "WPF", score: 0, parent: [DotNET, UX] };
const Blend: Skill = { name: "Blend", score: 0, parent: [DotNET, UX] };
const Pencil: Skill = { name: "Pencil", score: 0, parent: [UX] };
const Agility: Skill = { name: "Agility", score: 0 };
const Scrum: Skill = { name: "Scrum", score: 0, parent: [Agility] };
const Tests: Skill = { name: "Tests", score: 0 };
const Codecept: Skill = { name: "CodeceptJS", score: 25, parent: [Tests] };
const UIAutomation: Skill = { name: "UIAutomation", score: 0, parent: [Tests] };
const JUnit: Skill = { name: "JUnit", score: 0, parent: [Tests, Java] };
const Communication: Skill = { name: "Communication", score: 0 };
type Skill = Partial<DomainSkill> & { name: string };
const DevOps: Skill = { name: "DevOps" };
const Docker: Skill = { name: "Docker", parent: [DevOps] };
const Kubernetes: Skill = { name: "Kubernetes", parent: [DevOps] };
const Skaffold: Skill = { name: "Skaffold", parent: [Kubernetes] };
const Ansible: Skill = { name: "Ansible", parent: [DevOps] };
const Helm: Skill = { name: "Helm", parent: [Kubernetes] };
const UX: Skill = { name: "UX" };
const CI_CD: Skill = { name: "CI/CD" };
const Maven: Skill = { name: "Maven" };
const Npm: Skill = { name: "Npm" };
const Jenkins: Skill = { name: "Jenkins", parent: [CI_CD, DevOps] };
const Linux: Skill = { name: "Linux" };
const Git: Skill = { name: "Git" };
const Svn: Skill = { name: "SVN" };
const Java: Skill = { name: "Java" };
const SpringBoot: Skill = { name: "SpringBoot", parent: [Java] };
const Jee: Skill = { name: "JEE", parent: [Java] };
const Gwt: Skill = { name: "GWT", parent: [Java, UX] };
const Typescript: Skill = { name: "Typescript" };
const Javascript: Skill = { name: "Javascript" };
const Angular: Skill = { name: "Angular", parent: [Typescript, UX] };
const React: Skill = { name: "React", parent: [Typescript, UX] };
const ReactNative: Skill = { name: "React Native", parent: [Typescript, UX] };
const ExtJS: Skill = { name: "ExtJS", parent: [Javascript, UX] };
const Bash: Skill = { name: "Bash" };
const HtmlCss: Skill = { name: "HTML/CSS" };
const Kotlin: Skill = { name: "Kotlin" };
const Groovy: Skill = { name: "Groovy" };
const DotNET: Skill = { name: ".NET" };
const CSHARP: Skill = { name: "C#", parent: [DotNET] };
const WPF: Skill = { name: "WPF", parent: [DotNET, UX] };
const Blend: Skill = { name: "Blend", parent: [DotNET, UX] };
const Pencil: Skill = { name: "Pencil", parent: [UX] };
const Agility: Skill = { name: "Agility" };
const Scrum: Skill = { name: "Scrum", parent: [Agility] };
const Tests: Skill = { name: "Tests" };
const Codecept: Skill = { name: "CodeceptJS", parent: [Tests] };
const UIAutomation: Skill = { name: "UIAutomation", parent: [Tests] };
const JUnit: Skill = { name: "JUnit", parent: [Tests, Java] };
DevOps.score = 100;
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 {
DevOps,
@ -85,5 +129,4 @@ export default {
Tests,
UIAutomation,
JUnit,
Communication,
} as { [key: string]: Skill };
} as { [key: string]: DomainSkill };

3
src/data/types.ts

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

16
src/main.ts

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