mirror of
https://github.com/NotXia/notxia.github.io.git
synced 2025-12-16 03:31:46 +01:00
Add timeline
This commit is contained in:
122
src/components/timeline/Timeline.vue
Normal file
122
src/components/timeline/Timeline.vue
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full" ref="container_timeline">
|
||||||
|
<div class="flex justify-center w-full h-full" v-if="month_offset > 0 && min_date && max_date">
|
||||||
|
<!-- Left side -->
|
||||||
|
<ol class="relative border-r text-right w-1/2 border-zinc-200 dark:border-zinc-700">
|
||||||
|
<li class="mr-4 absolute right-0" v-for="event in left_events" :key="props.left[event.index].title" :style="`top: ${event.offset*month_offset + 10}px`">
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Start point -->
|
||||||
|
<div class="absolute w-3 h-3 z-10 bg-gray-400 rounded-full border border-white dark:border-gray-900 dark:bg-gray-500" style="right: -1.43rem"></div>
|
||||||
|
<div v-if="monthDifference(props.left[event.index].start, props.left[event.index].end) > 0" >
|
||||||
|
<!-- End point -->
|
||||||
|
<div class="absolute w-3 h-3 z-10 bg-gray-400 rounded-full border border-white dark:border-gray-900 dark:bg-gray-500" :style="`right: -1.43rem; bottom: 0`"></div>
|
||||||
|
<!-- Interval line -->
|
||||||
|
<div class="absolute rounded-full border-l-2 border-gray-400 dark:border-gray-500" style="height: 100%; top: 0; right: -1.12rem"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center" :style="`height: ${monthDifference(props.left[event.index].start, props.left[event.index].end)*month_offset}px`">
|
||||||
|
<div>
|
||||||
|
<span class="mb-0 text-sm font-normal leading-5 whitespace-pre-wrap text-gray-500 dark:text-gray-400">{{ props.left[event.index].time_label }}</span>
|
||||||
|
<h3 class="text-lg mb-0 font-semibold leading-5 whitespace-pre-wrap text-gray-900 dark:text-white">{{ props.left[event.index].title }}</h3>
|
||||||
|
<p class="mb-0 text-base font-normal leading-5 whitespace-pre-wrap text-gray-500 dark:text-gray-400">{{ props.left[event.index].description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="w-4"></div>
|
||||||
|
|
||||||
|
<!-- Right side -->
|
||||||
|
<ol class="relative w-1/2 border-l border-zinc-200 dark:border-zinc-700">
|
||||||
|
<li class="ml-4 absolute left-0" v-for="event in right_events" :key="props.right[event.index].title" :style="`top: ${event.offset*month_offset + 10}px`">
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Start point -->
|
||||||
|
<div class="absolute w-3 h-3 z-10 bg-gray-400 rounded-full border border-white dark:border-gray-900 dark:bg-gray-500" style="left: -1.43rem"></div>
|
||||||
|
<div v-if="monthDifference(props.right[event.index].start, props.right[event.index].end) > 0">
|
||||||
|
<!-- End point -->
|
||||||
|
<div class="absolute w-3 h-3 z-10 bg-gray-400 rounded-full border border-white dark:border-gray-900 dark:bg-gray-500" :style="`left: -1.43rem; bottom: 0`"></div>
|
||||||
|
<!-- Interval line -->
|
||||||
|
<div class="absolute rounded-full border-l-2 border-we border-gray-400 dark:border-gray-500" style="height: 100%; top: 0; left: -1.12rem"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center" :style="`height: ${monthDifference(props.right[event.index].start, props.right[event.index].end)*month_offset}px`">
|
||||||
|
<div>
|
||||||
|
<span class="mb-0 text-sm font-normal leading-5 whitespace-pre-wrap text-gray-500 dark:text-gray-400">{{ props.right[event.index].time_label }}</span>
|
||||||
|
<h3 class="mb-0 text-lg font-semibold leading-5 whitespace-pre-wrap text-gray-900 dark:text-white">{{ props.right[event.index].title }}</h3>
|
||||||
|
<p class="mb-0 text-base font-normal leading-5 whitespace-pre-wrap text-gray-500 dark:text-gray-400">{{ props.right[event.index].description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import type { PropType, Ref } from "vue";
|
||||||
|
|
||||||
|
interface Event {
|
||||||
|
title: string, description: string, time_label: string,
|
||||||
|
start: Date, end: Date
|
||||||
|
};
|
||||||
|
const props = defineProps({
|
||||||
|
right: {
|
||||||
|
type: Object as PropType<Event[]>,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
type: Object as PropType<Event[]>,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const container_timeline = ref();
|
||||||
|
const month_offset = ref(-1);
|
||||||
|
const right_events:Ref<{ index: number, offset: number }[]> = ref([]);
|
||||||
|
const left_events:Ref<{ index: number, offset: number }[]> = ref([]);
|
||||||
|
const min_date:Ref<Date> = ref(new Date());
|
||||||
|
const max_date:Ref<Date> = ref(new Date());
|
||||||
|
|
||||||
|
|
||||||
|
// Return the number of months between the start and the end of the interval
|
||||||
|
function monthDifference(start_date:Date, end_date:Date):number {
|
||||||
|
return end_date.getMonth() - start_date.getMonth() + (12 * (end_date.getFullYear() - start_date.getFullYear()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimelineSize():void {
|
||||||
|
const available_space:number = container_timeline.value.clientHeight ?? 0;
|
||||||
|
let events_min_date:Date = new Date(),
|
||||||
|
events_max_date:Date = new Date(0);
|
||||||
|
|
||||||
|
// Searches for the start and the end of the time interval
|
||||||
|
[...props.right, ...props.left]?.forEach((event) => {
|
||||||
|
if (!events_min_date || event.start < events_min_date) { events_min_date = event.start; }
|
||||||
|
if (!events_max_date || event.end > events_max_date) { events_max_date = event.end; }
|
||||||
|
});
|
||||||
|
|
||||||
|
min_date.value = events_min_date;
|
||||||
|
max_date.value = events_max_date;
|
||||||
|
month_offset.value = Math.floor( available_space / (monthDifference(events_min_date, events_max_date)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateTimelineSize();
|
||||||
|
|
||||||
|
// Computes the offset w.r.t. the end of the time interval
|
||||||
|
right_events.value = props.right.map((event, index) => ({
|
||||||
|
offset: monthDifference(event.end, max_date.value),
|
||||||
|
index: index
|
||||||
|
}));
|
||||||
|
left_events.value = props.left.map((event, index) => ({
|
||||||
|
offset: monthDifference(event.end, max_date.value),
|
||||||
|
index: index
|
||||||
|
}));
|
||||||
|
|
||||||
|
window.addEventListener("resize", updateTimelineSize);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
78
src/views/resume/ExperienceTimeline.vue
Normal file
78
src/views/resume/ExperienceTimeline.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-full timeline-height">
|
||||||
|
<Timeline
|
||||||
|
:right="[
|
||||||
|
{
|
||||||
|
title: t('diploma'), time_label: '2015 - 2020',
|
||||||
|
description: t('diploma description'),
|
||||||
|
start: new Date(2015, september, 1), end: new Date(2020, june, 1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('bachelor'), time_label: '2020 - 2023',
|
||||||
|
description: t('bachelor description'),
|
||||||
|
start: new Date(2020, september, 1), end: new Date(2023, december, 1)
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:left="[
|
||||||
|
{
|
||||||
|
title: t('it/is pcto toyota'), time_label: `${t('december')} 2019 | ${t('july')} 2019 | ${t('february')} 2022`,
|
||||||
|
description: 'Toyota Material Handling Manufacturing Italy',
|
||||||
|
start: new Date(2019, february, 1), end: new Date(2019, november, 1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'CS50’s Introduction to AI with Python', time_label: '2022',
|
||||||
|
description: 'HarvardX',
|
||||||
|
start: new Date(2022, september, 1), end: new Date(2022, september, 1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'CISCO: IT Essentials', time_label: '2018',
|
||||||
|
description: 'CISCO Networking Academy',
|
||||||
|
start: new Date(2018, september, 1), end: new Date(2018, september, 1)
|
||||||
|
}
|
||||||
|
]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-xs text-gray-400 dark:text-slate-600">{{ t("like timelines") }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Timeline from "@/components/timeline/Timeline.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import locale from "./locale.timeline.json";
|
||||||
|
|
||||||
|
const { t } = useI18n({ messages: locale });
|
||||||
|
|
||||||
|
const january = 0, february = 1, march = 2, april = 3, may = 4, june = 5, july = 6, august = 7, september = 8, october = 9, november = 10, december = 11;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Extra small devices */
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.timeline-height { height: 90rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small devices */
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
.timeline-height { height: 80rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medium devices */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
.timeline-height { height: 70rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large devices */
|
||||||
|
@media only screen and (min-width: 992px) {
|
||||||
|
.timeline-height { height: 60rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra large devices */
|
||||||
|
@media only screen and (min-width: 1200px) {
|
||||||
|
.timeline-height { height: 60rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -2,17 +2,14 @@
|
|||||||
<Navbar />
|
<Navbar />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="container mx-auto">
|
<ExperienceTimeline />
|
||||||
<h1 class="text-5xl font-bold">
|
|
||||||
{{ t("hello") }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Navbar from "@/components/navbar/Navbar.vue";
|
import Navbar from "@/components/navbar/Navbar.vue";
|
||||||
|
import ExperienceTimeline from "./ExperienceTimeline.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import locale from "./locale.json";
|
import locale from "./locale.json";
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"en": {
|
"en": {
|
||||||
"hello": "Hello"
|
|
||||||
},
|
},
|
||||||
"it": {
|
"it": {
|
||||||
"hello": "Ciao"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
src/views/resume/locale.timeline.json
Normal file
26
src/views/resume/locale.timeline.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"january": "January", "february": "February", "march": "March", "april": "April", "may": "May", "june": "June",
|
||||||
|
"july": "July", "august": "August", "september": "September", "october": "October", "november": "November", "december": "December",
|
||||||
|
"like timelines": "I like timelines",
|
||||||
|
|
||||||
|
"diploma": "Secondary school diploma in IT",
|
||||||
|
"diploma description": "Aldini Valeriani, Bologna, Italy",
|
||||||
|
"bachelor": "Bachelor's degree in Computer Science",
|
||||||
|
"bachelor description": "Alma Mater Studiorum, University of Bologna",
|
||||||
|
|
||||||
|
"it/is pcto toyota": "IT/IS office, Internship"
|
||||||
|
},
|
||||||
|
"it": {
|
||||||
|
"january": "Gennaio", "february": "Febbraio", "march": "Marzo", "april": "Aprile", "may": "Maggio", "june": "Giugno",
|
||||||
|
"july": "Luglio", "august": "Agosto", "september": "Settembre", "october": "Ottobre", "november": "Novembre", "december": "Dicembre",
|
||||||
|
"like timelines": "Mi piacciono le linee del tempo",
|
||||||
|
|
||||||
|
"diploma": "Diploma di perito informatico",
|
||||||
|
"diploma description": "I.T.I Aldini Valeriani, Bologna",
|
||||||
|
"bachelor": "Laurea triennale in Informatica",
|
||||||
|
"bachelor description": "Alma Mater Studiorum, Università di Bologna",
|
||||||
|
|
||||||
|
"it/is pcto toyota": "Ufficio IT/IS, Stage"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user