feat(tag-editor): add batch metadata editor with artwork support (Duplicate) (#2252)

* feat(tag-editor): add batch metadata editor with artwork support

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
Chris Scott
2026-07-17 22:17:11 -04:00
committed by GitHub
parent cf4556743d
commit 75bdf60912
47 changed files with 4055 additions and 737 deletions
+12
View File
@@ -786,6 +786,18 @@ export const controller: GeneralController = {
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
refreshItems(args) {
const server = getServerById(args.apiClientProps.serverId);
if (!server) {
throw new Error(`${i18n.t('error.apiRouteError')}: refreshItems`);
}
return apiController(
'refreshItems',
server.type,
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
},
removeFromPlaylist(args) {
const server = getServerById(args.apiClientProps.serverId);
+12
View File
@@ -301,6 +301,18 @@ export const contract = c.router({
400: jfType._response.error,
},
},
refreshItem: {
body: z.null(),
method: 'POST',
path: 'Items/:id/Refresh',
query: z.object({
MetadataRefreshMode: z.string().optional(),
}),
responses: {
204: z.null(),
400: jfType._response.error,
},
},
removeFavorite: {
body: jfType._parameters.favorite,
method: 'DELETE',
@@ -1556,6 +1556,21 @@ export const JellyfinController: InternalControllerEndpoint = {
throw new Error('Failed to move item in playlist');
}
},
refreshItems: async (args) => {
const { apiClientProps, query } = args;
await Promise.all(
query.ids.map((id) =>
jfApiClient(apiClientProps).refreshItem({
body: null,
params: { id },
query: { MetadataRefreshMode: 'FullRefresh' },
}),
),
);
return null;
},
removeFromPlaylist: async (args) => {
const { apiClientProps, query } = args;
@@ -1027,6 +1027,7 @@ export const NavidromeController: InternalControllerEndpoint = {
throw new Error('Failed to move item in playlist');
}
},
refreshItems: SubsonicController.refreshItems,
removeFromPlaylist: async (args) => {
const { apiClientProps, query } = args;
+16
View File
@@ -186,6 +186,14 @@ export const contract = c.router({
200: ssType._response.randomSongList,
},
},
getScanStatus: {
method: 'GET',
path: 'getScanStatus.view',
query: ssType._parameters.getScanStatus,
responses: {
200: ssType._response.getScanStatus,
},
},
getServerInfo: {
method: 'GET',
path: 'getOpenSubsonicExtensions.view',
@@ -345,6 +353,14 @@ export const contract = c.router({
200: ssType._response.setRating,
},
},
startScan: {
method: 'GET',
path: 'startScan.view',
query: ssType._parameters.startScan,
responses: {
200: ssType._response.startScan,
},
},
updateInternetRadioStation: {
method: 'GET',
path: 'updateInternetRadioStation.view',
@@ -2082,6 +2082,17 @@ export const SubsonicController: InternalControllerEndpoint = {
return res.body;
},
refreshItems: async (args) => {
const { apiClientProps } = args;
const res = await ssApiClient(apiClientProps).startScan({ query: {} });
if (res.status !== 200) {
throw new Error('Failed to start scan');
}
return null;
},
removeFromPlaylist: async ({ apiClientProps, query }) => {
const res = await ssApiClient(apiClientProps).updatePlaylist({
query: {