mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2024-11-27 08:10:26 +00:00
refactor(identify): minor improvements
* Simplify templating for VProgressLinear usage * Set height to auto since it showed an scrollbar in all my devices (1366x768 and 1920x1080) * Use tabName with computed model to simplify logic * Use back and close buttons instead of lower actions * Set primary color by default in VProgressLinear * Handle loading state in the prefilled search watcher (the request usually goes very fast, but in a slow connection, the component is left in an awkward state for quite some time). * Improve typings
This commit is contained in:
parent
8ec4cc6d04
commit
f6e07ce4fe
@ -2,29 +2,37 @@
|
||||
<v-dialog
|
||||
:model-value="model"
|
||||
:fullscreen="$vuetify.display.mobile"
|
||||
:height="!$vuetify.display.mobile ? '60vh' : undefined"
|
||||
:height="$vuetify.display.mobile ? undefined : 'auto'"
|
||||
@after-leave="emit('close')">
|
||||
<v-card
|
||||
v-if="externalInfos && searchData && item"
|
||||
height="100%"
|
||||
class="d-flex flex-column identify-tab"
|
||||
:loading="isLoading">
|
||||
<v-card-title>{{ $t('identify') }}</v-card-title>
|
||||
loading>
|
||||
<template #loader>
|
||||
<v-progress-linear v-model="progress" :indeterminate="isLoading" />
|
||||
</template>
|
||||
<v-toolbar color="transparent">
|
||||
<template #prepend>
|
||||
<v-btn icon :disabled="tabName === 'searchMenu'" @click="clear">
|
||||
<i-mdi-arrow-left />
|
||||
</v-btn>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn icon @click="model = false">
|
||||
<i-mdi-close />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-toolbar-title>
|
||||
{{ $t('identify') }}
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-subtitle v-if="itemPath" class="pb-3">
|
||||
{{ itemPath }}
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- Somehow this will only show if display is table? -->
|
||||
<v-progress-linear
|
||||
v-model="progress"
|
||||
height="4"
|
||||
class="mb-2 d-inline-table"
|
||||
:style="{
|
||||
display: 'inline-table'
|
||||
}" />
|
||||
|
||||
<v-card-text
|
||||
class="pa-0 px-2 flex-grow-1"
|
||||
:class="{
|
||||
@ -88,34 +96,6 @@
|
||||
'justify-end': !$vuetify.display.mobile,
|
||||
'justify-center': $vuetify.display.mobile
|
||||
}">
|
||||
<v-btn
|
||||
v-if="
|
||||
(tabName === 'searchMenu' && searchResults !== undefined) ||
|
||||
tabName === 'resultsMenu'
|
||||
"
|
||||
variant="flat"
|
||||
width="8em"
|
||||
color="secondary"
|
||||
class="mr-1"
|
||||
:loading="isLoading"
|
||||
@click="
|
||||
[
|
||||
tabName === 'resultsMenu'
|
||||
? (tabName = 'searchMenu')
|
||||
: (tabName = 'resultsMenu')
|
||||
]
|
||||
">
|
||||
{{ tabName === 'resultsMenu' ? t('goBack') : t('goNext') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="flat"
|
||||
width="8em"
|
||||
color="secondary"
|
||||
class="mr-1"
|
||||
:loading="isLoading"
|
||||
@click="emit('close')">
|
||||
{{ t('cancel') }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="tabName === 'searchMenu'"
|
||||
variant="flat"
|
||||
@ -160,9 +140,15 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const remote = useRemote();
|
||||
const tabName = ref<'searchMenu' | 'resultsMenu'>('searchMenu');
|
||||
const model = ref(true);
|
||||
const isLoading = ref(false);
|
||||
const externalInfos = ref<ExternalIdInfo[]>([]);
|
||||
const searchData = ref<IdentifySearchItem[]>([]);
|
||||
const searchResults = ref<RemoteSearchResult[]>();
|
||||
const replaceImage = ref(false);
|
||||
const tabName = computed(() =>
|
||||
searchResults.value === undefined ? 'searchMenu' : 'resultsMenu'
|
||||
);
|
||||
const progress = computed(() => {
|
||||
switch (tabName.value) {
|
||||
case 'searchMenu': {
|
||||
@ -176,12 +162,6 @@ const progress = computed(() => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const externalInfos = ref<ExternalIdInfo[]>();
|
||||
const searchData = ref<IdentifySearchItem[]>();
|
||||
const searchResults = ref<RemoteSearchResult[]>();
|
||||
const replaceImage = ref(false);
|
||||
|
||||
const itemPath = computed<string | undefined>(() => {
|
||||
if (!props.item) {
|
||||
return;
|
||||
@ -206,7 +186,6 @@ async function searchInformation(): Promise<void> {
|
||||
|
||||
if (Array.isArray(results)) {
|
||||
searchResults.value = results;
|
||||
tabName.value = 'resultsMenu';
|
||||
} else {
|
||||
useSnackbar(t('identifySearchError'), 'error');
|
||||
}
|
||||
@ -242,11 +221,17 @@ async function applySelectedSearch(result: RemoteSearchResult): Promise<void> {
|
||||
useSnackbar(t('identifyApplyError'), 'error');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
emit('close');
|
||||
tabName.value = 'searchMenu';
|
||||
model.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search results
|
||||
*/
|
||||
function clear(): void {
|
||||
searchResults.value = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a pre-filled search data object.
|
||||
*/
|
||||
@ -299,65 +284,60 @@ function getFilledSearchData(
|
||||
watch(
|
||||
() => props.item,
|
||||
async () => {
|
||||
tabName.value = 'searchMenu';
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
const results = (
|
||||
await remote.sdk.newUserApi(getItemLookupApi).getExternalIdInfos({
|
||||
itemId: props.item.Id ?? ''
|
||||
})
|
||||
).data;
|
||||
const results = (
|
||||
await remote.sdk.newUserApi(getItemLookupApi).getExternalIdInfos({
|
||||
itemId: props.item.Id ?? ''
|
||||
})
|
||||
).data;
|
||||
|
||||
externalInfos.value = results;
|
||||
externalInfos.value = results;
|
||||
|
||||
const initSearch: IdentifySearchItem[] = [
|
||||
{
|
||||
key: 'search-item-Name',
|
||||
title: t('name'),
|
||||
type: 'string'
|
||||
const initSearch: IdentifySearchItem[] = [
|
||||
{
|
||||
key: 'search-item-Name',
|
||||
title: t('name'),
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
if (!['BoxSet', 'Person'].includes(props.item.Type || '')) {
|
||||
initSearch.push({
|
||||
key: 'search-item-Year',
|
||||
title: t('year'),
|
||||
type: 'number'
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
if (!['BoxSet', 'Person'].includes(props.item.Type || '')) {
|
||||
initSearch.push({
|
||||
key: 'search-item-Year',
|
||||
title: t('year'),
|
||||
type: 'number'
|
||||
const prefilledSearch = getFilledSearchData(props.item, results);
|
||||
|
||||
const webCrawlSearch: IdentifySearchItem[] = results.map((info) => {
|
||||
let title = info.Name ?? '';
|
||||
|
||||
if (info.Type) {
|
||||
title += ` ${info.Type.split(/(?=[A-Z])/).join(' ')}`;
|
||||
}
|
||||
|
||||
const prefillValue = prefilledSearch.find(
|
||||
(e) => e.key === info.Key
|
||||
)?.value;
|
||||
|
||||
return {
|
||||
key: info.Key ?? '',
|
||||
value: prefillValue,
|
||||
title: `${title} ID`,
|
||||
type: 'string'
|
||||
};
|
||||
});
|
||||
|
||||
searchData.value = [...initSearch, ...webCrawlSearch];
|
||||
} catch {
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
const prefilledSearch = getFilledSearchData(props.item, results);
|
||||
|
||||
const webCrawlSearch: IdentifySearchItem[] = results.map((info) => {
|
||||
let title = info.Name ?? '';
|
||||
|
||||
if (info.Type) {
|
||||
title += ` ${info.Type.split(/(?=[A-Z])/).join(' ')}`;
|
||||
}
|
||||
|
||||
const prefillValue = prefilledSearch.find(
|
||||
(e) => e.key === info.Key
|
||||
)?.value;
|
||||
|
||||
return {
|
||||
key: info.Key ?? '',
|
||||
value: prefillValue,
|
||||
title: `${title} ID`,
|
||||
type: 'string'
|
||||
};
|
||||
});
|
||||
|
||||
searchData.value = [...initSearch, ...webCrawlSearch];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*
|
||||
Force the progress bar background height
|
||||
Only apply if the .v-progress-linear has a .d-inline-table class
|
||||
*/
|
||||
.v-progress-linear.d-inline-table > .v-progress-linear__background {
|
||||
height: var(--v-progress-linear-height);
|
||||
}
|
||||
</style>
|
||||
|
@ -21,11 +21,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RemoteSearchResult } from '@jellyfin/sdk/lib/generated-client';
|
||||
import {
|
||||
BaseItemKind,
|
||||
RemoteSearchResult
|
||||
} from '@jellyfin/sdk/lib/generated-client';
|
||||
|
||||
defineProps<{
|
||||
item: RemoteSearchResult;
|
||||
itemType?: string;
|
||||
itemType?: BaseItemKind;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
|
@ -18,9 +18,7 @@ import { computed } from 'vue';
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { CardShapes, getShapeFromItemType } from '@/utils/items';
|
||||
|
||||
const props = defineProps<{ url?: string; itemType?: string }>();
|
||||
const props = defineProps<{ url?: string; itemType?: BaseItemKind }>();
|
||||
|
||||
const shape = computed(() =>
|
||||
getShapeFromItemType((props.itemType as BaseItemKind) ?? undefined)
|
||||
);
|
||||
const shape = computed(() => getShapeFromItemType(props.itemType ?? undefined));
|
||||
</script>
|
||||
|
@ -61,8 +61,11 @@ const vuetify = createVuetify({
|
||||
VCheckbox: {
|
||||
color: 'primary'
|
||||
},
|
||||
VProgressLinear: {
|
||||
color: 'primary'
|
||||
},
|
||||
VBtn: {
|
||||
color: '',
|
||||
color: undefined,
|
||||
variant: 'text'
|
||||
},
|
||||
VTooltip: {
|
||||
|
@ -529,7 +529,7 @@ export async function getItemRemoteSearch(
|
||||
}
|
||||
|
||||
for (const search of queryProviderIDs) {
|
||||
buildSearch.ProviderIds[search.key] = String(search.value);
|
||||
buildSearch.ProviderIds[search.key] = search.value;
|
||||
}
|
||||
|
||||
const searcher = remote.sdk.newUserApi(getItemLookupApi);
|
||||
|
2
frontend/types/global/components.d.ts
vendored
2
frontend/types/global/components.d.ts
vendored
@ -198,6 +198,8 @@ declare module '@vue/runtime-core' {
|
||||
VTabs: typeof import('vuetify/components')['VTabs']
|
||||
VTextarea: typeof import('vuetify/components')['VTextarea']
|
||||
VTextField: typeof import('vuetify/components')['VTextField']
|
||||
VToolbar: typeof import('vuetify/components')['VToolbar']
|
||||
VToolbarTitle: typeof import('vuetify/components')['VToolbarTitle']
|
||||
VTooltip: typeof import('vuetify/components')['VTooltip']
|
||||
VWindow: typeof import('vuetify/components')['VWindow']
|
||||
VWindowItem: typeof import('vuetify/components')['VWindowItem']
|
||||
|
2
frontend/types/global/routes.d.ts
vendored
2
frontend/types/global/routes.d.ts
vendored
@ -25,6 +25,7 @@ import type {
|
||||
// vue-router extensions
|
||||
_RouterTyped,
|
||||
RouterLinkTyped,
|
||||
RouterLinkPropsTyped,
|
||||
NavigationGuard,
|
||||
UseLinkFnTyped,
|
||||
|
||||
@ -112,6 +113,7 @@ declare module 'vue-router/auto' {
|
||||
export function onBeforeRouteUpdate(guard: NavigationGuard<RouteNamedMap>): void
|
||||
|
||||
export const RouterLink: RouterLinkTyped<RouteNamedMap>
|
||||
export const RouterLinkProps: RouterLinkPropsTyped<RouteNamedMap>
|
||||
|
||||
// Experimental Data Fetching
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user