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_name": "server name",
"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_url": "url",
"input_username": "username",

View file

@ -627,30 +627,34 @@ export const JellyfinController: ControllerEndpoint = {
getSimilarSongs: async (args) => {
const { apiClientProps, query } = args;
// Prefer getSimilarSongs, where possible. Fallback to InstantMix
// where no similar songs were found.
const res = await jfApiClient(apiClientProps).getSimilarSongs({
params: {
itemId: query.songId,
},
query: {
Fields: 'Genres, DateCreated, MediaSources, ParentId',
Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (apiClientProps.server?.preferInstantMix !== true) {
// Prefer getSimilarSongs, where possible, and not overridden.
// InstantMix can be overridden by plugins, so this may be preferred by the user.
// Otherwise, similarSongs may have a better output than InstantMix, if sufficient
// data exists from the server.
const res = await jfApiClient(apiClientProps).getSimilarSongs({
params: {
itemId: query.songId,
},
query: {
Fields: 'Genres, DateCreated, MediaSources, ParentId',
Limit: query.count,
UserId: apiClientProps.server?.userId || undefined,
},
});
if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) {
acc.push(jfNormalize.song(song, apiClientProps.server, ''));
if (res.status === 200 && res.body.Items.length) {
const results = res.body.Items.reduce<Song[]>((acc, song) => {
if (song.Id !== query.songId) {
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 { Text } from '/@/shared/components/text/text';
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';
const autodiscover = isElectron() ? window.api.autodiscover : null;
@ -99,7 +99,8 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
legacyAuth: false,
name: (localSettings ? localSettings.env.SERVER_NAME : window.SERVER_NAME) ?? '',
password: '',
savePassword: false,
preferInstantMix: undefined,
savePassword: undefined,
type:
(localSettings
? localSettings.env.SERVER_TYPE
@ -151,17 +152,28 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
});
}
const serverItem = {
const serverItem: ServerListItem = {
credential: data.credential,
id: nanoid(),
name: values.name,
ndCredential: data.ndCredential,
type: values.type as ServerType,
url: values.url.replace(/\/$/, ''),
userId: data.userId,
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);
setCurrentServer(serverItem);
closeAllModals();
@ -271,6 +283,21 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
{...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">
{onCancel && (
<Button onClick={onCancel} variant="subtle">

View file

@ -48,7 +48,8 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
legacyAuth: false,
name: server?.name,
password: password || '',
savePassword: server.savePassword || false,
preferInstantMix: server.preferInstantMix,
savePassword: server.savePassword,
type: server?.type,
url: server?.url,
username: server?.username,
@ -85,17 +86,28 @@ export const EditServerForm = ({ isUpdate, onCancel, password, server }: EditSer
});
}
const serverItem = {
const serverItem: ServerListItem = {
credential: data.credential,
id: server.id,
name: values.name,
ndCredential: data.ndCredential,
savePassword: values.savePassword,
type: values.type,
url: values.url,
userId: data.userId,
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);
toast.success({
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">
<Button onClick={onCancel} variant="subtle">
{t('common.cancel', { postProcess: 'titleCase' })}

View file

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