From 69ed608241cdaa1a32108c1122d8fbb297e5a2a5 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Tue, 9 Jun 2026 20:58:39 +0200 Subject: [PATCH] refactor: remove deprecated navidrome/audiobookshelf widgets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate into the unified Audio Stats widget which adapts its UI based on the linked integration kind — Navidrome shows artists/albums/ songs/now-playing, Audiobookshelf shows libraries/audiobooks/podcasts/ listening-time/sessions. Removes: widget definitions, components, API routers, and the deprecatedWidgetKinds mechanism (no longer needed). --- .../board/items/item-select-modal.tsx | 3 +- .../api/src/router/widgets/audiobookshelf.ts | 12 ----- packages/api/src/router/widgets/index.ts | 2 - packages/api/src/router/widgets/navidrome.ts | 21 --------- packages/definitions/src/widget.ts | 6 --- .../src/audio-stats/audio-stats-content.tsx | 6 +-- .../widgets/src/audio-stats/component.tsx | 1 - packages/widgets/src/audio-stats/shared.ts | 6 +-- .../widgets/src/audiobookshelf/component.tsx | 38 --------------- packages/widgets/src/audiobookshelf/index.ts | 44 ----------------- packages/widgets/src/index.tsx | 4 -- packages/widgets/src/navidrome/component.tsx | 34 -------------- packages/widgets/src/navidrome/index.ts | 47 ------------------- 13 files changed, 4 insertions(+), 220 deletions(-) delete mode 100644 packages/api/src/router/widgets/audiobookshelf.ts delete mode 100644 packages/api/src/router/widgets/navidrome.ts delete mode 100644 packages/widgets/src/audiobookshelf/component.tsx delete mode 100644 packages/widgets/src/audiobookshelf/index.ts delete mode 100644 packages/widgets/src/navidrome/component.tsx delete mode 100644 packages/widgets/src/navidrome/index.ts diff --git a/apps/nextjs/src/components/board/items/item-select-modal.tsx b/apps/nextjs/src/components/board/items/item-select-modal.tsx index fb540f603..84947d150 100644 --- a/apps/nextjs/src/components/board/items/item-select-modal.tsx +++ b/apps/nextjs/src/components/board/items/item-select-modal.tsx @@ -3,7 +3,7 @@ import { Avatar, Box, Button, Card, Center, Group, Stack, Text, Tooltip } from " import { clientApi } from "@homarr/api/client"; import { createId, objectEntries } from "@homarr/common"; -import { deprecatedWidgetKinds, getIconUrl, getIntegrationName } from "@homarr/definitions"; +import { getIconUrl, getIntegrationName } from "@homarr/definitions"; import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; import { createModal, modalSizeSelect, useModalAction } from "@homarr/modals"; import { useSettings } from "@homarr/settings"; @@ -26,7 +26,6 @@ export const ItemSelectModal = createModal(({ actions }) => { const items = useMemo( () => objectEntries(widgetImports) - .filter(([kind]) => !deprecatedWidgetKinds.has(kind)) .map(([kind, value]) => ({ kind, supportedIntegrations: diff --git a/packages/api/src/router/widgets/audiobookshelf.ts b/packages/api/src/router/widgets/audiobookshelf.ts deleted file mode 100644 index 3048f8397..000000000 --- a/packages/api/src/router/widgets/audiobookshelf.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { audiobookshelfRequestHandler } from "@homarr/request-handler/audiobookshelf"; - -import { createOneIntegrationMiddleware } from "../../middlewares/integration"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; - -export const audiobookshelfRouter = createTRPCRouter({ - getStats: publicProcedure.concat(createOneIntegrationMiddleware("query", "audiobookshelf")).query(async ({ ctx }) => { - const innerHandler = audiobookshelfRequestHandler.handler(ctx.integration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return data.data; - }), -}); diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts index 9d1a2ab01..393537d68 100644 --- a/packages/api/src/router/widgets/index.ts +++ b/packages/api/src/router/widgets/index.ts @@ -33,8 +33,6 @@ export const widgetRouter = createTRPCRouter({ speedtestTracker: lazy(() => import("./speedtest-tracker").then((mod) => mod.speedtestTrackerRouter)), uptimeKuma: lazy(() => import("./uptime-kuma").then((mod) => mod.uptimeKumaRouter)), audioStats: lazy(() => import("./audio-stats").then((mod) => mod.audioStatsRouter)), - audiobookshelf: lazy(() => import("./audiobookshelf").then((mod) => mod.audiobookshelfRouter)), - navidrome: lazy(() => import("./navidrome").then((mod) => mod.navidromeRouter)), umami: lazy(() => import("./umami").then((mod) => mod.umamiRouter)), ups: lazy(() => import("./ups").then((mod) => mod.upsRouter)), }); diff --git a/packages/api/src/router/widgets/navidrome.ts b/packages/api/src/router/widgets/navidrome.ts deleted file mode 100644 index 0bb27a4eb..000000000 --- a/packages/api/src/router/widgets/navidrome.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { z } from "zod/v4"; - -import { navidromeRequestHandler } from "@homarr/request-handler/navidrome"; - -import { createOneIntegrationMiddleware } from "../../middlewares/integration"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; - -export const navidromeRouter = createTRPCRouter({ - getStats: publicProcedure - .concat(createOneIntegrationMiddleware("query", "navidrome")) - .input( - z.object({ - integrationId: z.string(), - }), - ) - .query(async ({ ctx }) => { - const innerHandler = navidromeRequestHandler.handler(ctx.integration, {}); - const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return data.data; - }), -}); diff --git a/packages/definitions/src/widget.ts b/packages/definitions/src/widget.ts index c2a7e724e..10556a213 100644 --- a/packages/definitions/src/widget.ts +++ b/packages/definitions/src/widget.ts @@ -40,19 +40,13 @@ export const widgetKinds = [ "speedtestTracker", "uptimeKuma", "audioStats", - "audiobookshelf", - "navidrome", "umami", "ups", ] as const; export type WidgetKind = (typeof widgetKinds)[number]; -export const deprecatedWidgetKinds = new Set(["navidrome", "audiobookshelf"]); - export const widgetDefaultSizes: Partial> = { uptimeKuma: { width: 2, height: 3 }, audioStats: { width: 2, height: 2 }, - audiobookshelf: { width: 2, height: 2 }, paperlessNgx: { width: 2, height: 2 }, - navidrome: { width: 2, height: 2 }, }; diff --git a/packages/widgets/src/audio-stats/audio-stats-content.tsx b/packages/widgets/src/audio-stats/audio-stats-content.tsx index 31aa7e8ac..4c7677167 100644 --- a/packages/widgets/src/audio-stats/audio-stats-content.tsx +++ b/packages/widgets/src/audio-stats/audio-stats-content.tsx @@ -9,7 +9,7 @@ import { useScopedI18n } from "@homarr/translation/client"; import classes from "./component.module.css"; import type { AudioStatsBackend, AudioStatsDisplayOptions } from "./shared"; -import { getGridCols, getIconSize, getVisibleStats, translationScopeByBackend } from "./shared"; +import { AUDIO_STATS_TRANSLATION_SCOPE, getGridCols, getIconSize, getVisibleStats } from "./shared"; const rootClassByCompact = { true: classes.rootCompact, @@ -31,7 +31,6 @@ interface AudioStatsContentProps { stats: NavidromeDashboardData | AudiobookshelfDashboardData; options: AudioStatsDisplayOptions; width: number; - translationScope?: keyof typeof translationScopeByBackend; } export function AudioStatsContent({ @@ -39,9 +38,8 @@ export function AudioStatsContent({ stats, options, width, - translationScope = "audioStats", }: AudioStatsContentProps) { - const t = useScopedI18n(translationScopeByBackend[translationScope]); + const t = useScopedI18n(AUDIO_STATS_TRANSLATION_SCOPE); const visibleStats = getVisibleStats(backend, options, stats); const compactKey = String(options.compactMode ?? false) as keyof typeof rootClassByCompact; const gridCols = getGridCols(width, visibleStats.length, options.compactMode ?? false); diff --git a/packages/widgets/src/audio-stats/component.tsx b/packages/widgets/src/audio-stats/component.tsx index af222dc74..6e3e484b5 100644 --- a/packages/widgets/src/audio-stats/component.tsx +++ b/packages/widgets/src/audio-stats/component.tsx @@ -34,7 +34,6 @@ export default function AudioStatsWidget({ integrationIds, options, width }: Wid stats={response.data} options={displayOptions} width={width} - translationScope="audioStats" /> ); } diff --git a/packages/widgets/src/audio-stats/shared.ts b/packages/widgets/src/audio-stats/shared.ts index 3e22a813e..39fc9b0b6 100644 --- a/packages/widgets/src/audio-stats/shared.ts +++ b/packages/widgets/src/audio-stats/shared.ts @@ -166,8 +166,4 @@ export const getIconSize = (width: number, compact: boolean): number => { return match?.size ?? fallbackIconSize[String(compact) as keyof typeof fallbackIconSize]; }; -export const translationScopeByBackend = { - navidrome: "widget.navidrome", - audiobookshelf: "widget.audiobookshelf", - audioStats: "widget.audioStats", -} as const; +export const AUDIO_STATS_TRANSLATION_SCOPE = "widget.audioStats" as const; diff --git a/packages/widgets/src/audiobookshelf/component.tsx b/packages/widgets/src/audiobookshelf/component.tsx deleted file mode 100644 index 89d7a7d05..000000000 --- a/packages/widgets/src/audiobookshelf/component.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import { clientApi } from "@homarr/api/client"; - -import type { WidgetComponentProps } from "../definition"; -import { AudioStatsContent } from "../audio-stats/audio-stats-content"; -import type { AudiobookshelfDisplayOptions } from "../audio-stats/shared"; - -const defaultAudiobookshelfOptions: AudiobookshelfDisplayOptions = { - showLibraryCount: true, - showAudiobooks: true, - showPodcasts: true, - showListeningTime: true, - showActiveSessions: true, - compactMode: false, -}; - -export default function AudiobookshelfWidget({ - integrationIds, - options, - width, -}: WidgetComponentProps<"audiobookshelf">) { - const [stats] = clientApi.widget.audiobookshelf.getStats.useSuspenseQuery({ - integrationId: integrationIds[0] ?? "", - }); - - const displayOptions = { ...defaultAudiobookshelfOptions, ...options }; - - return ( - - ); -} diff --git a/packages/widgets/src/audiobookshelf/index.ts b/packages/widgets/src/audiobookshelf/index.ts deleted file mode 100644 index abb69055b..000000000 --- a/packages/widgets/src/audiobookshelf/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { IconHeadphones, IconServerOff } from "@tabler/icons-react"; - -import { createWidgetDefinition } from "../definition"; -import { optionsBuilder } from "../options"; - -export const { definition, componentLoader } = createWidgetDefinition("audiobookshelf", { - icon: IconHeadphones, - supportedIntegrations: ["audiobookshelf"], - integrationsRequired: true, - createOptions() { - return optionsBuilder.from((factory) => ({ - showLibraryCount: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showAudiobooks: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showPodcasts: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showListeningTime: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showActiveSessions: factory.switch({ - defaultValue: true, - withDescription: true, - }), - compactMode: factory.switch({ - defaultValue: false, - withDescription: true, - }), - })); - }, - errors: { - INTERNAL_SERVER_ERROR: { - icon: IconServerOff, - message: (t) => t("widget.audiobookshelf.error.internalServerError"), - }, - }, -}).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx index 8d3bc15eb..6376a3dba 100644 --- a/packages/widgets/src/index.tsx +++ b/packages/widgets/src/index.tsx @@ -9,8 +9,6 @@ import type { SettingsContextProps } from "@homarr/settings/creator"; import * as anchorNote from "./anchor-note"; import * as audioStats from "./audio-stats"; -import * as audiobookshelf from "./audiobookshelf"; -import * as navidrome from "./navidrome"; import * as app from "./app"; import * as bookmarks from "./bookmarks"; import * as calendar from "./calendar"; @@ -101,8 +99,6 @@ export const widgetImports = { speedtestTracker, uptimeKuma, audioStats, - audiobookshelf, - navidrome, umami, ups, } satisfies WidgetImportRecord; diff --git a/packages/widgets/src/navidrome/component.tsx b/packages/widgets/src/navidrome/component.tsx deleted file mode 100644 index e0bed4abe..000000000 --- a/packages/widgets/src/navidrome/component.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import { clientApi } from "@homarr/api/client"; - -import type { WidgetComponentProps } from "../definition"; -import { AudioStatsContent } from "../audio-stats/audio-stats-content"; -import type { NavidromeDisplayOptions } from "../audio-stats/shared"; - -const defaultNavidromeOptions: NavidromeDisplayOptions = { - showArtists: true, - showAlbums: true, - showSongs: true, - showNowPlaying: true, - showNowPlayingList: true, - maxNowPlayingItems: 3, -}; - -export default function NavidromeWidget({ integrationIds, options, width }: WidgetComponentProps<"navidrome">) { - const [stats] = clientApi.widget.navidrome.getStats.useSuspenseQuery({ - integrationId: integrationIds[0] ?? "", - }); - - const displayOptions = { ...defaultNavidromeOptions, ...options }; - - return ( - - ); -} diff --git a/packages/widgets/src/navidrome/index.ts b/packages/widgets/src/navidrome/index.ts deleted file mode 100644 index 7a5b22b4d..000000000 --- a/packages/widgets/src/navidrome/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { IconMusic, IconServerOff } from "@tabler/icons-react"; -import { z } from "zod/v4"; - -import { createWidgetDefinition } from "../definition"; -import { optionsBuilder } from "../options"; - -export const { definition, componentLoader } = createWidgetDefinition("navidrome", { - icon: IconMusic, - supportedIntegrations: ["navidrome"], - integrationsRequired: true, - createOptions() { - return optionsBuilder.from((factory) => ({ - showArtists: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showAlbums: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showSongs: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showNowPlaying: factory.switch({ - defaultValue: true, - withDescription: true, - }), - showNowPlayingList: factory.switch({ - defaultValue: true, - withDescription: true, - }), - maxNowPlayingItems: factory.number({ - defaultValue: 3, - validate: z.number().min(1).max(5), - step: 1, - withDescription: true, - }), - })); - }, - errors: { - INTERNAL_SERVER_ERROR: { - icon: IconServerOff, - message: (t) => t("widget.navidrome.error.internalServerError"), - }, - }, -}).withDynamicImport(() => import("./component"));