Skip to content

Commit a1e9407

Browse files
committed
feat(panel): refactor overview, rework season picker & loading
1 parent ccb3e48 commit a1e9407

File tree

9 files changed

+325
-125
lines changed

9 files changed

+325
-125
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script lang="ts" setup>
2+
import { NH2 } from 'naive-ui';
3+
4+
import { type Component, type PropType, useAttrs } from 'vue';
5+
6+
defineProps({
7+
component: {
8+
type: Object as PropType<Component>,
9+
required: false,
10+
default: NH2,
11+
},
12+
});
13+
14+
const emit = defineEmits<{
15+
(e: 'onClick', href?: string): void;
16+
}>();
17+
18+
const attrs = useAttrs() as Record<keyof HTMLAnchorElement, string> | undefined;
19+
20+
const onTitleClick = (e: MouseEvent) => {
21+
e.preventDefault();
22+
e.stopPropagation();
23+
emit('onClick', attrs?.href);
24+
};
25+
</script>
26+
27+
<template>
28+
<a class="anchor-link" @click="onTitleClick">
29+
<component :is="component" class="hover-link">
30+
<slot />
31+
</component>
32+
</a>
33+
</template>
34+
35+
<style lang="scss" scoped>
36+
@use '~/styles/z-index' as layers;
37+
38+
.anchor-link {
39+
z-index: layers.$in-front;
40+
color: inherit;
41+
text-decoration: none;
42+
}
43+
44+
.hover-link {
45+
transition: color 0.3s var(--n-bezier);
46+
will-change: color;
47+
48+
&:hover {
49+
color: var(--trakt-red);
50+
}
51+
}
52+
</style>

src/components/common/list/ListItemPanel.vue

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ import PosterPlaceholder from '~/assets/images/poster-placholder.webp';
1515
import { type ListScrollItem } from '~/models/list-scroll.model';
1616
1717
import { useShowStore } from '~/stores/data/show.store';
18-
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
18+
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
1919
import { useI18n } from '~/utils';
20-
import { createTab } from '~/utils/browser/browser.utils';
2120
import { deCapitalise } from '~/utils/string.utils';
2221
2322
const i18n = useI18n('list', 'item', 'panel');
@@ -92,12 +91,12 @@ const tooltipOptions = computed<PopoverProps>(() => ({
9291
delay: 500,
9392
}));
9493
95-
const { openLinksInNewTab } = useExtensionSettingsStoreRefs();
94+
const { openTab } = useExtensionSettingsStore();
9695
const onTagClick = (e: MouseEvent, url?: string) => {
9796
e.preventDefault();
9897
e.stopPropagation();
9998
if (!url) return;
100-
createTab({ url, active: openLinksInNewTab.value });
99+
openTab(url);
101100
};
102101
</script>
103102

+75-61
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
<script setup lang="ts">
2-
import { NFlex, NH2, NH4, NSkeleton } from 'naive-ui';
3-
import { computed, onMounted, onUnmounted, ref, toRefs, Transition, watch } from 'vue';
2+
import { NFlex, NSkeleton } from 'naive-ui';
3+
import { computed, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
44
5-
import type { TraktEpisodeExtended } from '~/models/trakt/trakt-episode.model';
5+
import type {
6+
TraktEpisodeExtended,
7+
TraktEpisodeShort,
8+
} from '~/models/trakt/trakt-episode.model';
69
import type { TraktShowExtended } from '~/models/trakt/trakt-show.model';
710
11+
import TitleLink from '~/components/common/buttons/TitleLink.vue';
12+
import ShowPanelOverview from '~/components/views/panel/ShowPanelOverview.vue';
813
import ShowPanelPicker from '~/components/views/panel/ShowPanelPicker.vue';
914
import ShowPanelPoster from '~/components/views/panel/ShowPanelPoster.vue';
15+
import { ResolveExternalLinks } from '~/settings/external.links';
1016
import { type ShowSeasons, useShowStore } from '~/stores/data/show.store';
17+
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
1118
import { deCapitalise } from '~/utils/string.utils';
1219
1320
const props = defineProps({
@@ -25,10 +32,9 @@ const props = defineProps({
2532
},
2633
});
2734
28-
const { getShowRef, getShowSeasonsRef, getShowEpisodeRef } = useShowStore();
29-
3035
const show = ref<TraktShowExtended>();
3136
const seasons = ref<ShowSeasons>();
37+
const episodes = ref<TraktEpisodeShort[]>();
3238
const episode = ref<TraktEpisodeExtended>();
3339
3440
const { showId, seasonNumber, episodeNumber } = toRefs(props);
@@ -47,50 +53,65 @@ const episodeNb = computed(() => {
4753
return _episodeNumber;
4854
});
4955
56+
const panelType = computed<'show' | 'season' | 'episode'>(() => {
57+
if (episodeNb?.value !== undefined && seasonNb?.value !== undefined) return 'episode';
58+
if (seasonNb?.value !== undefined) return 'season';
59+
return 'show';
60+
});
61+
5062
const showTitle = computed(() => {
5163
if (!show.value?.title) return;
5264
return deCapitalise(show.value.title);
5365
});
5466
55-
const episodeTitle = computed(() => {
56-
if (!episode.value?.title) return;
57-
return deCapitalise(episode.value?.title);
58-
});
59-
6067
const subscriptions = new Set<() => void>();
6168
69+
const { getShowRef, getShowSeasonsRef, getShowSeasonEpisodesRef, getShowEpisodeRef } =
70+
useShowStore();
71+
6272
const watchData = () =>
6373
watch(
64-
props,
74+
[showId, seasonNb, episodeNb],
6575
(next, prev) => {
66-
if (next.showId !== prev?.showId) {
76+
// show changes
77+
if (next.at(0) !== prev?.at(0)) {
6778
show.value = undefined;
6879
seasons.value = undefined;
80+
episodes.value = undefined;
6981
episode.value = undefined;
70-
} else if (next.episodeNumber !== prev?.episodeNumber) {
82+
}
83+
// season changes
84+
else if (next.at(1) !== prev?.at(1)) {
7185
episode.value = undefined;
72-
} else if (next.seasonNumber !== prev?.seasonNumber) {
86+
episodes.value = undefined;
87+
}
88+
// episode changes
89+
else if (next.at(2) !== prev?.at(2)) {
7390
episode.value = undefined;
7491
}
7592
76-
if (showId?.value) subscriptions.add(getShowRef(showId.value, show).unsub);
77-
if (showId?.value)
93+
if (showId?.value) {
94+
subscriptions.add(getShowRef(showId.value, show).unsub);
7895
subscriptions.add(getShowSeasonsRef(showId.value, seasons).unsub);
79-
if (
80-
showId?.value &&
81-
seasonNb?.value !== undefined &&
82-
episodeNb?.value !== undefined
83-
) {
84-
subscriptions.add(
85-
getShowEpisodeRef(
86-
{
87-
id: showId.value,
88-
season: seasonNb.value,
89-
episode: episodeNb.value,
90-
},
91-
episode,
92-
).unsub,
93-
);
96+
97+
if (seasonNb?.value !== undefined) {
98+
subscriptions.add(
99+
getShowSeasonEpisodesRef(showId.value, seasonNb?.value, episodes).unsub,
100+
);
101+
}
102+
103+
if (seasonNb?.value !== undefined && episodeNb?.value !== undefined) {
104+
subscriptions.add(
105+
getShowEpisodeRef(
106+
{
107+
id: showId.value,
108+
season: seasonNb.value,
109+
episode: episodeNb.value,
110+
},
111+
episode,
112+
).unsub,
113+
);
114+
}
94115
}
95116
},
96117
{ immediate: true },
@@ -104,41 +125,40 @@ onUnmounted(() => {
104125
subscriptions.forEach(unsub => unsub());
105126
subscriptions.clear();
106127
});
128+
129+
const { openTab } = useExtensionSettingsStore();
130+
131+
const titleUrl = computed(() => {
132+
if (!show.value?.ids?.trakt) return;
133+
return ResolveExternalLinks.search({
134+
type: 'show',
135+
source: 'trakt',
136+
id: show.value.ids.trakt,
137+
});
138+
});
107139
</script>
108140

109141
<template>
110142
<NFlex justify="center" align="center" vertical>
111-
<NH2 v-if="showTitle" class="show-title">{{ showTitle }}</NH2>
112-
<NSkeleton v-else />
143+
<TitleLink v-if="showTitle" class="show-title" :href="titleUrl" @on-click="openTab">
144+
{{ showTitle }}
145+
</TitleLink>
146+
<NSkeleton v-else class="show-title-skeleton" style="width: 50dvh" round />
113147

114-
<ShowPanelPicker :seasons="seasons" :season-number="seasonNb" />
148+
<ShowPanelPicker :seasons="seasons" :episodes="episodes" :mode="panelType" />
115149

116150
<ShowPanelPoster
117151
:show-id="show?.ids.tmdb"
118152
:season-number="seasonNb"
119153
:episode-number="episodeNb"
120154
/>
121155

122-
<Transition name="scale" mode="out-in">
123-
<NFlex
124-
v-if="episodeNumber !== undefined"
125-
:key="`season-${seasonNb}-episode-${episodeNb}`"
126-
justify="center"
127-
align="center"
128-
vertical
129-
class="episode-container"
130-
>
131-
<NH4 v-if="episodeTitle" class="episode-title">{{ episodeTitle }}</NH4>
132-
<NSkeleton v-else />
133-
134-
<div v-if="episode">{{ episode?.overview }}</div>
135-
<NSkeleton v-else />
136-
</NFlex>
137-
</Transition>
156+
<ShowPanelOverview v-if="panelType === 'episode'" :episode="episode" />
138157
</NFlex>
139158
</template>
140159

141160
<style lang="scss" scoped>
161+
@use '~/styles/z-index' as layers;
142162
@use '~/styles/transition' as transition;
143163
@include transition.scale;
144164
@@ -156,19 +176,13 @@ onUnmounted(() => {
156176
box-shadow: var(--image-box-shadow);
157177
}
158178
159-
.show-title {
179+
.show-title:deep(h2),
180+
.show-title-skeleton {
160181
margin-bottom: 0.5rem;
161182
}
162183
163-
.episode {
164-
&-container {
165-
width: 100%;
166-
}
167-
168-
&-title {
169-
margin-top: 1rem;
170-
margin-bottom: 1rem;
171-
font-weight: bold;
172-
}
184+
.show-title-skeleton {
185+
height: 1.5rem;
186+
margin-top: 0.625rem;
173187
}
174188
</style>

0 commit comments

Comments
 (0)