refactor: remove deprecated navidrome/audiobookshelf widgets

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).
This commit is contained in:
Thomas Camlong committed 2026-06-09 20:58:54 +02:00
1 parent 9da8ea1dec
commit 69ed608241
13 files changed
+4 -220

No files matched your search

@@ -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);
@@ -34,7 +34,6 @@ export default function AudioStatsWidget({ integrationIds, options, width }: Wid
stats={response.data}
options={displayOptions}
width={width}
translationScope="audioStats"
/>
);
}
+1 -5
View File
@@ -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;
@@ -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 (
<AudioStatsContent
backend="audiobookshelf"
stats={stats}
options={displayOptions}
width={width}
translationScope="audiobookshelf"
/>
);
}
@@ -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"));
-4
View File
@@ -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;
@@ -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 (
<AudioStatsContent
backend="navidrome"
stats={stats}
options={displayOptions}
width={width}
translationScope="navidrome"
/>
);
}
-47
View File
@@ -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"));