Skip to content

Commit

Permalink
feat: insights
Browse files Browse the repository at this point in the history
  • Loading branch information
colinlienard committed May 14, 2024
1 parent 42aefff commit 5267d51
Show file tree
Hide file tree
Showing 10 changed files with 485 additions and 9 deletions.
30 changes: 30 additions & 0 deletions components/Confetti.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { confetti } from '@tsparticles/confetti';
let interval: NodeJS.Timeout;
onMounted(() => {
let count = 0;
interval = setInterval(() => {
confetti({
particleCount: 100,
spread: 360,
origin: { x: Math.random(), y: Math.random() * 0.8 },
});
count++;
if (count === 5) {
clearInterval(interval);
}
}, 500);
});
onUnmounted(() => {
clearInterval(interval);
});
</script>

<template>
{{ null }}
</template>
108 changes: 108 additions & 0 deletions components/InsightCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<script setup lang="ts">
export type Card = {
title: string;
repo: string;
value: string;
unit: string | null;
};
const { card, index } = defineProps<{ card: Card; index: number }>();
const flippedCards = useFlippedCards();
const flipped = computed(() => flippedCards.value.includes(card.title));
const [translateX, translateY, rotate] = (() => {
switch (index) {
case 0:
return ['-1rem', '1rem', '-4deg'];
case 1:
return ['0rem', '2rem', '2deg'];
case 2:
return ['0rem', '0rem', '-2deg'];
case 3:
return ['1rem', '-2rem', '1deg'];
case 4:
return ['2rem', '2rem', '4deg'];
case 5:
default:
return ['1rem', '-2rem', '-6deg'];
}
})();
</script>

<template>
<li
class="[perspective:1000px] hover:z-10"
:style="{ transform: `translateX(${translateX}) translateY(${translateY}) rotate(${rotate})` }"
@click="!flipped && flippedCards.value.push(card.title)"
>
<div
:class="`card ${flipped && 'flipped'} relative h-96 w-64 cursor-pointer rounded-lg border border-solid border-slate-200 bg-slate-50 text-center shadow-lg transition-all [transform-style:preserve-3d] *:absolute *:flex *:h-full *:w-full *:flex-col *:items-center *:justify-center *:gap-2 *:p-10 *:[backface-visibility:hidden]`"
@click="!flipped && flippedCards.value.push(card.title)"
>
<div>
<h4 class="text-balance">{{ card.title }}...</h4>
</div>
<div class="relative overflow-hidden [transform:rotateY(180deg)]">
<h4 class="text-balance text-sm text-slate-500">{{ card.title }}</h4>
<p class="text-lg font-semibold leading-tight">{{ card.repo }}</p>
<p class="text-sm text-slate-500">with</p>
<span class="text-lg font-semibold leading-tight">{{ card.value }} {{ card.unit }}</span>
<div v-if="flipped" class="shine" />
</div>
</div>
</li>
</template>

<style scoped>
.card:hover {
transform: scale(1.05);
}
.card.flipped {
transform: rotateY(180deg);
animation: flip 1s cubic-bezier(0.6, 0, 0.8, 1);
cursor: default;
}
.card.flipped:hover {
transform: scale(1.05) rotateY(180deg);
}
.shine {
position: absolute;
width: 6rem;
height: 32rem;
background-color: white;
transform: rotate(45deg);
top: 200%;
animation: shine 0.75s 0.25s linear;
}
@keyframes flip {
0% {
transform: scale(1) rotateY(0deg);
}
20% {
transform: scale(1.2) rotateY(140deg);
}
80% {
transform: scale(1.3) rotateY(170deg);
}
95% {
transform: scale(0.9) rotateY(180deg);
}
100% {
transform: scale(1) rotateY(180deg);
}
}
@keyframes shine {
0% {
top: -110%;
}
100% {
top: 110%;
}
}
</style>
6 changes: 5 additions & 1 deletion components/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ onUnmounted(() => {
>
<header class="flex w-full items-center justify-between max-md:px-6">
<h1 class="font-serif font-semibold md:text-lg">
<NuxtLink to="/"><strong>githundred</strong></NuxtLink>
<NuxtLink to="/" tabindex="-1"><strong>githundred</strong></NuxtLink>
</h1>
<NuxtLink
to="https://github.com/colinlienard/githundred"
Expand Down Expand Up @@ -73,6 +73,10 @@ onUnmounted(() => {
<p class="text-sm">Last updated {{ lastUpdated }} ago</p>
</div>
</div>
<div class="flex gap-4">
<NuxtLink to="/">list</NuxtLink>
<NuxtLink to="/insights">insights</NuxtLink>
</div>
<div
class="mt-8 grid w-full grid-cols-[1fr_1fr] gap-2 py-8 [grid-template-areas:'a_a''b_b''c_d'] md:mt-16 md:flex"
>
Expand Down
9 changes: 9 additions & 0 deletions composables/useFlippedCards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const useFlippedCards = defineStore('flippedCards', () => {
const value = ref<string[]>([]);

onUnmounted(() => {
value.value = [];
});

return { value };
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@heroicons/vue": "^2.1.3",
"@pinia/nuxt": "^0.5.1",
"@tsparticles/confetti": "^3.3.0",
"node-emoji": "^2.1.3",
"nuxt": "^3.11.2",
"vue": "^3.4.21",
Expand Down
4 changes: 2 additions & 2 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ onUnmounted(() => {
<img :src="repo.image" :alt="`GitHub ${repo.ownerName} avatar`" class="h-6 rounded" />
<h3>
<span v-if="settings.showOwners" class="text-slate-500">{{ repo.ownerName }}/</span>
{{ repo.name }}
<span>{{ repo.name }}</span>
</h3>
</div>
</div>
<div>
<div class="flex items-center gap-1">
<StarIcon class="h-4" />
<span>{{ repo.starsNumber.toLocaleString() }}</span>
<span>{{ repo.stargazerCount.toLocaleString() }}</span>
</div>
</div>
<div class="text-slate-500">
Expand Down
73 changes: 73 additions & 0 deletions pages/insights.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import type { Card } from '~/components/InsightCard.vue';
const { data } = await useFetch('/api/insights');
type Entries<T> = {
[K in keyof T]: [K, T[K]];
}[keyof T][];
const cards = computed<Card[]>(() => {
if (!data.value) return [];
return (Object.entries(data.value) as Entries<typeof data.value>).map(
([key, { name, value }]) => {
const [title, unit] = getTexts(key);
return { title, repo: name, value: getFormattedValue(key, value), unit };
},
);
});
function getTexts(key: keyof NonNullable<typeof data.value>): [string, string | null] {
switch (key) {
case 'mostCommits':
return ['The one with the most commits is', 'commits'];
case 'mostContributors':
return ['The one with the most contributors is', 'contributors'];
case 'mostForked':
return ['The most forked is', 'forks'];
case 'mostRecent':
return ['The most recent is', null];
case 'mostUsedLanguage':
return ['The most used language is', 'occurrences'];
case 'oldest':
return ['The oldest is', null];
}
}
function getFormattedValue(
key: keyof NonNullable<typeof data.value>,
value: string | number,
): string {
switch (key) {
case 'mostCommits':
case 'mostContributors':
case 'mostForked':
case 'mostUsedLanguage':
return value.toLocaleString();
case 'mostRecent':
case 'oldest': {
const allMonths = (value as number) / 30 / 24 / 60 / 60 / 1000;
const years = Math.floor(allMonths / 12);
const months = Math.floor(allMonths % 12);
let result = years > 0 ? `${years} year${years > 1 ? 's' : ''}` : '';
if (months > 0) {
result += `${result.length > 0 ? ' and ' : ''}${months} month${months > 1 ? 's' : ''}`;
}
return result;
}
}
}
const flippedCards = useFlippedCards();
</script>

<template>
<div class="flex flex-col items-center gap-6">
<h3 class="text-xl font-semibold">Across all these repositories...</h3>
<ul class="-gap-4 grid w-fit grid-cols-3">
<InsightCard v-for="(card, index) of cards" :key="card.title" :card="card" :index="index" />
</ul>
<Confetti v-if="flippedCards.value.length === 6" />
<button @click="flippedCards.value = []">reset</button>
</div>
</template>
Loading

0 comments on commit 5267d51

Please sign in to comment.