mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
fix: fix selecting items from local libraries when editing channels (#1592)
This commit is contained in:
committed by
GitHub
parent
74ed6f76c8
commit
b48e54b65c
1
docs/generated/tunarr-v1.1.0-dev.0-openapi.json
Normal file
1
docs/generated/tunarr-v1.1.0-dev.0-openapi.json
Normal file
File diff suppressed because one or more lines are too long
@@ -217,7 +217,8 @@ export const programmingApi: RouterPluginAsyncCallback = async (fastify) => {
|
||||
}),
|
||||
querystring: z.object({
|
||||
facetQuery: z.string().optional(),
|
||||
libraryId: z.string().uuid().optional(),
|
||||
mediaSourceId: z.uuid().optional(),
|
||||
libraryId: z.uuid().optional(),
|
||||
}),
|
||||
body: z.object({
|
||||
filter: SearchFilterQuerySchema.optional(),
|
||||
@@ -237,6 +238,7 @@ export const programmingApi: RouterPluginAsyncCallback = async (fastify) => {
|
||||
facetName: req.params.facetName,
|
||||
libraryId: req.query.libraryId,
|
||||
filter: req.body.filter,
|
||||
mediaSourceId: req.query.mediaSourceId,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ export const ImportedLibrarySelector = ({ initialLibraryId }: Props) => {
|
||||
const { data: libraries } = useMediaSourceLibraries(
|
||||
selectedServer?.id ?? '',
|
||||
{
|
||||
enabled: isNonEmptyString(selectedServer?.id),
|
||||
enabled:
|
||||
isNonEmptyString(selectedServer?.id) && selectedServer.type !== 'local',
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
ToggleButtonGroup,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import { isNonEmptyString } from '@tunarr/shared/util';
|
||||
import { tag } from '@tunarr/types';
|
||||
import type { SearchRequest } from '@tunarr/types/api';
|
||||
import {
|
||||
capitalize,
|
||||
@@ -154,7 +156,13 @@ export const ProgrammingSelector = ({
|
||||
if (selectedServer?.type === 'local') {
|
||||
return (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<SearchInput />
|
||||
<SearchInput mediaSourceId={tag<MediaSourceId>(selectedServer.id)} />
|
||||
<SelectedProgrammingActions
|
||||
toggleOrSetSelectedProgramsDrawer={
|
||||
toggleOrSetSelectedProgramsDrawer
|
||||
}
|
||||
/>
|
||||
<LibraryProgramGrid mediaSource={selectedServer} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -197,10 +205,8 @@ export const ProgrammingSelector = ({
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<SearchInput
|
||||
library={{
|
||||
...selectedLibrary.view,
|
||||
mediaSource: selectedServer!,
|
||||
}}
|
||||
mediaSourceId={tag<MediaSourceId>(selectedServer!.id)}
|
||||
libraryId={selectedLibrary.view.id}
|
||||
/>
|
||||
<SelectedProgrammingActions
|
||||
toggleOrSetSelectedProgramsDrawer={
|
||||
|
||||
@@ -72,7 +72,9 @@ const ProgramGridItemInner = <T extends ProgramOrFolder>(
|
||||
},
|
||||
aspectRatio: isMusicItem(item)
|
||||
? 'square'
|
||||
: isEpisode(item)
|
||||
: isEpisode(item) ||
|
||||
item.type === 'other_video' ||
|
||||
item.type === 'music_video'
|
||||
? 'landscape'
|
||||
: 'portrait',
|
||||
isPlaylist: item.type === 'playlist',
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Stack } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import { type DateSearchField } from '@tunarr/types/api';
|
||||
import dayjs from 'dayjs';
|
||||
import { isNumber } from 'lodash-es';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import type { FieldKey, FieldPrefix } from '../../types/SearchBuilder.ts';
|
||||
import { SearchForm } from './SearchInput.tsx';
|
||||
import type { SearchForm } from './SearchInput.tsx';
|
||||
|
||||
type Props = {
|
||||
field: DateSearchField;
|
||||
formKey: FieldKey<FieldPrefix, 'fieldSpec'>;
|
||||
library?: MediaSourceLibrary;
|
||||
};
|
||||
|
||||
export function DateSearchValueNode({ field, formKey }: Props) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Autocomplete, CircularProgress, TextField } from '@mui/material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import { search } from '@tunarr/shared/util';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import type { FactedStringSearchField } from '@tunarr/types/api';
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
@@ -9,16 +9,18 @@ import { useDebounceValue } from 'usehooks-ts';
|
||||
import { postApiProgramsFacetsByFacetNameOptions } from '../../generated/@tanstack/react-query.gen.ts';
|
||||
import { isNonEmptyString } from '../../helpers/util.ts';
|
||||
import type { FieldKey, FieldPrefix } from '../../types/SearchBuilder.ts';
|
||||
import { SearchForm } from './SearchInput.tsx';
|
||||
import type { SearchForm } from './SearchInput.tsx';
|
||||
|
||||
export function FacetStringValueSearchNode({
|
||||
formKey,
|
||||
library,
|
||||
mediaSourceId,
|
||||
libraryId,
|
||||
field,
|
||||
}: {
|
||||
field: FactedStringSearchField;
|
||||
formKey: FieldKey<FieldPrefix, 'fieldSpec'>;
|
||||
library?: MediaSourceLibrary;
|
||||
mediaSourceId?: MediaSourceId;
|
||||
libraryId?: string;
|
||||
}) {
|
||||
const { control } = useFormContext<SearchForm>();
|
||||
const [facetSearchInputValue, setFacetSearchInputValue] = useDebounceValue(
|
||||
@@ -32,7 +34,8 @@ export function FacetStringValueSearchNode({
|
||||
facetName: search.virtualFieldToIndexField[field.key] ?? field.key,
|
||||
},
|
||||
query: {
|
||||
libraryId: library?.id,
|
||||
mediaSourceId,
|
||||
libraryId,
|
||||
facetQuery: isNonEmptyString(facetSearchInputValue)
|
||||
? facetSearchInputValue
|
||||
: undefined,
|
||||
@@ -45,7 +48,7 @@ export function FacetStringValueSearchNode({
|
||||
return facetQuery.data?.facetValues
|
||||
? Object.keys(facetQuery.data.facetValues)
|
||||
: [];
|
||||
}, [facetQuery.data?.facetValues]);
|
||||
}, [facetQuery.data]);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import { FormProvider, useFormContext } from 'react-hook-form';
|
||||
import { searchFilterToString } from '../../../../shared/dist/src/util/searchUtil';
|
||||
import { SearchGroupNode } from './SearchGroupNode.tsx';
|
||||
@@ -7,10 +7,14 @@ import type { SearchForm } from './SearchInput.tsx';
|
||||
import { SearchInputToggle } from './SearchInputToggle.tsx';
|
||||
|
||||
type Props = {
|
||||
library?: MediaSourceLibrary;
|
||||
mediaSourceId?: MediaSourceId;
|
||||
libraryId?: string;
|
||||
};
|
||||
|
||||
export const PointAndClickSearchBuilder = ({ library }: Props) => {
|
||||
export const PointAndClickSearchBuilder = ({
|
||||
mediaSourceId,
|
||||
libraryId,
|
||||
}: Props) => {
|
||||
const form = useFormContext<SearchForm>();
|
||||
const filter = form.watch('filter');
|
||||
|
||||
@@ -36,7 +40,8 @@ export const PointAndClickSearchBuilder = ({ library }: Props) => {
|
||||
formKey="filter.filter"
|
||||
index={0}
|
||||
remove={() => {}}
|
||||
library={library}
|
||||
mediaSourceId={mediaSourceId}
|
||||
libraryId={libraryId}
|
||||
/>
|
||||
</FormProvider>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import type { SearchRequest } from '@tunarr/types/api';
|
||||
import { useToggle } from '@uidotdev/usehooks';
|
||||
import { isEmpty, isNil } from 'lodash-es';
|
||||
@@ -25,10 +25,14 @@ import { SearchInputToggle } from './SearchInputToggle.tsx';
|
||||
|
||||
type SearchBuilderProps = {
|
||||
onSearch: (query: SearchRequest) => void;
|
||||
library?: MediaSourceLibrary;
|
||||
mediaSourceId?: MediaSourceId;
|
||||
libraryId?: string;
|
||||
};
|
||||
|
||||
export function SearchFilterBuilder({ library }: SearchBuilderProps) {
|
||||
export function SearchFilterBuilder({
|
||||
libraryId,
|
||||
mediaSourceId,
|
||||
}: SearchBuilderProps) {
|
||||
const [smartCollectionModalOpen, toggleSmartCollectionModal] =
|
||||
useToggle(false);
|
||||
|
||||
@@ -147,7 +151,10 @@ export function SearchFilterBuilder({ library }: SearchBuilderProps) {
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
<PointAndClickSearchBuilder library={library} />
|
||||
<PointAndClickSearchBuilder
|
||||
mediaSourceId={mediaSourceId}
|
||||
libraryId={libraryId}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<CreateSmartCollectionDialog
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Stack,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import { isNonEmptyString } from '@tunarr/shared/util';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import type { SearchFilter } from '@tunarr/types/api';
|
||||
@@ -21,7 +22,9 @@ import { useGetFieldName } from '../../hooks/searchBuilderHooks.ts';
|
||||
import { SearchValueNode } from './SearchValueNode.tsx';
|
||||
|
||||
export type GroupNodeProps = {
|
||||
library?: MediaSourceLibrary;
|
||||
mediaSourceId?: MediaSourceId;
|
||||
libraryId?: string;
|
||||
mediaTypeFilter?: MediaSourceLibrary['mediaType'];
|
||||
index: number;
|
||||
depth: number;
|
||||
formKey: FieldPrefix;
|
||||
@@ -33,7 +36,9 @@ export function SearchGroupNode({
|
||||
formKey,
|
||||
remove: removeSelf,
|
||||
index,
|
||||
library,
|
||||
libraryId,
|
||||
mediaSourceId,
|
||||
mediaTypeFilter,
|
||||
}: GroupNodeProps) {
|
||||
const { control } = useFormContext();
|
||||
const prefix = isNonEmptyString(formKey)
|
||||
@@ -120,7 +125,9 @@ export function SearchGroupNode({
|
||||
index={index}
|
||||
only={fields.length === 1}
|
||||
remove={removeChild}
|
||||
library={library}
|
||||
libraryId={libraryId}
|
||||
mediaSourceId={mediaSourceId}
|
||||
mediaTypeFilter={mediaTypeFilter}
|
||||
/>
|
||||
) : (
|
||||
<SearchGroupNode
|
||||
@@ -129,7 +136,9 @@ export function SearchGroupNode({
|
||||
formKey={`${prefix}children.${index}` as FieldPrefix}
|
||||
remove={removeChild}
|
||||
index={index}
|
||||
library={library}
|
||||
libraryId={libraryId}
|
||||
mediaSourceId={mediaSourceId}
|
||||
mediaTypeFilter={mediaTypeFilter}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { useMatches } from '@tanstack/react-router';
|
||||
import type { MediaSourceId } from '@tunarr/shared';
|
||||
import { isNonEmptyString, search as tunarrSearch } from '@tunarr/shared/util';
|
||||
import type { MediaSourceLibrary } from '@tunarr/types';
|
||||
import type { SearchFilter, SearchRequest } from '@tunarr/types/api';
|
||||
import { difference, isEmpty, last } from 'lodash-es';
|
||||
import { useCallback, useState } from 'react';
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
import { SearchFilterBuilder } from './SearchFilterBuilder.tsx';
|
||||
|
||||
type Props = {
|
||||
library?: MediaSourceLibrary;
|
||||
mediaSourceId?: MediaSourceId;
|
||||
libraryId?: string;
|
||||
initialSearchFilter?: SearchFilter;
|
||||
};
|
||||
|
||||
@@ -48,7 +49,11 @@ export type SearchForm = {
|
||||
queryBuilderType: QueryBuilderType;
|
||||
};
|
||||
|
||||
export const SearchInput = ({ library, initialSearchFilter }: Props) => {
|
||||
export const SearchInput = ({
|
||||
libraryId,
|
||||
initialSearchFilter,
|
||||
mediaSourceId,
|
||||
}: Props) => {
|
||||
const routeMatch = useMatches();
|
||||
const formMethods = useForm<SearchForm>({
|
||||
defaultValues: {
|
||||
@@ -198,7 +203,8 @@ export const SearchInput = ({ library, initialSearchFilter }: Props) => {
|
||||
)}
|
||||
/>
|
||||
<SearchFilterBuilder
|
||||
library={library}
|
||||
libraryId={libraryId}
|
||||
mediaSourceId={mediaSourceId}
|
||||
onSearch={handleSearchChange}
|
||||
/>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
|
||||
@@ -14,8 +14,8 @@ import { OperatorsByType } from '@tunarr/types/api';
|
||||
import { find, flatten, isArray, isNumber, map } from 'lodash-es';
|
||||
import { useCallback } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import type { SearchFieldSpec } from '../../helpers/searchBuilderConstants.ts';
|
||||
import {
|
||||
SearchFieldSpec,
|
||||
SearchFieldSpecs,
|
||||
getOperatorLabel,
|
||||
} from '../../helpers/searchBuilderConstants.ts';
|
||||
@@ -33,7 +33,16 @@ type ValueNodeProps = GroupNodeProps & {
|
||||
};
|
||||
|
||||
export function SearchValueNode(props: ValueNodeProps) {
|
||||
const { library, depth, index, formKey, only, remove } = props;
|
||||
const {
|
||||
libraryId,
|
||||
mediaSourceId,
|
||||
mediaTypeFilter,
|
||||
depth,
|
||||
index,
|
||||
formKey,
|
||||
only,
|
||||
remove,
|
||||
} = props;
|
||||
const { control, watch, setValue } = useFormContext<SearchForm>();
|
||||
const selfValue = watch(formKey) as SearchFilterValueNode;
|
||||
const getFieldName = useGetFieldName(formKey);
|
||||
@@ -113,7 +122,7 @@ export function SearchValueNode(props: ValueNodeProps) {
|
||||
shouldDirty: true,
|
||||
});
|
||||
},
|
||||
[getFieldName, setValue],
|
||||
[dayjs, getFieldName, setValue],
|
||||
);
|
||||
|
||||
const handleOpChange = useCallback(
|
||||
@@ -185,7 +194,8 @@ export function SearchValueNode(props: ValueNodeProps) {
|
||||
return (
|
||||
<FacetStringValueSearchNode
|
||||
formKey={getFieldName('fieldSpec')}
|
||||
library={library}
|
||||
libraryId={libraryId}
|
||||
mediaSourceId={mediaSourceId}
|
||||
field={fieldSpec}
|
||||
/>
|
||||
);
|
||||
@@ -193,7 +203,6 @@ export function SearchValueNode(props: ValueNodeProps) {
|
||||
return (
|
||||
<DateSearchValueNode
|
||||
formKey={getFieldName('fieldSpec')}
|
||||
library={library}
|
||||
field={fieldSpec}
|
||||
/>
|
||||
);
|
||||
@@ -237,9 +246,9 @@ export function SearchValueNode(props: ValueNodeProps) {
|
||||
>
|
||||
{seq.collect(SearchFieldSpecs, (spec) => {
|
||||
if (
|
||||
library &&
|
||||
mediaTypeFilter &&
|
||||
isArray(spec.visibleForLibraryTypes) &&
|
||||
!spec.visibleForLibraryTypes.includes(library.mediaType)
|
||||
!spec.visibleForLibraryTypes.includes(mediaTypeFilter)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12460,6 +12460,7 @@ export type PostApiProgramsFacetsByFacetNameData = {
|
||||
};
|
||||
query?: {
|
||||
facetQuery?: string;
|
||||
mediaSourceId?: string;
|
||||
libraryId?: string;
|
||||
};
|
||||
url: '/api/programs/facets/{facetName}';
|
||||
|
||||
@@ -36,7 +36,7 @@ function MediaSourceBrowserPage() {
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={2}>
|
||||
<SearchInput library={library} />
|
||||
<SearchInput libraryId={libraryId} />
|
||||
<LibraryProgramGrid
|
||||
mediaSource={library.mediaSource}
|
||||
library={library}
|
||||
|
||||
Reference in New Issue
Block a user