Migrate plugins catalog to React

This commit is contained in:
viown
2025-06-11 16:53:51 +03:00
parent 3aaba84281
commit 92cdfcc1ea
13 changed files with 217 additions and 204 deletions
@@ -1,6 +1,6 @@
export enum QueryKey {
ConfigurationPages = 'ConfigurationPages',
PackageInfo = 'PackageInfo',
Packages = 'Packages',
Plugins = 'Plugins',
Repositories = 'Repositories'
}
@@ -7,12 +7,24 @@ import type { AxiosRequestConfig } from 'axios';
import { useApi } from 'hooks/useApi';
import { QueryKey } from './queryKey';
import { queryClient } from 'utils/query/queryClient';
import type { PackageInfo } from '@jellyfin/sdk/lib/generated-client/models/package-info';
const fetchPackageInfo = async (
api: Api,
params: PackageApiGetPackageInfoRequest,
options?: AxiosRequestConfig
) => {
const packagesData = queryClient.getQueryData([ QueryKey.Packages ]) as PackageInfo[];
if (packagesData && params.assemblyGuid) {
// Use cached query to avoid re-fetching
const pkg = packagesData.find(v => v.guid === params.assemblyGuid);
if (pkg) {
return pkg;
}
}
const response = await getPackageApi(api)
.getPackageInfo(params, options);
return response.data;
@@ -24,7 +36,7 @@ const getPackageInfoQuery = (
) => queryOptions({
// Don't retry since requests for plugins not available in repos fail
retry: false,
queryKey: [ QueryKey.PackageInfo, params?.name, params?.assemblyGuid ],
queryKey: [ QueryKey.Packages, params?.name, params?.assemblyGuid ],
queryFn: ({ signal }) => fetchPackageInfo(api!, params!, { signal }),
enabled: !!params && !!api && !!params.name
});
@@ -0,0 +1,32 @@
import type { Api } from '@jellyfin/sdk';
import { getPackageApi } from '@jellyfin/sdk/lib/utils/api/package-api';
import { queryOptions, useQuery } from '@tanstack/react-query';
import type { AxiosRequestConfig } from 'axios';
import { useApi } from 'hooks/useApi';
import { QueryKey } from './queryKey';
const fetchPackages = async (
api: Api,
options?: AxiosRequestConfig
) => {
const response = await getPackageApi(api)
.getPackages(options);
return response.data;
};
const getPackagesQuery = (
api?: Api
) => queryOptions({
queryKey: [ QueryKey.Packages ],
queryFn: ({ signal }) => fetchPackages(api!, { signal }),
enabled: !!api,
staleTime: 15 * 60 * 1000 // 15 minutes
});
export const usePackages = () => {
const { api } = useApi();
return useQuery(getPackagesQuery(api));
};
@@ -0,0 +1,28 @@
import React from 'react';
import type { PackageInfo } from '@jellyfin/sdk/lib/generated-client/models/package-info';
import ExtensionIcon from '@mui/icons-material/Extension';
import BaseCard from 'apps/dashboard/components/BaseCard';
import { useLocation } from 'react-router-dom';
type IProps = {
pkg: PackageInfo;
};
const PackageCard = ({ pkg }: IProps) => {
const location = useLocation();
return (
<BaseCard
title={pkg.name}
image={pkg.imageUrl}
icon={<ExtensionIcon sx={{ width: 80, height: 80 }} />}
to={{
pathname: `/dashboard/plugins/${pkg.guid}`,
search: `?name=${encodeURIComponent(pkg.name || '')}`,
hash: location.hash
}}
/>
);
};
export default PackageCard;
@@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useApi } from 'hooks/useApi';
import type { PluginInfo } from '@jellyfin/sdk/lib/generated-client/models/plugin-info';
@@ -37,14 +37,16 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => {
const [ isUninstallConfirmOpen, setIsUninstallConfirmOpen ] = useState(false);
const { api } = useApi();
const navigateToPluginSettings = useCallback(() => {
if (configurationPage) {
navigate({
pathname: '/configurationpage',
search: `?name=${encodeURIComponent(configurationPage.Name || '')}`,
hash: location.hash
});
const pluginPage = useMemo(() => (
{
pathname: '/configurationpage',
search: `?name=${encodeURIComponent(configurationPage?.Name || '')}`,
hash: location.hash
}
), [ location, configurationPage ]);
const navigateToPluginSettings = useCallback(() => {
navigate(pluginPage);
}, [ navigate, location, configurationPage ]);
const onEnablePlugin = useCallback(() => {
@@ -105,12 +107,12 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => {
<BaseCard
title={plugin.Name}
secondaryTitle={plugin.Version}
to={pluginPage}
text={`${globalize.translate('LabelStatus')} ${plugin.Status}`}
image={plugin.HasImage ? api?.getUri(`/Plugins/${plugin.Id}/${plugin.Version}/Image`) : null}
icon={<ExtensionIcon sx={{ width: 80, height: 80 }} />}
action={true}
actionRef={actionRef}
onClick={navigateToPluginSettings}
onActionClick={onActionClick}
/>
<Menu
@@ -0,0 +1,17 @@
import type { PackageInfo } from '@jellyfin/sdk/lib/generated-client/models/package-info';
const getPackageCategories = (packages?: PackageInfo[]) => {
if (!packages) return [];
const categories: string[] = [];
for (const pkg of packages) {
if (pkg.category && !categories.includes(pkg.category)) {
categories.push(pkg.category);
}
}
return categories.sort((a, b) => a.localeCompare(b));
};
export default getPackageCategories;
@@ -0,0 +1,15 @@
import type { PackageInfo } from '@jellyfin/sdk/lib/generated-client/models/package-info';
const getPackagesByCategory = (packages: PackageInfo[] | undefined, category: string) => {
if (!packages) return [];
return packages.filter(pkg => pkg.category == category).sort((a, b) => {
if (a.name && b.name) {
return a.name?.localeCompare(b.name);
} else {
return 0;
}
});
};
export default getPackagesByCategory;