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:
Fernando Fernández 2023-05-02 10:39:42 +02:00
parent 8ec4cc6d04
commit f6e07ce4fe
7 changed files with 97 additions and 109 deletions

View File

@ -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>

View File

@ -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<{

View File

@ -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>

View File

@ -61,8 +61,11 @@ const vuetify = createVuetify({
VCheckbox: {
color: 'primary'
},
VProgressLinear: {
color: 'primary'
},
VBtn: {
color: '',
color: undefined,
variant: 'text'
},
VTooltip: {

View File

@ -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);

View File

@ -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']

View File

@ -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