server add/edit refactor, allow jellyfin prefer instant mix

This commit is contained in:
Kendall Garner 2025-09-28 19:19:24 -07:00
parent eb0ccec0bc
commit 6df270ba34
No known key found for this signature in database
GPG key ID: 9355F387FE765C94
5 changed files with 91 additions and 30 deletions

View file

@ -237,6 +237,8 @@
"input_legacyAuthentication": "enable legacy authentication", "input_legacyAuthentication": "enable legacy authentication",
"input_name": "server name", "input_name": "server name",
"input_password": "password", "input_password": "password",
"input_preferInstantMix": "prefer instant mix",
"input_preferInstantMixDescription": "only use instant mix to get similar songs. useful if you have plugins that modify this behavior",
"input_savePassword": "save password", "input_savePassword": "save password",
"input_url": "url", "input_url": "url",
"input_username": "username", "input_username": "username",

View file

@ -627,30 +627,34 @@ export const JellyfinController: ControllerEndpoint = {
getSimilarSongs: async (args) => { getSimilarSongs: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
// Prefer getSimilarSongs, where possible. Fallback to InstantMix if (apiClientProps.server?.preferInstantMix !== true) {
// where no similar songs were found. // Prefer getSimilarSongs, where possible, and not overridden.
const res = await jfApiClient(apiClientProps).getSimilarSongs({ // InstantMix can be overridden by plugins, so this may be preferred by the user.
params: { // Otherwise, similarSongs may have a better output than InstantMix, if sufficient
itemId: query.songId, // data exists from the server.
}, const res = await jfApiClient(apiClientProps).getSimilarSongs({
query: { params: {
Fields: 'Genres, DateCreated, MediaSources, ParentId', itemId: query.songId,
Limit: query.count, },
UserId: apiClientProps.server?.userId || undefined, query: {
}, Fields: 'Genres, DateCreated, MediaSources, ParentId',
}); Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (res.status === 200 && res.body.Items.length) { if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => { const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) { if (song.Id !== query.songId) {
acc.push(jfNormalize.song(song, apiClientProps.server, '')); acc.push(jfNormalize.song(song, apiClientProps.server, ''));
}
return acc;
}, []);
if (results.length > 0) {
return results;
} }
return acc;
}, []);
if (results.length > 0) {
return results;
} }
} }

View file

@ -21,7 +21,7 @@ import { Stack } from '/@/shared/components/stack/stack';
import { TextInput } from '/@/shared/components/text-input/text-input'; import { TextInput } from '/@/shared/components/text-input/text-input';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { AuthenticationResponse } from '/@/shared/types/domain-types'; import { AuthenticationResponse, ServerListItem } from '/@/shared/types/domain-types';
import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types'; import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types';
const autodiscover = isElectron() ? window.api.autodiscover : null; const autodiscover = isElectron() ? window.api.autodiscover : null;
@ -99,7 +99,8 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
legacyAuth: false, legacyAuth: false,
name: (localSettings ? localSettings.env.SERVER_NAME : window.SERVER_NAME) ?? '', name: (localSettings ? localSettings.env.SERVER_NAME : window.SERVER_NAME) ?? '',
password: '', password: '',
savePassword: false, preferInstantMix: undefined,
savePassword: undefined,
type: type:
(localSettings (localSettings
? localSettings.env.SERVER_TYPE ? localSettings.env.SERVER_TYPE
@ -151,17 +152,28 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
}); });
} }
const serverItem = { const serverItem: ServerListItem = {
credential: data.credential, credential: data.credential,
id: nanoid(), id: nanoid(),
name: values.name, name: values.name,
ndCredential: data.ndCredential,
type: values.type as ServerType, type: values.type as ServerType,
url: values.url.replace(/\/$/, ''), url: values.url.replace(/\/$/, ''),
userId: data.userId, userId: data.userId,
username: data.username, username: data.username,
}; };
if (values.preferInstantMix !== undefined) {
serverItem.preferInstantMix = values.preferInstantMix;
}
if (values.savePassword !== undefined) {
serverItem.savePassword = values.savePassword;
}
if (data.ndCredential !== undefined) {
serverItem.ndCredential = data.ndCredential;
}
addServer(serverItem); addServer(serverItem);
setCurrentServer(serverItem); setCurrentServer(serverItem);
closeAllModals(); closeAllModals();
@ -271,6 +283,21 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
{...form.getInputProps('legacyAuth', { type: 'checkbox' })} {...form.getInputProps('legacyAuth', { type: 'checkbox' })}
/> />
)} )}
{form.values.type === ServerType.JELLYFIN && (
<Checkbox
description={t('form.addServer.input', {
context: 'preferInstantMixDescription',
postProcess: 'sentenceCase',
})}
label={t('form.addServer.input', {
context: 'preferInstantMix',
postProcess: 'titleCase',
})}
{...form.getInputProps('preferInstantMix', {
type: 'checkbox',
})}
/>
)}
<Group grow justify="flex-end"> <Group grow justify="flex-end">
{onCancel && ( {onCancel && (
<Button onClick={onCancel} variant="subtle"> <Button onClick={onCancel} variant="subtle">

View file

@ -48,7 +48,8 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
legacyAuth: false, legacyAuth: false,
name: server?.name, name: server?.name,
password: password || '', password: password || '',
savePassword: server.savePassword || false, preferInstantMix: server.preferInstantMix,
savePassword: server.savePassword,
type: server?.type, type: server?.type,
url: server?.url, url: server?.url,
username: server?.username, username: server?.username,
@ -85,17 +86,28 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
}); });
} }
const serverItem = { const serverItem: ServerListItem = {
credential: data.credential, credential: data.credential,
id: server.id,
name: values.name, name: values.name,
ndCredential: data.ndCredential,
savePassword: values.savePassword,
type: values.type, type: values.type,
url: values.url, url: values.url,
userId: data.userId, userId: data.userId,
username: data.username, username: data.username,
}; };
if (values.preferInstantMix !== undefined) {
serverItem.preferInstantMix = values.preferInstantMix;
}
if (values.savePassword !== undefined) {
serverItem.savePassword = values.savePassword;
}
if (data.ndCredential !== undefined) {
serverItem.ndCredential = data.ndCredential;
}
updateServer(server.id, serverItem); updateServer(server.id, serverItem);
toast.success({ toast.success({
message: t('form.updateServer.title', { postProcess: 'sentenceCase' }), message: t('form.updateServer.title', { postProcess: 'sentenceCase' }),
@ -188,6 +200,21 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
})} })}
/> />
)} )}
{form.values.type === ServerType.JELLYFIN && (
<Checkbox
description={t('form.addServer.input', {
context: 'preferInstantMixDescription',
postProcess: 'sentenceCase',
})}
label={t('form.addServer.input', {
context: 'preferInstantMix',
postProcess: 'titleCase',
})}
{...form.getInputProps('preferInstantMix', {
type: 'checkbox',
})}
/>
)}
<Group justify="flex-end"> <Group justify="flex-end">
<Button onClick={onCancel} variant="subtle"> <Button onClick={onCancel} variant="subtle">
{t('common.cancel', { postProcess: 'titleCase' })} {t('common.cancel', { postProcess: 'titleCase' })}

View file

@ -89,6 +89,7 @@ export type ServerListItem = {
id: string; id: string;
name: string; name: string;
ndCredential?: string; ndCredential?: string;
preferInstantMix?: boolean;
savePassword?: boolean; savePassword?: boolean;
type: ServerType; type: ServerType;
url: string; url: string;