mirror of
https://github.com/maciejpedzich/spotifyplaylistarchive.com.git
synced 2024-09-19 18:16:19 +02:00
Create a separate SnapshotCalendar component
This commit is contained in:
parent
0e0a7683b3
commit
9197910e57
144
components/snapshot/Calendar.vue
Normal file
144
components/snapshot/Calendar.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<script setup lang="ts">
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
import ProgressSpinner from 'primevue/progressspinner';
|
||||
import Calendar from 'primevue/calendar';
|
||||
|
||||
import { Snapshot } from '~~/models/snapshot';
|
||||
|
||||
// Moving these interfaces to separate files makes TypeScript scream at you
|
||||
interface PrimeVueDate {
|
||||
day: number;
|
||||
month: number;
|
||||
year: number;
|
||||
today: boolean;
|
||||
otherMonth: boolean;
|
||||
}
|
||||
|
||||
interface DateChangePayload {
|
||||
month: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
defineProps<{ page: 'snapshot-pick' | 'compare' }>();
|
||||
|
||||
const route = useRoute();
|
||||
const playlistId = route.params.playlistId;
|
||||
|
||||
const isPortableScreen = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
const calendarDisplayDate = ref(new Date());
|
||||
const queryMonth = ref(calendarDisplayDate.value.getMonth());
|
||||
const queryYear = ref(calendarDisplayDate.value.getFullYear());
|
||||
|
||||
const hoursOffset = -(calendarDisplayDate.value.getTimezoneOffset() / 60);
|
||||
const sinceDateParam = computed(() =>
|
||||
new Date(queryYear.value, queryMonth.value, 1, hoursOffset).toISOString()
|
||||
);
|
||||
const untilDateParam = computed(() =>
|
||||
new Date(queryYear.value, queryMonth.value + 1, 1, hoursOffset).toISOString()
|
||||
);
|
||||
|
||||
const queryString = computed(() => {
|
||||
const queryParamsObj = new URLSearchParams();
|
||||
|
||||
queryParamsObj.set('sinceDate', sinceDateParam.value);
|
||||
queryParamsObj.set('untilDate', untilDateParam.value);
|
||||
|
||||
return queryParamsObj.toString();
|
||||
});
|
||||
|
||||
const {
|
||||
pending: loadingCalendarEntries,
|
||||
error: calendarEntriesLoadError,
|
||||
data: calendarEntries,
|
||||
refresh: reloadCalendarEntries
|
||||
} = useFetch<Snapshot[]>(
|
||||
() => `/api/playlists/${playlistId}/snapshots?${queryString.value}`,
|
||||
{
|
||||
key: `snapshots-calendar-of-${playlistId}`,
|
||||
server: false
|
||||
}
|
||||
);
|
||||
|
||||
const snapshotLinkMap = computed<Record<string, string>>(() =>
|
||||
(calendarEntries.value || []).reduce((map, entry) => {
|
||||
const dateCapturedKey = entry.dateCaptured.substring(0, 10);
|
||||
map[dateCapturedKey] = `./snapshots/show/${entry.commitSha}`;
|
||||
|
||||
return map;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const updateQueryAndReload = async ({ month, year }: DateChangePayload) => {
|
||||
queryMonth.value = month;
|
||||
queryYear.value = year;
|
||||
|
||||
await reloadCalendarEntries();
|
||||
};
|
||||
|
||||
const primeVueDateToString = ({ year, month, day }: PrimeVueDate) =>
|
||||
// PrimeVue Calendar dates' months are zero-based
|
||||
// Just like in JavaScript date objects
|
||||
[year, month + 1, day]
|
||||
.map((num) => num.toString().padStart(2, '0'))
|
||||
.join('-');
|
||||
|
||||
const getSnapshotLinkFromDate = (calendarDate: PrimeVueDate) =>
|
||||
snapshotLinkMap.value[primeVueDateToString(calendarDate)];
|
||||
|
||||
const isDateCaptured = (calendarDate: PrimeVueDate) =>
|
||||
!!getSnapshotLinkFromDate(calendarDate);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLayout name="centered-content">
|
||||
<ClientOnly>
|
||||
<ProgressSpinner
|
||||
v-if="loadingCalendarEntries"
|
||||
class="abosulte top-0 right-0"
|
||||
/>
|
||||
<p
|
||||
v-else-if="!loadingCalendarEntries && calendarEntriesLoadError"
|
||||
class="text-2xl"
|
||||
>
|
||||
Something went wrong while fetching archive entries
|
||||
</p>
|
||||
<!-- Changing v-show to v-if breaks month/year change handling -->
|
||||
<Calendar
|
||||
v-show="!loadingCalendarEntries"
|
||||
v-model="calendarDisplayDate"
|
||||
:disabled="loadingCalendarEntries"
|
||||
:touch-u-i="isPortableScreen"
|
||||
inline
|
||||
@month-change="updateQueryAndReload"
|
||||
@year-change="updateQueryAndReload"
|
||||
>
|
||||
<template #date="{ date }">
|
||||
<NuxtLink
|
||||
v-if="isDateCaptured(date as PrimeVueDate)"
|
||||
:to="getSnapshotLinkFromDate(date as PrimeVueDate)"
|
||||
>
|
||||
<span class="bg-primary hover:bg-green-400 text-0 p-3 border-round">
|
||||
{{ (date as PrimeVueDate).day }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<span v-else>
|
||||
{{ (date as PrimeVueDate).day }}
|
||||
</span>
|
||||
</template>
|
||||
</Calendar>
|
||||
</ClientOnly>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-datepicker table td.p-datepicker-today > span.p-highlight) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
:deep(.p-datepicker table.p-datepicker-calendar td > span.p-highlight) {
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
@ -1,141 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
import ProgressSpinner from 'primevue/progressspinner';
|
||||
import Calendar from 'primevue/calendar';
|
||||
|
||||
import { Snapshot } from '~~/models/snapshot';
|
||||
|
||||
// Moving these interfaces to separate files makes TypeScript scream at you
|
||||
interface PrimeVueDate {
|
||||
day: number;
|
||||
month: number;
|
||||
year: number;
|
||||
today: boolean;
|
||||
otherMonth: boolean;
|
||||
}
|
||||
|
||||
interface DateChangePayload {
|
||||
month: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const playlistId = route.params.playlistId;
|
||||
|
||||
const isPortableScreen = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
const calendarDisplayDate = ref(new Date());
|
||||
const queryMonth = ref(calendarDisplayDate.value.getMonth());
|
||||
const queryYear = ref(calendarDisplayDate.value.getFullYear());
|
||||
|
||||
const hoursOffset = -(calendarDisplayDate.value.getTimezoneOffset() / 60);
|
||||
const sinceDateParam = computed(() =>
|
||||
new Date(queryYear.value, queryMonth.value, 1, hoursOffset).toISOString()
|
||||
);
|
||||
const untilDateParam = computed(() =>
|
||||
new Date(queryYear.value, queryMonth.value + 1, 1, hoursOffset).toISOString()
|
||||
);
|
||||
|
||||
const queryString = computed(() => {
|
||||
const queryParamsObj = new URLSearchParams();
|
||||
|
||||
queryParamsObj.set('sinceDate', sinceDateParam.value);
|
||||
queryParamsObj.set('untilDate', untilDateParam.value);
|
||||
|
||||
return queryParamsObj.toString();
|
||||
});
|
||||
|
||||
const {
|
||||
pending: loadingCalendarEntries,
|
||||
error: calendarEntriesLoadError,
|
||||
data: calendarEntries,
|
||||
refresh: reloadCalendarEntries
|
||||
} = useFetch<Snapshot[]>(
|
||||
() => `/api/playlists/${playlistId}/snapshots?${queryString.value}`,
|
||||
{
|
||||
key: `snapshots-calendar-of-${playlistId}`,
|
||||
server: false
|
||||
}
|
||||
);
|
||||
|
||||
const snapshotLinkMap = computed<Record<string, string>>(() =>
|
||||
(calendarEntries.value || []).reduce((map, entry) => {
|
||||
const dateCapturedKey = entry.dateCaptured.substring(0, 10);
|
||||
map[dateCapturedKey] = `./snapshots/show/${entry.commitSha}`;
|
||||
|
||||
return map;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const primeVueDateToString = ({ year, month, day }: PrimeVueDate) =>
|
||||
// PrimeVue Calendar dates' months are zero-based
|
||||
// Just like in JavaScript date objects
|
||||
[year, month + 1, day]
|
||||
.map((num) => num.toString().padStart(2, '0'))
|
||||
.join('-');
|
||||
|
||||
const getSnapshotLinkFromDate = (calendarDate: PrimeVueDate) =>
|
||||
snapshotLinkMap.value[primeVueDateToString(calendarDate)];
|
||||
|
||||
const isDateCaptured = (calendarDate: PrimeVueDate) =>
|
||||
!!getSnapshotLinkFromDate(calendarDate);
|
||||
|
||||
const updateQueryAndReload = async ({ month, year }: DateChangePayload) => {
|
||||
queryMonth.value = month;
|
||||
queryYear.value = year;
|
||||
|
||||
await reloadCalendarEntries();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="md:text-lg text-base">
|
||||
<p class="mt-0 text-center">
|
||||
Click on a highlighted date to show a snapshot captured that day.
|
||||
</p>
|
||||
<ClientOnly>
|
||||
<ProgressSpinner
|
||||
v-if="loadingCalendarEntries"
|
||||
class="abosulte top-0 right-0"
|
||||
/>
|
||||
<p
|
||||
v-else-if="!loadingCalendarEntries && calendarEntriesLoadError"
|
||||
class="text-2xl"
|
||||
>
|
||||
Something went wrong while fetching archive entries
|
||||
</p>
|
||||
<!-- Changing v-show to v-if breaks month/year change handling -->
|
||||
<Calendar
|
||||
v-show="!loadingCalendarEntries"
|
||||
v-model="calendarDisplayDate"
|
||||
:disabled="loadingCalendarEntries"
|
||||
:touch-u-i="isPortableScreen"
|
||||
inline
|
||||
@month-change="updateQueryAndReload"
|
||||
@year-change="updateQueryAndReload"
|
||||
>
|
||||
<template #date="{ date }">
|
||||
<NuxtLink
|
||||
v-if="isDateCaptured(date as PrimeVueDate)"
|
||||
:to="getSnapshotLinkFromDate(date as PrimeVueDate)"
|
||||
>
|
||||
<span class="bg-primary hover:bg-green-400 text-0 p-3 border-round">
|
||||
{{ (date as PrimeVueDate).day }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<span v-else>
|
||||
{{ (date as PrimeVueDate).day }}
|
||||
</span>
|
||||
</template>
|
||||
</Calendar>
|
||||
<SnapshotCalendar page="snapshot-pick" />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-datepicker table td.p-datepicker-today > span.p-highlight) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
:deep(.p-datepicker table.p-datepicker-calendar td > span.p-highlight) {
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user