Skip to content

Commit d3cee27

Browse files
committed
feat(image): init image store
1 parent 51d818d commit d3cee27

18 files changed

+309
-63
lines changed

src/components/common/list/ListItem.vue

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { NFlex, NImage, NSkeleton, NTime, NTimelineItem } from 'naive-ui';
33
4-
import { computed, type PropType } from 'vue';
4+
import { computed, type PropType, toRefs } from 'vue';
55
66
import type { ListScrollItem } from '~/components/common/list/ListScroll.model';
77
@@ -59,26 +59,31 @@ const emit = defineEmits<{
5959
(e: 'onHover', event: { index: number; item: ListScrollItem; hover: boolean }): void;
6060
}>();
6161
62-
const onHover = (hover: boolean) => {
63-
emit('onHover', { index: props.index, item: props.item, hover });
62+
const { item, size, index, noHeader, nextHasHeader, poster } = toRefs(props);
63+
64+
const onHover = (_hover: boolean) => {
65+
emit('onHover', { index: index?.value, item: item?.value, hover: _hover });
6466
};
6567
66-
const noHeader = computed(() => props.noHeader || props.item.date?.sameDayAsPrevious);
68+
const noHead = computed(() => noHeader.value || item?.value?.date?.sameDayAsPrevious);
6769
const nextHasHead = computed(
68-
() => props.nextHasHeader || !props.item.date?.sameDayAsNext,
70+
() => nextHasHeader.value || !item?.value?.date?.sameDayAsNext,
6971
);
70-
const date = computed(() => props.item.date?.current);
72+
const date = computed(() => item?.value?.date?.current);
73+
7174
const year = new Date().getFullYear();
7275
const sameYear = computed(() => date.value?.getFullYear() === year);
73-
const loading = computed(() => props.item.loading);
76+
const loading = computed(() => item?.value?.loading);
77+
78+
const resolvedPoster = computed(() => item?.value?.poster?.value || poster.value);
7479
</script>
7580

7681
<template>
7782
<NTimelineItem
7883
:key="item.id"
7984
class="timeline-item"
8085
:class="{
81-
'no-header': noHeader,
86+
'no-header': noHead,
8287
'next-has-header': nextHasHead,
8388
'show-date': !hideDate,
8489
}"
@@ -98,7 +103,7 @@ const loading = computed(() => props.item.loading);
98103
<template #default>
99104
<NFlex
100105
class="content"
101-
:class="{ 'no-border': noHeader }"
106+
:class="{ 'no-border': noHead }"
102107
size="small"
103108
:theme-overrides="{ gapSmall: '0' }"
104109
:wrap="false"
@@ -113,7 +118,7 @@ const loading = computed(() => props.item.loading);
113118
size="small"
114119
:theme-overrides="{ gapSmall: '0.25rem' }"
115120
>
116-
<template v-if="date && !noHeader && !loading">
121+
<template v-if="date && !noHead && !loading">
117122
<NTime class="month" :time="date" format="MMM" />
118123
<NTime class="day" :time="date" format="dd" />
119124
<NTime class="week" :time="date" format="eee" />
@@ -131,8 +136,8 @@ const loading = computed(() => props.item.loading);
131136
class="poster"
132137
lazy
133138
preview-disabled
134-
:src="poster"
135-
:preview-src="poster"
139+
:src="resolvedPoster"
140+
:preview-src="resolvedPoster"
136141
:fallback-src="PosterPlaceholder"
137142
/>
138143
<ListItemPanel :item="item" :loading="loading">
@@ -229,7 +234,7 @@ const loading = computed(() => props.item.loading);
229234
230235
.poster {
231236
justify-content: center;
232-
width: 5.5rem;
237+
min-width: 5.5rem;
233238
height: 8rem;
234239
background-color: #111;
235240
}

src/components/common/list/ListItemPanel.vue

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { computed, type PropType, toRefs } from 'vue';
66
import type { ListScrollItem } from '~/components/common/list/ListScroll.model';
77
88
import PosterPlaceholder from '~/assets/images/poster-placholder.webp';
9+
import { useI18n } from '~/utils';
10+
11+
const i18n = useI18n('list-item-panel');
912
1013
const props = defineProps({
1114
item: {
@@ -25,6 +28,10 @@ const props = defineProps({
2528
2629
const { item } = toRefs(props);
2730
31+
const type = computed(() =>
32+
item.value.type ? i18n(item.value.type, 'common', 'media', 'type') : item.value.type,
33+
);
34+
2835
const title = computed(() => {
2936
const media = item.value;
3037
if (media.movie) return media.movie.title;
@@ -42,13 +49,6 @@ const content = computed(() => {
4249
4350
const currentDate = computed(() => item.value.date?.current);
4451
const date = computed(() => currentDate.value?.toLocaleTimeString());
45-
46-
const type = computed(() => {
47-
const media = item.value;
48-
if ('movie' in media) return 'Movie';
49-
if (!media.episode) return 'Show';
50-
return 'Episode';
51-
});
5252
</script>
5353

5454
<template>

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { NVirtualList, VirtualListInst } from 'naive-ui';
22
import type { Ref } from 'vue';
3+
import type { TraktEpisode } from '~/models/trakt/trakt-episode.model';
4+
import type { TraktMovie } from '~/models/trakt/trakt-movie.model';
5+
import type { TraktSeason } from '~/models/trakt/trakt-season.model';
6+
import type { TraktShow } from '~/models/trakt/trakt-show.model';
37

48
export type VirtualListRef = VirtualListInst & InstanceType<typeof NVirtualList>;
59
export type VirtualListProps = {
@@ -17,9 +21,14 @@ export type ListScrollItem = {
1721
id: string | number;
1822
index: number;
1923

20-
movie?: { title: string; year: number };
21-
show?: { title: string; year: number };
22-
episode?: { title: string; season: number; number: number };
24+
type?: 'movie' | 'show' | 'season' | 'episode';
25+
26+
movie?: TraktMovie<'short'>;
27+
show?: TraktShow<'short'>;
28+
season?: TraktSeason<'short'>;
29+
episode?: TraktEpisode<'short'>;
30+
31+
poster?: Ref<string | undefined>;
2332

2433
loading?: boolean;
2534
date?: {

src/components/views/history/HistoryComponent.vue

+24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
import FloatingButton from '~/components/common/buttons/FloatingButton.vue';
1212
import ListScroll from '~/components/common/list/ListScroll.vue';
1313
import { useHistoryStore, useHistoryStoreRefs } from '~/stores/data/history.store';
14+
import { useImageStore } from '~/stores/data/image.store';
1415
import { useUserSettingsStoreRefs } from '~/stores/settings/user.store';
1516
import { useI18n } from '~/utils';
1617
@@ -43,11 +44,33 @@ onMounted(() => {
4344
});
4445
});
4546
47+
const { getImageUrl } = useImageStore();
48+
4649
const history = computed<ListScrollItem[]>(() => {
4750
const array = filteredHistory.value;
4851
if (!array.length) return [];
4952
return array.map((item, index) => {
5053
const _item: ListScrollItem = { ...item, index, loading: item.id < 0 };
54+
55+
if ('movie' in _item) _item.type = 'movie';
56+
else if ('episode' in _item) _item.type = 'episode';
57+
else if ('season' in _item) _item.type = 'season';
58+
else if ('show' in _item) _item.type = 'show';
59+
60+
if (!_item?.poster && _item.type) {
61+
if (_item?.movie?.ids?.tmdb) {
62+
_item.poster = getImageUrl({
63+
id: _item.movie.ids.tmdb,
64+
type: _item.type,
65+
});
66+
} else if (_item?.show?.ids?.tmdb) {
67+
_item.poster = getImageUrl({
68+
id: _item.show.ids.tmdb,
69+
type: 'show',
70+
});
71+
}
72+
}
73+
5174
if (!item.watched_at) return _item;
5275
5376
const date: ListScrollItem['date'] = { current: new Date(item.watched_at) };
@@ -61,6 +84,7 @@ const history = computed<ListScrollItem[]>(() => {
6184
date.previous?.toLocaleDateString() === date.current?.toLocaleDateString();
6285
date.sameDayAsNext =
6386
date.next?.toLocaleDateString() === date.current?.toLocaleDateString();
87+
6488
return { ..._item, date };
6589
});
6690
});

src/i18n/en/common/media.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"common__media__type__movie": {
3+
"message": "Movie",
4+
"description": "Media type for movies"
5+
},
6+
"common__media__type__show": {
7+
"message": "Show",
8+
"description": "Media type for shows"
9+
},
10+
"common__media__type__episode": {
11+
"message": "Episode",
12+
"description": "Media type for episodes"
13+
},
14+
"common__media__type__season": {
15+
"message": "Season",
16+
"description": "Media type for seasons"
17+
}
18+
}

src/models/tmdb/tmdb-client.model.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
import type {
2+
BaseOptions,
3+
BaseQuery,
4+
BaseRequest,
5+
BaseSettings,
6+
BaseTemplate,
7+
BaseTemplateOptions,
8+
ResponseOrTypedResponse,
9+
} from '~/services/common/base-client';
10+
111
import type { RecursiveRecord } from '~/utils/typescript.utils';
212

3-
import {
4-
type BaseOptions,
5-
type BaseQuery,
6-
type BaseRequest,
7-
type BaseSettings,
8-
type BaseTemplate,
9-
type BaseTemplateOptions,
10-
ClientEndpoint,
11-
type ResponseOrTypedResponse,
12-
} from '~/services/common/base-client';
13+
import { ClientEndpoint } from '~/services/common/base-client';
1314

1415
export type TmdbClientSettings = BaseSettings<{
1516
/** The consumer client identifier */

src/models/trakt/trakt-client.model.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
BaseCacheOption,
23
BaseInit,
34
BaseOptions,
45
BaseQuery,
@@ -98,13 +99,19 @@ export interface TraktClientEndpoint<Parameter extends TraktApiParams = Record<s
9899
(param?: Parameter, init?: TraktApiInit): Promise<TraktApiResponse<Response>>;
99100
}
100101

102+
export type TraktClientCachedEndpoint<Parameter extends TraktApiParams = Record<string, never>, Response = unknown> = (
103+
param?: Parameter,
104+
init?: BaseInit,
105+
cacheOptions?: BaseCacheOption,
106+
) => Promise<TraktApiResponse<Response>>;
107+
101108
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging -- To allow type extension
102109
export class TraktClientEndpoint<
103110
Parameter extends TraktApiParams = Record<string, never>,
104111
Response = unknown,
105112
Cache extends boolean = true,
106113
> extends ClientEndpoint<Parameter, Response, Cache, TraktApiTemplateOptions<keyof Parameter>> {
107-
declare cached: Cache extends true ? Omit<this, 'cached'> & TraktClientEndpoint<Parameter, Response> : never;
114+
declare cached: Cache extends true ? Omit<this, 'cached'> & TraktClientCachedEndpoint<Parameter, Response> : never;
108115
}
109116

110117
export type TraktApiRequest = BaseRequest;

src/models/trakt/trakt-history.model.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TraktApiExtended, TraktApiParamsExtended, TraktApiParamsPagination } from '~/models/trakt/trakt-client.model';
12
import type { Any, EntityTypes } from '~/models/trakt/trakt-entity.model';
23
import type { TraktEpisode } from '~/models/trakt/trakt-episode.model';
34
import type { TraktMovie } from '~/models/trakt/trakt-movie.model';
@@ -66,3 +67,14 @@ export type TraktHistoryRemoved = {
6667
ids: number[];
6768
};
6869
};
70+
71+
export type TraktHistoryGetQuery = {
72+
/** Trakt ID for a specific item. */
73+
id?: string;
74+
type?: 'movies' | 'shows' | 'seasons' | 'episodes';
75+
/** Timestamp in ISO 8601 GMT format (YYYY-MM-DD'T'hh:mm:ss.sssZ) */
76+
start_at?: string;
77+
/** Timestamp in ISO 8601 GMT format (YYYY-MM-DD'T'hh:mm:ss.sssZ) */
78+
end_at?: string;
79+
} & TraktApiParamsExtended<typeof TraktApiExtended.Full> &
80+
TraktApiParamsPagination;

src/services/common/base-client.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ export type TypedResponse<T> = Omit<Response, 'json'> & {
9898

9999
export type ResponseOrTypedResponse<T = unknown> = T extends never ? Response : TypedResponse<T>;
100100

101-
type ClientEndpointCall<Parameter extends Record<string, never> = Record<string, never>, Response = unknown> = (
101+
type ClientEndpointCall<Parameter extends RecursiveRecord = Record<string, never>, Response = unknown> = (
102102
param?: Parameter,
103103
init?: BaseInit,
104104
) => Promise<ResponseOrTypedResponse<Response>>;
105105

106106
export interface ClientEndpoint<Parameter extends RecursiveRecord = Record<string, never>, Response = unknown> {
107107
(param?: Parameter, init?: BaseInit): Promise<ResponseOrTypedResponse<Response>>;
108108
}
109-
type BaseCacheOption = { force?: boolean; retention?: number; evictOnError?: boolean };
109+
export type BaseCacheOption = { force?: boolean; retention?: number; evictOnError?: boolean };
110110

111111
type ClientEndpointCache<Parameter extends RecursiveRecord = Record<string, never>, Response = unknown> = (
112112
param?: Parameter,

src/services/trakt-client/api/endpoints/sync.endpoint.ts

+2-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
import type {
1010
TraktHistory,
1111
TraktHistoryAdded,
12+
TraktHistoryGetQuery,
1213
TraktHistoryRemoved,
1314
TraktHistoryRemovedRequest,
1415
TraktHistoryRequest,
@@ -299,19 +300,7 @@ export const sync = {
299300
* @see [get-watched-history]{@link https://trakt.docs.apiary.io/#reference/sync/get-history/get-watched-history}
300301
*/
301302
history: {
302-
get: new TraktClientEndpoint<
303-
{
304-
/** Trakt ID for a specific item. */
305-
id?: string;
306-
type?: 'movies' | 'shows' | 'seasons' | 'episodes';
307-
/** Timestamp in ISO 8601 GMT format (YYYY-MM-DD'T'hh:mm:ss.sssZ) */
308-
start_at?: string;
309-
/** Timestamp in ISO 8601 GMT format (YYYY-MM-DD'T'hh:mm:ss.sssZ) */
310-
end_at?: string;
311-
} & TraktApiParamsExtended<typeof TraktApiExtended.Full> &
312-
TraktApiParamsPagination,
313-
TraktHistory[]
314-
>({
303+
get: new TraktClientEndpoint<TraktHistoryGetQuery, TraktHistory[]>({
315304
method: HttpMethod.GET,
316305
url: '/sync/history/:type/:id?start_at=&end_at=',
317306
opts: {

0 commit comments

Comments
 (0)