Skip to content

Commit d683592

Browse files
committed
fix(image): switch to support backdrops in addition to posters
1 parent 8f3cd5d commit d683592

File tree

4 files changed

+98
-46
lines changed

4 files changed

+98
-46
lines changed

src/components/common/list/ListItem.vue

+10-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,15 @@ const loading = computed(() => item?.value?.loading);
9898
9999
const { getImageUrl } = useImageStore();
100100
101-
const resolvedPoster = computed(() => item.value.poster?.value || poster?.value);
101+
const resolvedPoster = computed(() => {
102+
if (poster?.value) return poster.value;
103+
const image = item.value.poster?.value;
104+
if (!image) return;
105+
if (typeof image === 'string') return image;
106+
if (episode.value && 'backdrop' in image) return image.backdrop;
107+
return image.poster;
108+
});
109+
102110
const objectFit = computed(() =>
103111
resolvedPoster.value === PosterPlaceholder ? 'contain' : 'cover',
104112
);
@@ -159,6 +167,7 @@ const ListScrollItemTypeLocal = ListScrollItemType;
159167
'--list-item-height': height ? `${height}px` : undefined,
160168
}"
161169
:data-key="item.id"
170+
:data-index="item.index"
162171
:line-type="loading ? 'dashed' : lineType"
163172
:color="loading ? 'grey' : color"
164173
@mouseenter="onHover(true)"

src/components/common/list/ListScroll.model.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { TraktMovie } from '~/models/trakt/trakt-movie.model';
66
import type { TraktPerson } from '~/models/trakt/trakt-people.model';
77
import type { TraktSeason } from '~/models/trakt/trakt-season.model';
88
import type { TraktShow } from '~/models/trakt/trakt-show.model';
9-
import type { ImageQuery } from '~/stores/data/image.store';
9+
import type { ImageQuery, ImageStoreMedias } from '~/stores/data/image.store';
1010

1111
export type VirtualListRef = VirtualListInst & InstanceType<typeof NVirtualList>;
1212
export type VirtualListProps = {
@@ -23,7 +23,7 @@ export type OnUpdated = (listRef: Ref<VirtualListRef | undefined>) => void;
2323

2424
export type ListScrollSourceItem = {
2525
id: string | number;
26-
type?: keyof typeof ListScrollItemType;
26+
type?: ListScrollItem['type'];
2727

2828
movie?: TraktMovie<'short'>;
2929
show?: TraktShow<'short'>;
@@ -60,9 +60,11 @@ export type ListScrollItem = {
6060
content?: string;
6161
tags?: ListScrollItemTag[];
6262

63-
poster?: Ref<string | undefined>;
63+
poster?: Ref<ImageStoreMedias | undefined>;
6464
getPosterQuery?: () => ImageQuery | undefined;
6565

66+
meta?: Record<string, unknown>;
67+
6668
loading?: boolean;
6769
date?: {
6870
previous?: Date;

src/components/common/list/use-list-scroll.ts

+9
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ export const useListScroll = <D extends string, T extends ListScrollSourceItemWi
100100
if (!_item.poster) _item.poster = ref<string | undefined>(undefined);
101101
if (!_item.getPosterQuery) _item.getPosterQuery = getPosterQuery(item, _item.type);
102102
_item.date = getDate(item, array, index, dateFn);
103+
_item.meta = {
104+
ids: {
105+
movie: item.movie?.ids,
106+
show: item.show?.ids,
107+
season: item.season?.ids,
108+
episode: item.episode?.ids,
109+
person: item.person?.ids,
110+
},
111+
};
103112

104113
return _item;
105114
});

src/stores/data/image.store.ts

+74-42
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,35 @@ import { storage } from '~/utils/browser/browser-storage.utils';
1111
import { debounce } from '~/utils/debounce.utils';
1212
import { arrayMax, findClosestMatch } from '~/utils/math.utils';
1313

14+
type ImageStoreMedia = {
15+
poster?: string;
16+
backdrop?: string;
17+
};
18+
1419
type ImageStore = {
15-
movie: Record<string, string>;
16-
show: Record<string, string>;
20+
movie: Record<string, ImageStoreMedia>;
21+
show: Record<string, ImageStoreMedia>;
1722
season: Record<string, string>;
1823
episode: Record<string, string>;
1924
person: Record<string, string>;
2025
};
2126

27+
type ImageStoreTypes = keyof ImageStore;
28+
29+
export type ImageStoreMedias = ImageStoreMedia | string;
30+
2231
export type ImageQuery = {
2332
id: number;
2433
season?: number;
2534
episode?: number;
26-
type: keyof ImageStore;
35+
type: ImageStoreTypes;
2736
};
2837

2938
type ImagePayload = {
30-
posters?: TmdbImage[];
31-
stills?: TmdbImage[];
32-
profiles?: TmdbImage[];
39+
posters?: TmdbImage[]; // movie, show, season
40+
backdrops?: TmdbImage[]; // movie or shows
41+
stills?: TmdbImage[]; // episodes
42+
profiles?: TmdbImage[]; // profiles
3343
};
3444

3545
const EmptyImageStore: ImageStore = {
@@ -47,22 +57,22 @@ export const useImageStore = defineStore('data.image', () => {
4757
const syncSaveImageStore = debounce(
4858
(_images = images) =>
4959
Promise.all([
50-
storage.sync.set(`data.image-store.movie`, _images.movie),
51-
storage.sync.set(`data.image-store.show`, _images.show),
52-
storage.sync.set(`data.image-store.season`, _images.season),
53-
storage.sync.set(`data.image-store.episode`, _images.episode),
54-
storage.sync.set(`data.image-store.person`, _images.person),
60+
storage.local.set(`data.image-store.movie`, _images.movie),
61+
storage.local.set(`data.image-store.show`, _images.show),
62+
storage.local.set(`data.image-store.season`, _images.season),
63+
storage.local.set(`data.image-store.episode`, _images.episode),
64+
storage.local.set(`data.image-store.person`, _images.person),
5565
]),
5666
1000,
5767
);
5868

5969
const syncRestoreImageStore = async (seed?: Partial<ImageStore>) => {
6070
const [movie, show, season, episode, person] = await Promise.all([
61-
storage.sync.get<Record<string, string>>(`data.image-store.movie`),
62-
storage.sync.get<Record<string, string>>(`data.image-store.show`),
63-
storage.sync.get<Record<string, string>>(`data.image-store.season`),
64-
storage.sync.get<Record<string, string>>(`data.image-store.episode`),
65-
storage.sync.get<Record<string, string>>(`data.image-store.person`),
71+
storage.local.get<Record<string, string>>(`data.image-store.movie`),
72+
storage.local.get<Record<string, string>>(`data.image-store.show`),
73+
storage.local.get<Record<string, string>>(`data.image-store.season`),
74+
storage.local.get<Record<string, string>>(`data.image-store.episode`),
75+
storage.local.get<Record<string, string>>(`data.image-store.person`),
6676
]);
6777
if (seed) Object.assign(images, seed);
6878
if (movie) Object.assign(images.movie, movie);
@@ -102,7 +112,7 @@ export const useImageStore = defineStore('data.image', () => {
102112
const fetchImageUrl = async (
103113
key: string,
104114
{ id, season, episode, type }: ImageQuery,
105-
): Promise<{ image: string; key: string; type: ImageQuery['type'] } | undefined> => {
115+
): Promise<{ image: ImageStoreMedias; key: string; type: ImageQuery['type'] } | undefined> => {
106116
let payload: ImagePayload;
107117
if (type === 'movie') {
108118
payload = await queueRequest(key, () => TraktService.posters.movie(id));
@@ -116,22 +126,34 @@ export const useImageStore = defineStore('data.image', () => {
116126
payload = await queueRequest(key, () => TraktService.posters.show(id));
117127
} else throw new Error('Unsupported type or missing parameters for fetchImageUrl');
118128

119-
const fetchedImages = payload.posters ?? payload.stills ?? payload.profiles;
120-
if (!fetchedImages?.length) {
121-
if (type === 'episode') {
122-
const eType = 'season';
123-
const eKey = `${eType}-${id}-${season}`;
124-
if (images[eType][eKey]) return { image: images[eType][eKey], key: eKey, type: eType };
125-
return fetchImageUrl(eKey, { id, season, type: eType });
126-
}
127-
if (type === 'season') {
128-
const sType = 'show';
129-
const sKey = `${sType}-${id}`;
130-
if (images[sType][sKey]) return { image: images[sType][sKey], key: sKey, type: sType };
131-
return fetchImageUrl(sKey, { id, type: sType });
132-
}
133-
return;
129+
if ((type === 'episode' && !payload.stills?.length) || (type === 'season' && !payload.posters?.length)) {
130+
const sType = 'show';
131+
const sKey = `${sType}-${id}`;
132+
if (images[sType][sKey]) return { image: images[sType][sKey], key: sKey, type: sType };
133+
return fetchImageUrl(sKey, { id, type: sType });
134134
}
135+
136+
if (['movie', 'show'].includes(type)) {
137+
if (!payload.backdrops?.length && !payload.posters?.length) return;
138+
const image = {
139+
poster: arrayMax(payload.posters ?? [], 'vote_average', i => !!i.file_path)?.file_path,
140+
backdrop: arrayMax(payload.backdrops ?? [], 'vote_average', i => !!i.file_path)?.file_path,
141+
};
142+
if (!image.poster && !image.backdrop) return;
143+
images[type][key] = image;
144+
145+
syncSaveImageStore().catch(err => console.error('Failed to save image store', err));
146+
return { image, key, type };
147+
}
148+
149+
if (type === 'person' && !payload.profiles?.length) return;
150+
if (type === 'season' && !payload.posters?.length) return;
151+
if (type === 'episode' && !payload.stills?.length) return;
152+
153+
const fetchedImages = payload.profiles ?? payload.posters ?? payload.stills;
154+
155+
if (!fetchedImages?.length) return;
156+
135157
const image = arrayMax(fetchedImages, 'vote_average', i => !!i.file_path)?.file_path;
136158
if (!image) return;
137159
images[type][key] = image;
@@ -145,24 +167,34 @@ export const useImageStore = defineStore('data.image', () => {
145167
return findClosestMatch(size, imageSizes.value.poster);
146168
};
147169

148-
const getImageUrl = async (query: ImageQuery, size: number, response: Ref<string | undefined> = ref()) => {
170+
const setResponseValue = (
171+
{ image, baseUrl, type, size }: { image: ImageStoreMedias; baseUrl: string; type: ImageQuery['type']; size: number },
172+
response: Ref<ImageStoreMedias | undefined> = ref(),
173+
) => {
174+
if (typeof image === 'string') response.value = `${baseUrl}${getImageSize(type, size)}${image}`;
175+
else {
176+
response.value = {
177+
poster: image.poster ? `${baseUrl}${getImageSize(type, size)}${image.poster}` : undefined,
178+
backdrop: image.backdrop ? `${baseUrl}${getImageSize(type, size)}${image.backdrop}` : undefined,
179+
};
180+
}
181+
return response;
182+
};
183+
184+
const getImageUrl = async (query: ImageQuery, size: number, response: Ref<ImageStoreMedias | undefined> = ref()) => {
149185
if (!tmdbConfig.value) throw new Error('TmdbConfiguration not initialized');
150186
if (!tmdbConfig.value?.images?.secure_base_url) throw new Error('TmdbConfiguration missing secure_base_url');
151187

152188
const { key, type } = getKeyAndType(query);
153189

154190
const baseUrl = tmdbConfig.value.images.secure_base_url;
155191

156-
if (images[type][key]) {
157-
response.value = `${baseUrl}${getImageSize(type, size)}${images[type][key]}`;
158-
return response;
159-
}
160-
161-
const image = await fetchImageUrl(key, query);
162-
if (!image) return response;
163-
response.value = `${baseUrl}${getImageSize(image.type, size)}${image.image}`;
192+
const image = images[type][key];
193+
if (image) return setResponseValue({ image, baseUrl, type, size }, response);
164194

165-
return response;
195+
const result = await fetchImageUrl(key, query);
196+
if (!result?.image) return response;
197+
return setResponseValue({ image: result.image, baseUrl, type, size }, response);
166198
};
167199

168200
return { initImageStore, getImageUrl, imageSizes };

0 commit comments

Comments
 (0)