Andrea Vos ee639965ed tmp
2024-07-04 22:58:40 +02:00

180 lines
5.5 KiB
Vue

<template>
<Tooltip v-if="celebrate1M" :text="$t('home.million')">
<span
ref="confettiLogo"
class="bg-primary logo-wrapper rounded-circle d-inline-flex justify-content-center align-items-center"
@mouseenter="fireConfetti"
>
<span class="logo" v-html="svg.replace(`<path `, `<path style='fill: #fff' `)"></span>
</span>
</Tooltip>
<span
v-else-if="flag"
:class="['logo-wrapper rounded-circle d-inline-flex justify-content-center align-items-center', forceShowFlag || forceShowFlagDyn ? 'logo-flag-forced' : '', flagName ? 'logo-has-flag' : '']"
:style="flagName ? `--flag: url('/flags/${flagName}.png')` : ''"
@transitionend="resetFlagIfNotOverwritten"
>
<span class="logo" v-html="svg"></span>
</span>
<span v-else class="logo" v-html="svg"></span>
</template>
<script lang="ts">
import Vue from 'vue';
import { Day } from '../src/calendar/helpers.ts';
import { calendar } from '../src/calendar/calendar.ts';
import { ImmutableArray } from '../src/helpers.ts';
interface Data {
svg: string;
flagName: string | null;
d: Day | null;
forceShowFlagDyn: boolean;
celebrate1M: boolean;
}
export default Vue.extend({
props: {
flag: { type: Boolean },
forceShowFlag: { type: Boolean },
day: { default: () => Day.today(), type: Day },
},
data(): Data {
return {
svg: process.env.LOGO!,
flagName: null,
d: this.day,
forceShowFlagDyn: false,
celebrate1M: false,
};
},
async mounted() {
this.flagName = this.selectFlag();
this.$eventHub.$on('calendar-select', (d: Day | null) => {
this.forceShowFlagDyn = !!d;
this.d = d;
// changing the flag is deferred until the transition has finished so that it does not suddenly change
if (d !== null) {
this.flagName = this.selectFlag();
}
});
const stats = await this.$axios.$get('/admin/stats-public');
if (stats && stats.overall.users >= 1_000_000 && stats.overall.users < 1_005_000) {
this.celebrate1M = true;
}
this.celebrate1M = true;
await this.fireConfetti();
},
methods: {
async fireConfetti() {
if (!this.celebrate1M) {
return;
}
await this.$loadScript('confetti', 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js');
const waitForConfetti = async () => {
return new Promise((resolve) => {
const interval = setInterval(() => {
// @ts-ignore
if (window.confetti !== undefined) {
clearInterval(interval);
resolve(true);
}
}, 100);
});
};
await waitForConfetti();
setTimeout(() => {
// @ts-ignore
const logoPos = this.$refs.confettiLogo!.getBoundingClientRect();
// @ts-ignore
window.confetti({
angle: -45,
spread: 90,
shapes: ['star'],
decay: 0.9,
origin: {
x: logoPos.x + logoPos.width / 2,
y: logoPos.y + logoPos.height / 2,
},
});
}, 1000);
},
selectFlag(): string | null {
const events = calendar.getCurrentYear()!.eventsByDate[(this.d || this.day).toString()];
if (!events) {
return null;
}
return new ImmutableArray(...events)
.filter((e) => e.flag && !e.flag.startsWith('_'))
.sorted((a, b) => b.level - a.level)
.groupBy((e) => e.level)
.indexOrFallback(0, ['0', new ImmutableArray()])[1]
.map((e) => e.flag)
.randomElement();
},
resetFlagIfNotOverwritten(): void {
if (this.d === null) {
this.flagName = this.selectFlag();
}
},
},
});
</script>
<style lang="scss">
.logo-wrapper {
width: 1.3em;
height: 1.3em;
position: relative;
overflow: hidden;
&:before {
content: ' ';
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-image: var(--flag);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
z-index: -5;
opacity: 0;
transition: all .25s ease-in-out;
}
}
.logo {
height: 1em;
width: 1em;
display: inline-block;
vertical-align: middle;
svg {
vertical-align: baseline !important;
}
}
.logo-wrapper.logo-flag-forced.logo-has-flag, a:hover .logo-wrapper.logo-has-flag {
svg path {
stroke: white;
stroke-width: 10;
}
&:before {
opacity: 1;
}
}
body[data-theme="dark"] {
.logo-wrapper.logo-flag-forced.logo-has-flag, a:hover .logo-wrapper.logo-has-flag {
svg path {
stroke: black;
}
}
}
</style>