<template> <tr :class="rowClass" v-bind="attrs"> <td> <router-link :to="{ name: 'show-episode', params: { showId: timeslot.showId, episodeId: timeslot.id }, }" class="tw-text-inherit tw-flex tw-gap-3 tw-items-center hocus:tw-underline hocus:tw-outline-none" :title="t('showTimeslots.editDescription')" > <div class="tw-w-12 tw-flex-none tw-aspect-square rounded-lg tw-bg-gray-400 tw-overflow-hidden" > <img v-if="noteImage" class="tw-object-cover tw-h-full tw-w-full tw-max-w-full tw-max-h-full" :src="noteImage.image" sizes="100px" :srcset=" noteImage.thumbnails .filter(({ width, height }) => width === height) .map(({ width, url }) => `${url} ${width}w`) .join(',') " alt="" /> </div> <span class="tw-truncate tw-max-w-[200px]"> <template v-if="!isLoadingNote"> <template v-if="note?.title"> {{ note.title }} </template> <span v-else class="tw-text-sm tw-text-gray-500"> {{ t('noneSetMasculine') }} </span> </template> </span> </router-link> </td> <td> {{ prettyDateTime(timeslot.start) }} </td> <td> {{ secondsToDurationString(duration) }} </td> <td> <AStatus class="tw-text-xs" rounded :is-warning="playlistState.state !== 'ok'" :is-success="playlistState.state === 'ok'" > {{ t(`playlist.state.${playlistState.state}.title`) }} </AStatus> </td> <td class="tw-relative tw-p-0" :class="{ 'tw-w-6': isOnAir || isNextUp }"> <div v-if="isNextUp || isOnAir" class="tw-absolute tw-right-0 tw-inset-y-0 tw-w-6 tw-leading-0 tw-px-[2px] tw-select-none tw-text-white tw-flex tw-items-center tw-text-sm tw-font-bold tw-whitespace-nowrap tw-pointer-events-none" :class="{ 'tw-bg-green-400': isNextUp, 'tw-bg-indigo-400': isOnAir }" > <span style="writing-mode: vertical-lr"> <template v-if="isOnAir">On Air</template> <template v-else-if="isNextUp">Next Up</template> </span> </div> </td> </tr> </template> <script lang="ts" setup> import { useObjectFromStore } from '@rokoli/bnb/drf' import { parseISO } from 'date-fns' import { computed, useAttrs } from 'vue' import { useI18n } from '@/i18n' import { TimeSlot } from '@/types' import { useNoteStore } from '@/stores/notes' import { useImage } from '@/stores/images' import { usePretty } from '@/mixins/prettyDate' import { calculateDurationSeconds, secondsToDurationString } from '@/util' import { usePlaylistStore } from '@/stores' import { usePlaylistState } from '@/stores/playlists' import AStatus from '@/components/generic/AStatus.vue' defineOptions({ compatConfig: { MODE: 3 }, inheritAttrs: false, ATTR_FALSE_VALUE: false, }) const props = defineProps<{ timeslot: TimeSlot isNextUp?: boolean isOnAir?: boolean }>() const attrs = useAttrs() const { t } = useI18n() const { prettyDateTime } = usePretty() const playlistStore = usePlaylistStore() const noteStore = useNoteStore() const { obj: note, isLoading: isLoadingNote } = useObjectFromStore( () => props.timeslot.noteId, noteStore, ) const noteImage = useImage(computed(() => note.value?.imageId ?? null)) const duration = computed(() => calculateDurationSeconds(props.timeslot.start, props.timeslot.end)) const { obj: playlist } = useObjectFromStore(() => props.timeslot.playlistId, playlistStore) const playlistState = usePlaylistState(playlist, duration) const rowClass = computed(() => { const now = new Date() const startDate = parseISO(props.timeslot.start) const endDate = new Date(startDate.getTime() + duration.value * 1000) return { 'tw-opacity-50': now > endDate } }) </script>