diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index eddfb247f6c..89b46f26486 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -15,7 +15,7 @@
:mode="defaultTransitionMode ?? route.meta.layout.transition.mode">
diff --git a/frontend/src/composables/apis.ts b/frontend/src/composables/apis.ts
index 16b0bc1770c..809663c6c7a 100644
--- a/frontend/src/composables/apis.ts
+++ b/frontend/src/composables/apis.ts
@@ -2,7 +2,7 @@ import type { Api } from '@jellyfin/sdk';
import type { BaseItemDto, BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
import type { AxiosResponse } from 'axios';
import { deepEqual } from 'fast-equals';
-import { computed, getCurrentScope, isRef, shallowRef, toValue, unref, watch, type ComputedRef, type Ref } from 'vue';
+import { computed, effectScope, getCurrentScope, isRef, shallowRef, toValue, unref, watch, type ComputedRef, type Ref } from 'vue';
import { until } from '@vueuse/core';
import { useLoading } from '@/composables/use-loading';
import { useSnackbar } from '@/composables/use-snackbar';
@@ -11,6 +11,7 @@ import { remote } from '@/plugins/remote';
import { isConnectedToServer } from '@/store';
import { apiStore } from '@/store/api';
import { isArray, isNil } from '@/utils/validation';
+import { router } from '@/plugins/router';
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */
type OmittedKeys = 'fields' | 'userId' | 'enableImages' | 'enableTotalRecordCount' | 'enableImageTypes';
@@ -191,6 +192,7 @@ function _sharedInternalLogic any>, K ex
const stringArgs = computed(() => {
return JSON.stringify(argsRef.value);
});
+
/**
* TODO: Check why previous returns unknown by default without the type annotation
*/
@@ -275,29 +277,38 @@ function _sharedInternalLogic any>, K ex
argsRef.value = normalizeArgs();
if (getCurrentScope() !== undefined) {
- if (args.length) {
- watch(args, async (_newVal, oldVal) => {
- const normalizedArgs = normalizeArgs();
-
- /**
- * Does a deep comparison to avoid useless double requests
- */
- if (!normalizedArgs.every((a, index) => deepEqual(a, toValue(oldVal[index])))) {
- argsRef.value = normalizedArgs;
- await runNormally();
- }
- });
- }
+ const handleArgsChange = async (_: typeof args, old: typeof args | undefined): Promise => {
+ const normalizedArgs = normalizeArgs();
+
+ /**
+ * Does a deep comparison to avoid useless double requests
+ */
+ if (old && !normalizedArgs.every((a, index) => deepEqual(a, toValue(old[index])))) {
+ argsRef.value = normalizedArgs;
+ await runNormally();
+ }
+ };
+ const scope = effectScope();
- watch(isConnectedToServer, runWithRetry);
+ scope.run(() => {
+ if (args.length) {
+ watch(args, handleArgsChange);
+ }
- if (isRef(api)) {
- watch(api, runNormally);
- }
+ watch(isConnectedToServer, runWithRetry);
- if (isRef(methodName)) {
- watch(methodName, runNormally);
- }
+ if (isRef(api)) {
+ watch(api, runNormally);
+ }
+
+ if (isRef(methodName)) {
+ watch(methodName, runNormally);
+ }
+ });
+
+ watch(() => router.currentRoute.value.name, () => scope.stop(),
+ { once: true, flush: 'sync' }
+ );
}
/**
diff --git a/frontend/src/pages/item/[itemId].vue b/frontend/src/pages/item/[itemId].vue
index 5e48c987e26..80527ae637c 100644
--- a/frontend/src/pages/item/[itemId].vue
+++ b/frontend/src/pages/item/[itemId].vue
@@ -321,16 +321,12 @@ const { data: relatedItems } = await useBaseItem(getLibraryApi, 'getSimilarItems
itemId: route.params.itemId,
limit: 12
}));
-const { data: currentSeries } = await useBaseItem(getUserLibraryApi, 'getItem')(
- () => ({
- itemId: item.value.SeriesId ?? ''
- })
-);
-const { data: childItems } = await useBaseItem(getItemsApi, 'getItems')(
- () => ({
- parentId: item.value.Id
- })
-);
+const { data: currentSeries } = await useBaseItem(getUserLibraryApi, 'getItem')(() => ({
+ itemId: item.value.SeriesId ?? ''
+}));
+const { data: childItems } = await useBaseItem(getItemsApi, 'getItems')(() => ({
+ parentId: item.value.Id
+}));
const selectedSource = ref();
const currentVideoTrack = ref();
diff --git a/frontend/src/pages/library/[itemId].vue b/frontend/src/pages/library/[itemId].vue
index e9d356643bc..e0ba58dc611 100644
--- a/frontend/src/pages/library/[itemId].vue
+++ b/frontend/src/pages/library/[itemId].vue
@@ -9,11 +9,16 @@
-
+
{{ t('lazyLoading', { value: items.length }) }}
+
- {{ items?.length ?? 0 }}
+ {{ items.length ?? 0 }}
import {
- BaseItemKind, SortOrder, type BaseItemDto
+ BaseItemKind, SortOrder
} from '@jellyfin/sdk/lib/generated-client';
import { getArtistsApi } from '@jellyfin/sdk/lib/utils/api/artists-api';
import { getGenresApi } from '@jellyfin/sdk/lib/utils/api/genres-api';
@@ -74,7 +79,6 @@ import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api';
import { computed, onBeforeMount, ref, shallowRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
-import { apiStore } from '@/store/api';
import { methodsAsObject, useBaseItem } from '@/composables/apis';
import type { Filters } from '@/components/Buttons/FilterButton.vue';
import { useItemPageTitle } from '@/composables/page-title';
@@ -83,6 +87,7 @@ const { t } = useI18n();
const route = useRoute('/library/[itemId]');
const lazyLoadLimit = 50;
+const initialId = route.params.itemId;
const COLLECTION_TYPES_MAPPINGS: Record = {
tvshows: BaseItemKind.Series,
movies: BaseItemKind.Movie,
@@ -95,7 +100,6 @@ const innerItemKind = shallowRef();
const sortBy = shallowRef();
const sortAscending = shallowRef(true);
const queryLimit = shallowRef(lazyLoadLimit);
-const lazyLoadIds = shallowRef([]);
const filters = ref({
status: [],
features: [],
@@ -158,7 +162,6 @@ const isSortable = computed(
'Person',
'Genre',
'MusicGenre',
- 'MusicGenre',
'Studio'
].includes(viewType.value)
);
@@ -201,7 +204,7 @@ const method = computed(() => methods.value.methodName);
/**
* TODO: Improve the type situation of this statement
*/
-const { loading, data: queryItems } = await useBaseItem(api, method)(() => ({
+const { loading, data: items } = await useBaseItem(api, method)(() => ({
parentId: parentId.value,
personTypes: viewType.value === 'Person' ? ['Actor'] : undefined,
includeItemTypes: viewType.value ? [viewType.value] : undefined,
@@ -220,24 +223,15 @@ const { loading, data: queryItems } = await useBaseItem(api, method)(() => ({
isHd: filters.value.types.includes('isHD') || undefined,
is4K: filters.value.types.includes('is4K') || undefined,
is3D: filters.value.types.includes('is3D') || undefined,
- startIndex: queryLimit.value ? undefined : lazyLoadLimit,
limit: queryLimit.value
}));
-/**
- * The queryItems for the 2nd request will return the items from (lazyloadLimit, n],
- * so checking if just the first matches is a good solution
- */
-const fullQueryIsCached = computed(() => loading.value ? !queryLimit.value && queryItems.value[0].Id !== lazyLoadIds.value[0] : true);
-const items = computed(() => fullQueryIsCached.value ? [...(apiStore.getItemsById(lazyLoadIds.value) as BaseItemDto[]), ...queryItems.value] : queryItems.value);
-
useItemPageTitle(library);
/**
- * We fetch the 1st 100 items and, after mount, we fetch the rest.
+ * We fetch the 1st 50 items and, after mount, we fetch the rest.
*/
onBeforeMount(() => {
- lazyLoadIds.value = queryItems.value.map(i => i.Id);
queryLimit.value = undefined;
});