feat(discord): show track title in Discord member list (configurable name template) (#885)

* feat(discord): override activity name in member list with track title (configurable template)

The Discord member list and the collapsed Rich Presence card display the
activity's `name` field next to the music icon. Without an override
Discord falls back to the registered application name ("Psysonic"), so
the line reads "Psysonic" while every track plays instead of the actual
track title that comparable players show.

Add a fourth user-configurable template `discordTemplateName` (Settings ->
Integrations -> Discord Rich Presence). The Rust side passes it through
`Activity::new().name(...)` when the rendered template is non-empty; an
empty template falls back to Discord's default application name. Default
template is "{title}".

Tauri boundary: additive optional `nameTemplate` field on the existing
`discord_update_presence` command. Existing call sites that omit it keep
working -- the Rust handler applies the same "{title}" fallback.

* i18n(settings): discord name template label in all locales

Adds the discordTemplateName label across en, de, fr, es, nl, nb, ro,
zh, ru -- shown next to the new "User list line (name)" template input
under Discord Rich Presence.

* docs(release): CHANGELOG and credits for discord name template (PR #885)
This commit is contained in:
Frank Stellmacher
2026-05-28 20:32:41 +02:00
committed by GitHub
parent 403979b35d
commit 455aec4def
18 changed files with 70 additions and 6 deletions
+9
View File
@@ -258,6 +258,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Discord Rich Presence — track title in the member list
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#885](https://github.com/Psychotoxical/psysonic/pull/885)**
* The Discord member list and the collapsed presence card now show the playing track next to the music icon instead of the static "Psysonic" label — matches how comparable players appear in the user list.
* New **User list line (name)** template under **Settings → Integrations → Discord Rich Presence**, default `{title}`. Same placeholders as the other templates: `{title}`, `{artist}`, `{album}`. Leaving it empty restores the previous "Psysonic" display.
## Fixed ## Fixed
### Analytics — Opus waveform and loudness analysis ### Analytics — Opus waveform and loudness analysis
@@ -262,12 +262,13 @@ fn apply_template(template: &str, title: &str, artist: &str, album: Option<&str>
/// Bundled output of [`compute_discord_text_fields`]. /// Bundled output of [`compute_discord_text_fields`].
pub(crate) struct DiscordTextFields { pub(crate) struct DiscordTextFields {
pub name: String,
pub details: String, pub details: String,
pub state: String, pub state: String,
pub large_text: String, pub large_text: String,
} }
/// Pure helper: resolve all three configurable Discord text fields, applying /// Pure helper: resolve all four configurable Discord text fields, applying
/// the supplied templates (or falling back to documented defaults). /// the supplied templates (or falling back to documented defaults).
pub(crate) fn compute_discord_text_fields( pub(crate) fn compute_discord_text_fields(
title: &str, title: &str,
@@ -276,7 +277,9 @@ pub(crate) fn compute_discord_text_fields(
details_template: Option<&str>, details_template: Option<&str>,
state_template: Option<&str>, state_template: Option<&str>,
large_text_template: Option<&str>, large_text_template: Option<&str>,
name_template: Option<&str>,
) -> DiscordTextFields { ) -> DiscordTextFields {
let name = apply_template(name_template.unwrap_or("{title}"), title, artist, album);
let details = apply_template( let details = apply_template(
details_template.unwrap_or("{artist} - {title}"), details_template.unwrap_or("{artist} - {title}"),
title, title,
@@ -291,6 +294,7 @@ pub(crate) fn compute_discord_text_fields(
album, album,
); );
DiscordTextFields { DiscordTextFields {
name,
details, details,
state, state,
large_text, large_text,
@@ -318,6 +322,10 @@ pub(crate) fn compute_discord_start_timestamp(elapsed_secs: f64, now_unix_secs:
/// Supported placeholders: {title}, {artist}, {album} /// Supported placeholders: {title}, {artist}, {album}
/// - `large_text_template`: template string for the large image tooltip. Default: "{album}". /// - `large_text_template`: template string for the large image tooltip. Default: "{album}".
/// Supported placeholders: {title}, {artist}, {album} /// Supported placeholders: {title}, {artist}, {album}
/// - `name_template`: template string overriding Discord's default application name in the
/// user list (e.g. "🎵 Bohemian Rhapsody" instead of "🎵 Psysonic"). Default: "{title}".
/// Empty string falls back to the registered Discord application name.
/// Supported placeholders: {title}, {artist}, {album}
#[tauri::command] #[tauri::command]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn discord_update_presence( pub async fn discord_update_presence(
@@ -332,6 +340,7 @@ pub async fn discord_update_presence(
details_template: Option<String>, details_template: Option<String>,
state_template: Option<String>, state_template: Option<String>,
large_text_template: Option<String>, large_text_template: Option<String>,
name_template: Option<String>,
) -> Result<(), String> { ) -> Result<(), String> {
// Resolve artwork on a dedicated blocking thread — reqwest::blocking must not // Resolve artwork on a dedicated blocking thread — reqwest::blocking must not
// run on the Tokio async executor directly. // run on the Tokio async executor directly.
@@ -377,6 +386,7 @@ pub async fn discord_update_presence(
details_template.as_deref(), details_template.as_deref(),
state_template.as_deref(), state_template.as_deref(),
large_text_template.as_deref(), large_text_template.as_deref(),
name_template.as_deref(),
); );
let assets = if let Some(ref url) = artwork_url { let assets = if let Some(ref url) = artwork_url {
@@ -402,8 +412,11 @@ pub async fn discord_update_presence(
} }
// Only reach here when playing // Only reach here when playing
let activity = Activity::new() let mut activity = Activity::new().activity_type(ActivityType::Listening);
.activity_type(ActivityType::Listening) if !texts.name.is_empty() {
activity = activity.name(texts.name.as_str());
}
let activity = activity
.details(&texts.details) .details(&texts.details)
.state(&texts.state) .state(&texts.state)
.assets(assets) .assets(assets)
@@ -545,7 +558,9 @@ mod tests {
#[test] #[test]
fn text_fields_use_documented_defaults_when_templates_are_none() { fn text_fields_use_documented_defaults_when_templates_are_none() {
let f = compute_discord_text_fields("Song", "Artist", Some("Album"), None, None, None); let f =
compute_discord_text_fields("Song", "Artist", Some("Album"), None, None, None, None);
assert_eq!(f.name, "Song");
assert_eq!(f.details, "Artist - Song"); assert_eq!(f.details, "Artist - Song");
assert_eq!(f.state, "Album"); assert_eq!(f.state, "Album");
assert_eq!(f.large_text, "Album"); assert_eq!(f.large_text, "Album");
@@ -560,7 +575,9 @@ mod tests {
Some("{title} | {album}"), Some("{title} | {album}"),
Some("by {artist}"), Some("by {artist}"),
Some("{album} ({artist})"), Some("{album} ({artist})"),
Some("{title} ({artist})"),
); );
assert_eq!(f.name, "Song (Artist)");
assert_eq!(f.details, "Song | Album"); assert_eq!(f.details, "Song | Album");
assert_eq!(f.state, "by Artist"); assert_eq!(f.state, "by Artist");
assert_eq!(f.large_text, "Album (Artist)"); assert_eq!(f.large_text, "Album (Artist)");
@@ -568,8 +585,9 @@ mod tests {
#[test] #[test]
fn text_fields_substitute_empty_for_missing_album() { fn text_fields_substitute_empty_for_missing_album() {
let f = compute_discord_text_fields("Song", "Artist", None, None, None, None); let f = compute_discord_text_fields("Song", "Artist", None, None, None, None, None);
// {album} placeholder → empty, but the surrounding template stays. // {album} placeholder → empty, but the surrounding template stays.
assert_eq!(f.name, "Song");
assert_eq!(f.details, "Artist - Song"); assert_eq!(f.details, "Artist - Song");
assert_eq!(f.state, ""); assert_eq!(f.state, "");
assert_eq!(f.large_text, ""); assert_eq!(f.large_text, "");
@@ -584,7 +602,9 @@ mod tests {
Some("{artist} {title}"), Some("{artist} {title}"),
None, None,
None, None,
None,
); );
assert_eq!(f.name, "Bohemian Rhapsody");
assert_eq!(f.details, "Queen Bohemian Rhapsody"); assert_eq!(f.details, "Queen Bohemian Rhapsody");
} }
@@ -202,6 +202,16 @@ export function IntegrationsTab() {
<div style={{ paddingTop: 8 }}> <div style={{ paddingTop: 8 }}>
<div style={{ fontWeight: 500, fontSize: 13, marginBottom: 8 }}>{t('settings.discordTemplates')}</div> <div style={{ fontWeight: 500, fontSize: 13, marginBottom: 8 }}>{t('settings.discordTemplates')}</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 12 }}>{t('settings.discordTemplatesDesc')}</div> <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 12 }}>{t('settings.discordTemplatesDesc')}</div>
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateName')}</label>
<input
className="input"
type="text"
value={auth.discordTemplateName}
onChange={e => auth.setDiscordTemplateName(e.target.value)}
placeholder="{title}"
/>
</div>
<div className="form-group" style={{ marginBottom: '0.75rem' }}> <div className="form-group" style={{ marginBottom: '0.75rem' }}>
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateDetails')}</label> <label style={{ fontSize: 12 }}>{t('settings.discordTemplateDetails')}</label>
<input <input
+1
View File
@@ -337,6 +337,7 @@ const CONTRIBUTOR_ENTRIES = [
'Local library index (preview): SQLite per-server track store, background initial and delta sync, live and Advanced Search against the local index, integrity verify and auto-reconcile on count drop (PR #846)', 'Local library index (preview): SQLite per-server track store, background initial and delta sync, live and Advanced Search against the local index, integrity verify and auto-reconcile on count drop (PR #846)',
'Server index-key rebuild: safe dual-DB migration flow, per-server analysis strategy controls, and playback/index scope hardening (PR #864)', 'Server index-key rebuild: safe dual-DB migration flow, per-server analysis strategy controls, and playback/index scope hardening (PR #864)',
'Settings: opt-in Linux input-focus repaint — workaround for the WebKitGTK 2.50.x text-field freeze (PR #884)', 'Settings: opt-in Linux input-focus repaint — workaround for the WebKitGTK 2.50.x text-field freeze (PR #884)',
'Discord Rich Presence: configurable activity-name template — member list shows the playing track instead of "Psysonic" (PR #885)',
], ],
}, },
] as const; ] as const;
+1
View File
@@ -225,6 +225,7 @@ export const settings = {
discordTemplateDetails: 'Primäre Zeile (details)', discordTemplateDetails: 'Primäre Zeile (details)',
discordTemplateState: 'Sekundäre Zeile (state)', discordTemplateState: 'Sekundäre Zeile (state)',
discordTemplateLargeText: 'Album-Tooltip (largeText)', discordTemplateLargeText: 'Album-Tooltip (largeText)',
discordTemplateName: 'Userliste-Zeile (name)',
nowPlayingEnabled: 'Im Livefenster anzeigen', nowPlayingEnabled: 'Im Livefenster anzeigen',
nowPlayingEnabledDesc: 'Überträgt den aktuell gespielten Titel an die Livehörer-Ansicht des Servers. Deaktivieren, um keine Wiedergabedaten zu senden.', nowPlayingEnabledDesc: 'Überträgt den aktuell gespielten Titel an die Livehörer-Ansicht des Servers. Deaktivieren, um keine Wiedergabedaten zu senden.',
enableBandsintown: 'Bandsintown-Tourdaten', enableBandsintown: 'Bandsintown-Tourdaten',
+1
View File
@@ -248,6 +248,7 @@ export const settings = {
discordTemplateDetails: 'Primary line (details)', discordTemplateDetails: 'Primary line (details)',
discordTemplateState: 'Secondary line (state)', discordTemplateState: 'Secondary line (state)',
discordTemplateLargeText: 'Album tooltip (largeText)', discordTemplateLargeText: 'Album tooltip (largeText)',
discordTemplateName: 'User list line (name)',
nowPlayingEnabled: 'Show in Now Playing', nowPlayingEnabled: 'Show in Now Playing',
nowPlayingEnabledDesc: 'Broadcast your currently playing track to the server\'s live listener view. Disable to stop sending playback data.', nowPlayingEnabledDesc: 'Broadcast your currently playing track to the server\'s live listener view. Disable to stop sending playback data.',
enableBandsintown: 'Bandsintown tour dates', enableBandsintown: 'Bandsintown tour dates',
+1
View File
@@ -224,6 +224,7 @@ export const settings = {
discordTemplateDetails: 'Línea principal (details)', discordTemplateDetails: 'Línea principal (details)',
discordTemplateState: 'Línea secundaria (state)', discordTemplateState: 'Línea secundaria (state)',
discordTemplateLargeText: 'Tooltip del álbum (largeText)', discordTemplateLargeText: 'Tooltip del álbum (largeText)',
discordTemplateName: 'Línea de la lista (name)',
nowPlayingEnabled: 'Mostrar en Reproduciendo Ahora', nowPlayingEnabled: 'Mostrar en Reproduciendo Ahora',
nowPlayingEnabledDesc: 'Transmite tu pista actual a la vista de oyentes en vivo del servidor. Desactiva para dejar de enviar datos de reproducción.', nowPlayingEnabledDesc: 'Transmite tu pista actual a la vista de oyentes en vivo del servidor. Desactiva para dejar de enviar datos de reproducción.',
enableBandsintown: 'Fechas de gira de Bandsintown', enableBandsintown: 'Fechas de gira de Bandsintown',
+1
View File
@@ -222,6 +222,7 @@ export const settings = {
discordTemplateDetails: 'Ligne principale (details)', discordTemplateDetails: 'Ligne principale (details)',
discordTemplateState: 'Ligne secondaire (state)', discordTemplateState: 'Ligne secondaire (state)',
discordTemplateLargeText: 'Info-bulle album (largeText)', discordTemplateLargeText: 'Info-bulle album (largeText)',
discordTemplateName: 'Ligne de la liste (name)',
nowPlayingEnabled: 'Afficher dans la fenêtre live', nowPlayingEnabled: 'Afficher dans la fenêtre live',
nowPlayingEnabledDesc: 'Diffuse le titre en cours de lecture vers la vue des auditeurs en direct du serveur. Désactiver pour ne pas envoyer de données de lecture.', nowPlayingEnabledDesc: 'Diffuse le titre en cours de lecture vers la vue des auditeurs en direct du serveur. Désactiver pour ne pas envoyer de données de lecture.',
enableBandsintown: 'Dates de tournée Bandsintown', enableBandsintown: 'Dates de tournée Bandsintown',
+1
View File
@@ -221,6 +221,7 @@ export const settings = {
discordTemplateDetails: 'Primær linje (details)', discordTemplateDetails: 'Primær linje (details)',
discordTemplateState: 'Sekundær linje (state)', discordTemplateState: 'Sekundær linje (state)',
discordTemplateLargeText: 'Album-verktøytips (largeText)', discordTemplateLargeText: 'Album-verktøytips (largeText)',
discordTemplateName: 'Brukerliste-linje (name)',
nowPlayingEnabled: 'Vis i "Nå spiller"', nowPlayingEnabled: 'Vis i "Nå spiller"',
nowPlayingEnabledDesc: 'Send sporet som spilles av til tjenerens live-lyttervisning. Deaktiver for å stoppe sending av avspillingsdata.', nowPlayingEnabledDesc: 'Send sporet som spilles av til tjenerens live-lyttervisning. Deaktiver for å stoppe sending av avspillingsdata.',
enableBandsintown: 'Bandsintown-turnédatoer', enableBandsintown: 'Bandsintown-turnédatoer',
+1
View File
@@ -222,6 +222,7 @@ export const settings = {
discordTemplateDetails: 'Primaire regel (details)', discordTemplateDetails: 'Primaire regel (details)',
discordTemplateState: 'Secundaire regel (state)', discordTemplateState: 'Secundaire regel (state)',
discordTemplateLargeText: 'Album-tooltip (largeText)', discordTemplateLargeText: 'Album-tooltip (largeText)',
discordTemplateName: 'Gebruikerslijst-regel (name)',
nowPlayingEnabled: 'Weergeven in live-venster', nowPlayingEnabled: 'Weergeven in live-venster',
nowPlayingEnabledDesc: 'Stuurt het huidige nummer naar de live-luisteraarsweergave van de server. Uitschakelen om geen afspeelgegevens te verzenden.', nowPlayingEnabledDesc: 'Stuurt het huidige nummer naar de live-luisteraarsweergave van de server. Uitschakelen om geen afspeelgegevens te verzenden.',
enableBandsintown: 'Bandsintown-tourdata', enableBandsintown: 'Bandsintown-tourdata',
+1
View File
@@ -227,6 +227,7 @@ export const settings = {
discordTemplateDetails: 'Linia principală (detalii)', discordTemplateDetails: 'Linia principală (detalii)',
discordTemplateState: 'Linia secundară (status)', discordTemplateState: 'Linia secundară (status)',
discordTemplateLargeText: 'Tooltip album (largeText)', discordTemplateLargeText: 'Tooltip album (largeText)',
discordTemplateName: 'Linia din listă (name)',
nowPlayingEnabled: 'Se afișează în Now Playing', nowPlayingEnabled: 'Se afișează în Now Playing',
nowPlayingEnabledDesc: 'Difuzează piesa redată curent către vizualizatorul de ascultare live a serverului. Dezactivează pentru a opri trimiterea datelor de redare.', nowPlayingEnabledDesc: 'Difuzează piesa redată curent către vizualizatorul de ascultare live a serverului. Dezactivează pentru a opri trimiterea datelor de redare.',
enableBandsintown: 'Date de turneu Bandsintown', enableBandsintown: 'Date de turneu Bandsintown',
+1
View File
@@ -252,6 +252,7 @@ export const settings = {
discordTemplateDetails: 'Основная строка (details)', discordTemplateDetails: 'Основная строка (details)',
discordTemplateState: 'Вторичная строка (state)', discordTemplateState: 'Вторичная строка (state)',
discordTemplateLargeText: 'Подсказка альбома (largeText)', discordTemplateLargeText: 'Подсказка альбома (largeText)',
discordTemplateName: 'Строка в списке пользователей (name)',
nowPlayingEnabled: 'Показывать в «Сейчас играет»', nowPlayingEnabled: 'Показывать в «Сейчас играет»',
nowPlayingEnabledDesc: nowPlayingEnabledDesc:
'Отправлять на сервер, что вы сейчас слушаете. Отключите, чтобы не делиться этим.', 'Отправлять на сервер, что вы сейчас слушаете. Отключите, чтобы не делиться этим.',
+1
View File
@@ -221,6 +221,7 @@ export const settings = {
discordTemplateDetails: '主行 (details)', discordTemplateDetails: '主行 (details)',
discordTemplateState: '副行 (state)', discordTemplateState: '副行 (state)',
discordTemplateLargeText: '专辑提示 (largeText)', discordTemplateLargeText: '专辑提示 (largeText)',
discordTemplateName: '用户列表行 (name)',
nowPlayingEnabled: '在实时窗口中显示', nowPlayingEnabled: '在实时窗口中显示',
nowPlayingEnabledDesc: '将当前播放的曲目广播到服务器的实时听众视图。禁用以停止发送播放数据。', nowPlayingEnabledDesc: '将当前播放的曲目广播到服务器的实时听众视图。禁用以停止发送播放数据。',
enableBandsintown: 'Bandsintown 巡演日期', enableBandsintown: 'Bandsintown 巡演日期',
@@ -18,6 +18,7 @@ export function setupDiscordPresence(): () => void {
let discordPrevTemplateDetails: string | null = null; let discordPrevTemplateDetails: string | null = null;
let discordPrevTemplateState: string | null = null; let discordPrevTemplateState: string | null = null;
let discordPrevTemplateLargeText: string | null = null; let discordPrevTemplateLargeText: string | null = null;
let discordPrevTemplateName: string | null = null;
let discordPrevCoverSource: string | null = null; let discordPrevCoverSource: string | null = null;
const discordServerCoverCache = new Map<string, string | null>(); const discordServerCoverCache = new Map<string, string | null>();
@@ -30,6 +31,7 @@ export function setupDiscordPresence(): () => void {
discordTemplateDetails, discordTemplateDetails,
discordTemplateState, discordTemplateState,
discordTemplateLargeText, discordTemplateLargeText,
discordTemplateName,
} = useAuthStore.getState(); } = useAuthStore.getState();
if (!discordRichPresence || !currentTrack) { if (!discordRichPresence || !currentTrack) {
@@ -41,6 +43,7 @@ export function setupDiscordPresence(): () => void {
discordPrevTemplateDetails = null; discordPrevTemplateDetails = null;
discordPrevTemplateState = null; discordPrevTemplateState = null;
discordPrevTemplateLargeText = null; discordPrevTemplateLargeText = null;
discordPrevTemplateName = null;
invoke('discord_clear_presence').catch(() => {}); invoke('discord_clear_presence').catch(() => {});
} }
return; return;
@@ -52,7 +55,8 @@ export function setupDiscordPresence(): () => void {
const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails; const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails;
const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState; const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState;
const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText; const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText;
if (!trackChanged && !playingChanged && !coverSourceChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return; const nameTemplateChanged = discordTemplateName !== discordPrevTemplateName;
if (!trackChanged && !playingChanged && !coverSourceChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged && !nameTemplateChanged) return;
discordPrevTrackId = currentTrack.id; discordPrevTrackId = currentTrack.id;
discordPrevIsPlaying = isPlaying; discordPrevIsPlaying = isPlaying;
@@ -61,6 +65,7 @@ export function setupDiscordPresence(): () => void {
discordPrevTemplateDetails = discordTemplateDetails; discordPrevTemplateDetails = discordTemplateDetails;
discordPrevTemplateState = discordTemplateState; discordPrevTemplateState = discordTemplateState;
discordPrevTemplateLargeText = discordTemplateLargeText; discordPrevTemplateLargeText = discordTemplateLargeText;
discordPrevTemplateName = discordTemplateName;
const sendPresence = (coverArtUrl: string | null) => { const sendPresence = (coverArtUrl: string | null) => {
invoke('discord_update_presence', { invoke('discord_update_presence', {
@@ -74,6 +79,7 @@ export function setupDiscordPresence(): () => void {
detailsTemplate: discordTemplateDetails, detailsTemplate: discordTemplateDetails,
stateTemplate: discordTemplateState, stateTemplate: discordTemplateState,
largeTextTemplate: discordTemplateLargeText, largeTextTemplate: discordTemplateLargeText,
nameTemplate: discordTemplateName,
}).catch(() => {}); }).catch(() => {});
}; };
+2
View File
@@ -12,6 +12,7 @@ export function createDiscordSettingsActions(set: SetState): Pick<
| 'setDiscordTemplateDetails' | 'setDiscordTemplateDetails'
| 'setDiscordTemplateState' | 'setDiscordTemplateState'
| 'setDiscordTemplateLargeText' | 'setDiscordTemplateLargeText'
| 'setDiscordTemplateName'
> { > {
return { return {
setDiscordRichPresence: (v) => set({ discordRichPresence: v }), setDiscordRichPresence: (v) => set({ discordRichPresence: v }),
@@ -20,5 +21,6 @@ export function createDiscordSettingsActions(set: SetState): Pick<
setDiscordTemplateDetails: (v) => set({ discordTemplateDetails: v }), setDiscordTemplateDetails: (v) => set({ discordTemplateDetails: v }),
setDiscordTemplateState: (v) => set({ discordTemplateState: v }), setDiscordTemplateState: (v) => set({ discordTemplateState: v }),
setDiscordTemplateLargeText: (v) => set({ discordTemplateLargeText: v }), setDiscordTemplateLargeText: (v) => set({ discordTemplateLargeText: v }),
setDiscordTemplateName: (v) => set({ discordTemplateName: v }),
}; };
} }
+1
View File
@@ -98,6 +98,7 @@ describe('trivial pass-through setters', () => {
['setDiscordTemplateDetails', 'discordTemplateDetails', '{artist} — {title}'], ['setDiscordTemplateDetails', 'discordTemplateDetails', '{artist} — {title}'],
['setDiscordTemplateState', 'discordTemplateState', '{album}'], ['setDiscordTemplateState', 'discordTemplateState', '{album}'],
['setDiscordTemplateLargeText', 'discordTemplateLargeText', 'Hi'], ['setDiscordTemplateLargeText', 'discordTemplateLargeText', 'Hi'],
['setDiscordTemplateName', 'discordTemplateName', '{title} — {artist}'],
])('%s stores a string value', (setter, key, value) => { ])('%s stores a string value', (setter, key, value) => {
(useAuthStore.getState() as unknown as Record<string, (v: unknown) => void>)[setter](value); (useAuthStore.getState() as unknown as Record<string, (v: unknown) => void>)[setter](value);
expect((useAuthStore.getState() as unknown as Record<string, unknown>)[key]).toBe(value); expect((useAuthStore.getState() as unknown as Record<string, unknown>)[key]).toBe(value);
+1
View File
@@ -75,6 +75,7 @@ export const useAuthStore = create<AuthState>()(
discordTemplateDetails: '{artist}', discordTemplateDetails: '{artist}',
discordTemplateState: '{title}', discordTemplateState: '{title}',
discordTemplateLargeText: '{album}', discordTemplateLargeText: '{album}',
discordTemplateName: '{title}',
useCustomTitlebar: false, useCustomTitlebar: false,
preloadMiniPlayer: false, preloadMiniPlayer: false,
linuxWebkitKineticScroll: true, linuxWebkitKineticScroll: true,
+5
View File
@@ -128,6 +128,10 @@ export interface AuthState {
discordTemplateDetails: string; discordTemplateDetails: string;
discordTemplateState: string; discordTemplateState: string;
discordTemplateLargeText: string; discordTemplateLargeText: string;
/** Template for Discord activity name (overrides the registered application
* name in the user list / collapsed presence). Default "{title}".
* Empty string falls back to "Psysonic". */
discordTemplateName: string;
useCustomTitlebar: boolean; useCustomTitlebar: boolean;
/** Pre-build the mini-player webview at app start on Linux/macOS so content is available instantly /** Pre-build the mini-player webview at app start on Linux/macOS so content is available instantly
* on first open. Ignored on Windows that platform always pre-creates as a hang workaround. */ * on first open. Ignored on Windows that platform always pre-creates as a hang workaround. */
@@ -311,6 +315,7 @@ export interface AuthState {
setDiscordTemplateDetails: (v: string) => void; setDiscordTemplateDetails: (v: string) => void;
setDiscordTemplateState: (v: string) => void; setDiscordTemplateState: (v: string) => void;
setDiscordTemplateLargeText: (v: string) => void; setDiscordTemplateLargeText: (v: string) => void;
setDiscordTemplateName: (v: string) => void;
setUseCustomTitlebar: (v: boolean) => void; setUseCustomTitlebar: (v: boolean) => void;
setPreloadMiniPlayer: (v: boolean) => void; setPreloadMiniPlayer: (v: boolean) => void;
setLinuxWebkitKineticScroll: (v: boolean) => void; setLinuxWebkitKineticScroll: (v: boolean) => void;