From 30ba45cc11c1d99bb931f7ca57da6125594df430 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:57:15 +0300 Subject: [PATCH 1/7] Migrate installed plugins page to React --- src/apps/dashboard/components/BaseCard.tsx | 73 +++++ .../controllers/plugins/installed/index.html | 13 - .../controllers/plugins/installed/index.js | 251 ------------------ .../plugins/components/PluginCard.tsx | 133 ++++++++++ src/apps/dashboard/routes/_asyncRoutes.ts | 1 + src/apps/dashboard/routes/_legacyRoutes.ts | 7 - src/apps/dashboard/routes/plugins/index.tsx | 86 ++++++ src/strings/en-us.json | 1 + 8 files changed, 294 insertions(+), 271 deletions(-) create mode 100644 src/apps/dashboard/components/BaseCard.tsx delete mode 100644 src/apps/dashboard/controllers/plugins/installed/index.html delete mode 100644 src/apps/dashboard/controllers/plugins/installed/index.js create mode 100644 src/apps/dashboard/features/plugins/components/PluginCard.tsx create mode 100644 src/apps/dashboard/routes/plugins/index.tsx diff --git a/src/apps/dashboard/components/BaseCard.tsx b/src/apps/dashboard/components/BaseCard.tsx new file mode 100644 index 000000000..1f4245461 --- /dev/null +++ b/src/apps/dashboard/components/BaseCard.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import Card from '@mui/material/Card'; +import CardHeader from '@mui/material/CardHeader'; +import CardMedia from '@mui/material/CardMedia'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import ExtensionIcon from '@mui/icons-material/Extension'; +import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils'; +import CardActionArea from '@mui/material/CardActionArea'; + +interface IProps { + title?: string; + text?: string; + image?: string | null; + onClick?: () => void; + action?: boolean; + actionRef: React.MutableRefObject; + onActionClick?: () => void; +}; + +const BaseCard = ({ title, text, image, onClick, action, actionRef, onActionClick }: IProps) => { + return ( + + + {image ? ( + + ) : ( + + + + )} + + {title}} + subheader={text} + action={ + action ? ( + + + + ) : null + } + /> + + ); +}; + +export default BaseCard; diff --git a/src/apps/dashboard/controllers/plugins/installed/index.html b/src/apps/dashboard/controllers/plugins/installed/index.html deleted file mode 100644 index b29fd6eee..000000000 --- a/src/apps/dashboard/controllers/plugins/installed/index.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-
-
-

${TabMyPlugins}

-
-
- -
-
-
-
-
diff --git a/src/apps/dashboard/controllers/plugins/installed/index.js b/src/apps/dashboard/controllers/plugins/installed/index.js deleted file mode 100644 index b3971ecf1..000000000 --- a/src/apps/dashboard/controllers/plugins/installed/index.js +++ /dev/null @@ -1,251 +0,0 @@ -import loading from 'components/loading/loading'; -import dom from 'scripts/dom'; -import globalize from 'lib/globalize'; -import 'components/cardbuilder/card.scss'; -import 'elements/emby-button/emby-button'; -import Dashboard, { pageIdOn } from 'utils/dashboard'; -import confirm from 'components/confirm/confirm'; -import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils'; - -function deletePlugin(page, uniqueid, version, name) { - const msg = globalize.translate('UninstallPluginConfirmation', name); - - confirm({ - title: globalize.translate('HeaderUninstallPlugin'), - text: msg, - primary: 'delete', - confirmText: globalize.translate('HeaderUninstallPlugin') - }).then(function () { - loading.show(); - ApiClient.uninstallPluginByVersion(uniqueid, version).then(function () { - reloadList(page); - }); - }); -} - -function enablePlugin(page, uniqueid, version) { - loading.show(); - ApiClient.enablePlugin(uniqueid, version).then(function () { - reloadList(page); - }); -} - -function disablePlugin(page, uniqueid, version) { - loading.show(); - ApiClient.disablePlugin(uniqueid, version).then(function () { - reloadList(page); - }); -} - -function showNoConfigurationMessage() { - Dashboard.alert({ - message: globalize.translate('MessageNoPluginConfiguration') - }); -} - -function showConnectMessage() { - Dashboard.alert({ - message: globalize.translate('MessagePluginConfigurationRequiresLocalAccess') - }); -} - -function getPluginCardHtml(plugin, pluginConfigurationPages) { - const configPage = pluginConfigurationPages.filter(function (pluginConfigurationPage) { - return pluginConfigurationPage.PluginId == plugin.Id; - })[0]; - - const configPageUrl = configPage ? Dashboard.getPluginUrl(configPage.Name) : null; - let html = ''; - - html += `
`; - html += '
'; - html += ''; - html += '
'; - - if (configPage || plugin.CanUninstall) { - if (globalize.getIsRTL()) { - html += '
'; - } else { - html += '
'; - } - html += ''; - html += '
'; - } - - html += '
'; - html += `${plugin.Name}${plugin.Version}`; - html += '
'; - html += `
${globalize.translate('LabelStatus')} ${plugin.Status}
`; - html += '
'; - html += '
'; - html += '
'; - return html; -} - -function renderPlugins(page, plugins) { - ApiClient.getJSON(ApiClient.getUrl('web/configurationpages') + '?pageType=PluginConfiguration').then(function (configPages) { - populateList(page, plugins, configPages); - }); -} - -function populateList(page, plugins, pluginConfigurationPages) { - plugins = plugins.sort(function (plugin1, plugin2) { - if (plugin1.Name > plugin2.Name) { - return 1; - } - return -1; - }); - - let html = plugins.map(function (p) { - return getPluginCardHtml(p, pluginConfigurationPages); - }).join(''); - - const installedPluginsElement = page.querySelector('.installedPlugins'); - installedPluginsElement.removeEventListener('click', onInstalledPluginsClick); - installedPluginsElement.addEventListener('click', onInstalledPluginsClick); - - if (plugins.length) { - installedPluginsElement.classList.add('itemsContainer'); - installedPluginsElement.classList.add('vertical-wrap'); - } else { - html += '
'; - html += '

' + globalize.translate('MessageNoPluginsInstalled') + '

'; - html += '

'; - html += globalize.translate('MessageBrowsePluginCatalog'); - html += '

'; - html += '
'; - } - - // add search box listener - const searchBar = page.querySelector('#txtSearchPlugins'); - if (searchBar) { - searchBar.addEventListener('input', () => onFilterType(page, searchBar)); - } - - installedPluginsElement.innerHTML = html; - loading.hide(); -} - -function showPluginMenu(page, elem) { - const card = dom.parentWithClass(elem, 'card'); - const id = card.getAttribute('data-id'); - const name = card.getAttribute('data-name'); - const removable = card.getAttribute('data-removable'); - const configHref = card.querySelector('.cardImageContainer').getAttribute('href'); - const status = card.getAttribute('data-status'); - const version = card.getAttribute('data-version'); - const menuItems = []; - - if (configHref) { - menuItems.push({ - name: globalize.translate('Settings'), - id: 'open', - icon: 'mode_edit' - }); - } - - if (removable === 'true') { - if (status === 'Disabled') { - menuItems.push({ - name: globalize.translate('EnablePlugin'), - id: 'enable', - icon: 'check_circle_outline' - }); - } - - if (status === 'Active') { - menuItems.push({ - name: globalize.translate('DisablePlugin'), - id: 'disable', - icon: 'do_not_disturb' - }); - } - - menuItems.push({ - name: globalize.translate('ButtonUninstall'), - id: 'delete', - icon: 'delete' - }); - } - - import('components/actionSheet/actionSheet').then((actionsheet) => { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'open': - Dashboard.navigate(configHref); - break; - case 'delete': - deletePlugin(page, id, version, name); - break; - case 'enable': - enablePlugin(page, id, version); - break; - case 'disable': - disablePlugin(page, id, version); - break; - } - } - }); - }); -} - -function reloadList(page) { - loading.show(); - ApiClient.getInstalledPlugins().then(function (plugins) { - renderPlugins(page, plugins); - }); -} - -function onInstalledPluginsClick(e) { - if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { - showNoConfigurationMessage(); - } else if (dom.parentWithClass(e.target, 'connectModePluginCard')) { - showConnectMessage(); - } else { - const btnCardMenu = dom.parentWithClass(e.target, 'btnCardMenu'); - if (btnCardMenu) { - showPluginMenu(dom.parentWithClass(btnCardMenu, 'page'), btnCardMenu); - } - } -} - -function onFilterType(page, searchBar) { - const filter = searchBar.value.toLowerCase(); - for (const card of page.querySelectorAll('.card')) { - if (filter && filter != '' && !card.textContent.toLowerCase().includes(filter)) { - card.style.display = 'none'; - } else { - card.style.display = 'unset'; - } - } -} - -pageIdOn('pageshow', 'pluginsPage', function () { - reloadList(this); -}); - -window.PluginsPage = { - renderPlugins: renderPlugins -}; diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx new file mode 100644 index 000000000..f1246fe64 --- /dev/null +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -0,0 +1,133 @@ +import React, { useCallback, 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'; +import globalize from 'lib/globalize'; +import BaseCard from 'apps/dashboard/components/BaseCard'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Settings from '@mui/icons-material/Settings'; +import Delete from '@mui/icons-material/Delete'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import BlockIcon from '@mui/icons-material/Block'; +import ListItemText from '@mui/material/ListItemText'; +import { PluginStatus } from '@jellyfin/sdk/lib/generated-client/models/plugin-status'; +import type { ConfigurationPageInfo } from '@jellyfin/sdk/lib/generated-client/models/configuration-page-info'; +import { useEnablePlugin } from '../api/useEnablePlugin'; +import { useDisablePlugin } from '../api/useDisablePlugin'; + +interface IProps { + plugin: PluginInfo; + configurationPage?: ConfigurationPageInfo; +}; + +const PluginCard = ({ plugin, configurationPage }: IProps) => { + const location = useLocation(); + const navigate = useNavigate(); + const actionRef = useRef(null); + const enablePlugin = useEnablePlugin(); + const disablePlugin = useDisablePlugin(); + const [ anchorEl, setAnchorEl ] = useState(null); + const [ isMenuOpen, setMenuOpen ] = useState(false); + const { api } = useApi(); + + const navigateToPluginSettings = useCallback(() => { + if (configurationPage) { + navigate({ + pathname: '/configurationpage', + search: `?name=${encodeURIComponent(configurationPage.Name || '')}`, + hash: location.hash + }); + } + }, [ navigate, location, configurationPage ]); + + const onEnablePlugin = useCallback(() => { + if (plugin.Id && plugin.Version) { + enablePlugin.mutate({ + pluginId: plugin.Id, + version: plugin.Version + }); + setAnchorEl(null); + setMenuOpen(false); + } + }, [ plugin, enablePlugin ]); + + const onDisablePlugin = useCallback(() => { + if (plugin.Id && plugin.Version) { + disablePlugin.mutate({ + pluginId: plugin.Id, + version: plugin.Version + }); + setAnchorEl(null); + setMenuOpen(false); + } + }, [ plugin, disablePlugin ]); + + const onMenuClose = useCallback(() => { + setAnchorEl(null); + setMenuOpen(false); + }, []); + + const onActionClick = useCallback(() => { + setAnchorEl(actionRef.current); + setMenuOpen(true); + }, []); + + return ( + <> + + + {configurationPage && ( + + + + + {globalize.translate('Settings')} + + )} + + {(plugin.CanUninstall && plugin.Status == PluginStatus.Active) && ( + + + + + {globalize.translate('DisablePlugin')} + + )} + + {(plugin.CanUninstall && plugin.Status == PluginStatus.Disabled) && ( + + + + + {globalize.translate('EnablePlugin')} + + )} + + {plugin.CanUninstall && ( + + + + + {globalize.translate('ButtonUninstall')} + + )} + + + ); +}; + +export default PluginCard; diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 2ab21c5d4..44029227a 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -15,6 +15,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'playback/resume', type: AppType.Dashboard }, { path: 'playback/streaming', type: AppType.Dashboard }, { path: 'playback/trickplay', type: AppType.Dashboard }, + { path: 'plugins', type: AppType.Dashboard }, { path: 'plugins/:pluginId', page: 'plugins/plugin', type: AppType.Dashboard }, { path: 'tasks', type: AppType.Dashboard }, { path: 'tasks/:id', page: 'tasks/task', type: AppType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index ceedb7265..aa51a6f91 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -72,12 +72,5 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'livetvtuner', view: 'livetvtuner.html' } - }, { - path: 'plugins', - pageProps: { - appType: AppType.Dashboard, - controller: 'plugins/installed/index', - view: 'plugins/installed/index.html' - } } ]; diff --git a/src/apps/dashboard/routes/plugins/index.tsx b/src/apps/dashboard/routes/plugins/index.tsx new file mode 100644 index 000000000..8e21ccdd8 --- /dev/null +++ b/src/apps/dashboard/routes/plugins/index.tsx @@ -0,0 +1,86 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import Box from '@mui/material/Box'; +import Page from 'components/Page'; +import globalize from 'lib/globalize'; +import Typography from '@mui/material/Typography'; +import TextField from '@mui/material/TextField'; +import Stack from '@mui/material/Stack'; +import { usePlugins } from 'apps/dashboard/features/plugins/api/usePlugins'; +import Loading from 'components/loading/LoadingComponent'; +import Alert from '@mui/material/Alert'; +import Grid from '@mui/material/Grid2'; +import PluginCard from 'apps/dashboard/features/plugins/components/PluginCard'; +import { useConfigurationPages } from 'apps/dashboard/features/plugins/api/useConfigurationPages'; +import { findBestConfigurationPage } from 'apps/dashboard/features/plugins/api/configurationPage'; + +export const Component = () => { + const { + data: plugins, + isPending, + isError } = usePlugins(); + const { + data: configurationPages, + isError: isConfigurationPagesError, + isPending: isConfigurationPagesPending + } = useConfigurationPages(); + const [ searchQuery, setSearchQuery ] = useState(''); + + const onSearchChange = useCallback((event: React.ChangeEvent) => { + setSearchQuery(event.target.value); + }, []); + + const filteredPlugins = useMemo(() => { + if (plugins) { + return plugins.filter(i => i.Name?.toLocaleLowerCase().includes(searchQuery.toLocaleLowerCase())); + } else { + return []; + } + }, [ plugins, searchQuery ]); + + if (isPending || isConfigurationPagesPending) { + return ; + } + + return ( + + + {isError || isConfigurationPagesError ? ( + {globalize.translate('PluginsLoadError')} + ) : ( + + + {globalize.translate('TabMyPlugins')} + + + + + + + {filteredPlugins.map(plugin => { + return ( + + + + ); + })} + + + + )} + + + ); +}; + +Component.displayName = 'InstalledPlugins'; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 5402157b2..adbbb5e8b 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1384,6 +1384,7 @@ "PluginLoadConfigError": "An error occurred while getting the plugin configuration pages.", "PluginLoadRepoError": "An error occurred while getting the plugin details from the repository.", "PluginUninstallError": "An error occurred while uninstalling the plugin.", + "PluginsLoadError": "An error occurred while loading plugins.", "Poster": "Poster", "PosterCard": "Poster Card", "PreferEmbeddedEpisodeInfosOverFileNames": "Prefer embedded episode information over filenames", From 1329e72aa10705e702d3cbc9efd2f151810a159c Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:46:38 +0300 Subject: [PATCH 2/7] Add icon prop to BaseCard --- src/apps/dashboard/components/BaseCard.tsx | 6 +++--- .../dashboard/features/plugins/components/PluginCard.tsx | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/apps/dashboard/components/BaseCard.tsx b/src/apps/dashboard/components/BaseCard.tsx index 1f4245461..1ba6ce5ec 100644 --- a/src/apps/dashboard/components/BaseCard.tsx +++ b/src/apps/dashboard/components/BaseCard.tsx @@ -6,7 +6,6 @@ import CardMedia from '@mui/material/CardMedia'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import MoreVertIcon from '@mui/icons-material/MoreVert'; -import ExtensionIcon from '@mui/icons-material/Extension'; import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils'; import CardActionArea from '@mui/material/CardActionArea'; @@ -14,13 +13,14 @@ interface IProps { title?: string; text?: string; image?: string | null; + icon?: React.ReactNode; onClick?: () => void; action?: boolean; actionRef: React.MutableRefObject; onActionClick?: () => void; }; -const BaseCard = ({ title, text, image, onClick, action, actionRef, onActionClick }: IProps) => { +const BaseCard = ({ title, text, image, icon, onClick, action, actionRef, onActionClick }: IProps) => { return ( - + {icon} )} diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx index f1246fe64..aca2a090b 100644 --- a/src/apps/dashboard/features/plugins/components/PluginCard.tsx +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -11,6 +11,7 @@ import Settings from '@mui/icons-material/Settings'; import Delete from '@mui/icons-material/Delete'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import BlockIcon from '@mui/icons-material/Block'; +import ExtensionIcon from '@mui/icons-material/Extension'; import ListItemText from '@mui/material/ListItemText'; import { PluginStatus } from '@jellyfin/sdk/lib/generated-client/models/plugin-status'; import type { ConfigurationPageInfo } from '@jellyfin/sdk/lib/generated-client/models/configuration-page-info'; @@ -80,6 +81,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { title={plugin.Name} text={`${globalize.translate('LabelStatus')} ${plugin.Status}`} image={plugin.HasImage ? api?.getUri(`/Plugins/${plugin.Id}/${plugin.Version}/Image`) : null} + icon={} action={true} actionRef={actionRef} onClick={navigateToPluginSettings} From b336175a7a357452a443e347ea36c5d739e30bdc Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:07:25 +0300 Subject: [PATCH 3/7] Add uninstall option --- .../plugins/components/PluginCard.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx index aca2a090b..8ad6885f1 100644 --- a/src/apps/dashboard/features/plugins/components/PluginCard.tsx +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -17,6 +17,8 @@ import { PluginStatus } from '@jellyfin/sdk/lib/generated-client/models/plugin-s import type { ConfigurationPageInfo } from '@jellyfin/sdk/lib/generated-client/models/configuration-page-info'; import { useEnablePlugin } from '../api/useEnablePlugin'; import { useDisablePlugin } from '../api/useDisablePlugin'; +import { useUninstallPlugin } from '../api/useUninstallPlugin'; +import ConfirmDialog from 'components/ConfirmDialog'; interface IProps { plugin: PluginInfo; @@ -29,8 +31,10 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { const actionRef = useRef(null); const enablePlugin = useEnablePlugin(); const disablePlugin = useDisablePlugin(); + const uninstallPlugin = useUninstallPlugin(); const [ anchorEl, setAnchorEl ] = useState(null); const [ isMenuOpen, setMenuOpen ] = useState(false); + const [ isUninstallConfirmOpen, setIsUninstallConfirmOpen ] = useState(false); const { api } = useApi(); const navigateToPluginSettings = useCallback(() => { @@ -65,6 +69,27 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { } }, [ plugin, disablePlugin ]); + const onCloseUninstallConfirmDialog = useCallback(() => { + setIsUninstallConfirmOpen(false); + }, []); + + const showUninstallConfirmDialog = useCallback(() => { + setIsUninstallConfirmOpen(true); + setAnchorEl(null); + setMenuOpen(false); + }, []); + + const onUninstall = useCallback(() => { + if (plugin.Id && plugin.Version) { + uninstallPlugin.mutate({ + pluginId: plugin.Id, + version: plugin.Version + }); + setAnchorEl(null); + setMenuOpen(false); + } + }, [ plugin, uninstallPlugin ]); + const onMenuClose = useCallback(() => { setAnchorEl(null); setMenuOpen(false); @@ -120,7 +145,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { )} {plugin.CanUninstall && ( - + @@ -128,6 +153,15 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { )} + ); }; From 33a045df54118193902399ee5d14a5ebb5cad86c Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:15:55 +0300 Subject: [PATCH 4/7] Add version sub-secondary text --- src/apps/dashboard/components/BaseCard.tsx | 23 ++++++++++++++----- .../plugins/components/PluginCard.tsx | 1 + 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/apps/dashboard/components/BaseCard.tsx b/src/apps/dashboard/components/BaseCard.tsx index 1ba6ce5ec..2dcc77f95 100644 --- a/src/apps/dashboard/components/BaseCard.tsx +++ b/src/apps/dashboard/components/BaseCard.tsx @@ -8,9 +8,11 @@ import Typography from '@mui/material/Typography'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import { getDefaultBackgroundClass } from 'components/cardbuilder/cardBuilderUtils'; import CardActionArea from '@mui/material/CardActionArea'; +import Stack from '@mui/material/Stack'; interface IProps { title?: string; + secondaryTitle?: string; text?: string; image?: string | null; icon?: React.ReactNode; @@ -20,7 +22,7 @@ interface IProps { onActionClick?: () => void; }; -const BaseCard = ({ title, text, image, icon, onClick, action, actionRef, onActionClick }: IProps) => { +const BaseCard = ({ title, secondaryTitle, text, image, icon, onClick, action, actionRef, onActionClick }: IProps) => { return ( {title}} + title={ + + + {title} + + {secondaryTitle && ( + {secondaryTitle} + )} + + } subheader={text} action={ action ? ( diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx index 8ad6885f1..299e6e795 100644 --- a/src/apps/dashboard/features/plugins/components/PluginCard.tsx +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -104,6 +104,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { <> } From efee261b50a27b233631d1d5e9869fefd1573b47 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Thu, 5 Jun 2025 21:32:16 +0300 Subject: [PATCH 5/7] Apply review feedback --- src/apps/dashboard/components/BaseCard.tsx | 2 +- src/apps/dashboard/features/plugins/components/PluginCard.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/dashboard/components/BaseCard.tsx b/src/apps/dashboard/components/BaseCard.tsx index 2dcc77f95..f6e683146 100644 --- a/src/apps/dashboard/components/BaseCard.tsx +++ b/src/apps/dashboard/components/BaseCard.tsx @@ -18,7 +18,7 @@ interface IProps { icon?: React.ReactNode; onClick?: () => void; action?: boolean; - actionRef: React.MutableRefObject; + actionRef?: React.MutableRefObject; onActionClick?: () => void; }; diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx index 299e6e795..d6efc1b1a 100644 --- a/src/apps/dashboard/features/plugins/components/PluginCard.tsx +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -127,7 +127,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { )} - {(plugin.CanUninstall && plugin.Status == PluginStatus.Active) && ( + {(plugin.CanUninstall && plugin.Status === PluginStatus.Active) && ( @@ -136,7 +136,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { )} - {(plugin.CanUninstall && plugin.Status == PluginStatus.Disabled) && ( + {(plugin.CanUninstall && plugin.Status === PluginStatus.Disabled) && ( From e4fa463f591abde70658111baa6df0dd1da4aa87 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Sat, 7 Jun 2025 10:24:15 +0300 Subject: [PATCH 6/7] Add target _blank to link --- src/apps/dashboard/routes/settings/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/dashboard/routes/settings/index.tsx b/src/apps/dashboard/routes/settings/index.tsx index b84f26363..9726190ea 100644 --- a/src/apps/dashboard/routes/settings/index.tsx +++ b/src/apps/dashboard/routes/settings/index.tsx @@ -158,7 +158,7 @@ export const Component = () => { helperText={( <> {globalize.translate('LabelDisplayLanguageHelp')} - + {globalize.translate('LearnHowYouCanContribute')} From 4ec1dba26f45f05da028ea1e34e8af07720b9436 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:01:02 +0300 Subject: [PATCH 7/7] Apply review feedback Co-authored-by: Bill Thornton --- .../plugins/components/PluginCard.tsx | 14 +++++------ src/apps/dashboard/routes/plugins/index.tsx | 23 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/apps/dashboard/features/plugins/components/PluginCard.tsx b/src/apps/dashboard/features/plugins/components/PluginCard.tsx index d6efc1b1a..08bd40bbe 100644 --- a/src/apps/dashboard/features/plugins/components/PluginCard.tsx +++ b/src/apps/dashboard/features/plugins/components/PluginCard.tsx @@ -33,7 +33,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { const disablePlugin = useDisablePlugin(); const uninstallPlugin = useUninstallPlugin(); const [ anchorEl, setAnchorEl ] = useState(null); - const [ isMenuOpen, setMenuOpen ] = useState(false); + const [ isMenuOpen, setIsMenuOpen ] = useState(false); const [ isUninstallConfirmOpen, setIsUninstallConfirmOpen ] = useState(false); const { api } = useApi(); @@ -54,7 +54,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { version: plugin.Version }); setAnchorEl(null); - setMenuOpen(false); + setIsMenuOpen(false); } }, [ plugin, enablePlugin ]); @@ -65,7 +65,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { version: plugin.Version }); setAnchorEl(null); - setMenuOpen(false); + setIsMenuOpen(false); } }, [ plugin, disablePlugin ]); @@ -76,7 +76,7 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { const showUninstallConfirmDialog = useCallback(() => { setIsUninstallConfirmOpen(true); setAnchorEl(null); - setMenuOpen(false); + setIsMenuOpen(false); }, []); const onUninstall = useCallback(() => { @@ -86,18 +86,18 @@ const PluginCard = ({ plugin, configurationPage }: IProps) => { version: plugin.Version }); setAnchorEl(null); - setMenuOpen(false); + setIsMenuOpen(false); } }, [ plugin, uninstallPlugin ]); const onMenuClose = useCallback(() => { setAnchorEl(null); - setMenuOpen(false); + setIsMenuOpen(false); }, []); const onActionClick = useCallback(() => { setAnchorEl(actionRef.current); - setMenuOpen(true); + setIsMenuOpen(true); }, []); return ( diff --git a/src/apps/dashboard/routes/plugins/index.tsx b/src/apps/dashboard/routes/plugins/index.tsx index 8e21ccdd8..aacdd06ce 100644 --- a/src/apps/dashboard/routes/plugins/index.tsx +++ b/src/apps/dashboard/routes/plugins/index.tsx @@ -17,7 +17,8 @@ export const Component = () => { const { data: plugins, isPending, - isError } = usePlugins(); + isError + } = usePlugins(); const { data: configurationPages, isError: isConfigurationPagesError, @@ -63,17 +64,15 @@ export const Component = () => { /> - - {filteredPlugins.map(plugin => { - return ( - - - - ); - })} + + {filteredPlugins.map(plugin => ( + + + + ))}