mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
refactor(tauri-bridge): split TauriEventBridge into per-concern hooks (#682)
Each Rust<->React bridge concern moves into its own hook under hooks/tauriBridge/; TauriEventBridge just composes them: - useZipDownloadBridge download:zip:progress - usePreviewBridge audio:preview-* lifecycle - useAudioDeviceBridge audio:device-changed / -reset - useCliBridge full cli:* listener surface - useTrayIconSync tray-icon visibility - useInAppKeybindings configurable keydown chords - useMediaAndWindowBridge media keys, tray, shortcuts, close/force-quit - usePlayerSnapshotPublisher psysonic --info JSON snapshot Pure code-move — event names, payloads, effect deps and cleanup all verbatim. The MainApp call site is unchanged.
This commit is contained in:
committed by
GitHub
parent
b89b5d7c94
commit
1dd74f0fa1
@@ -0,0 +1,188 @@
|
||||
import { useEffect } from 'react';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { getSimilarSongs } from '../../api/subsonicArtists';
|
||||
import { getMusicFolders } from '../../api/subsonicLibrary';
|
||||
import { search as subsonicSearch } from '../../api/subsonicSearch';
|
||||
import { shuffleArray } from '../../utils/shuffleArray';
|
||||
import { songToTrack } from '../../utils/songToTrack';
|
||||
import { showToast } from '../../utils/toast';
|
||||
import { switchActiveServer } from '../../utils/switchActiveServer';
|
||||
import i18n from '../../i18n';
|
||||
import { usePlayerStore } from '../../store/playerStore';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { executeCliPlayerCommand } from '../../config/shortcutActions';
|
||||
|
||||
/** The full `cli:*` listener surface forwarded from the Rust single-instance
|
||||
* handler: audio-device, instant-mix, library / server resolution, search and
|
||||
* player commands. */
|
||||
export function useCliBridge(navigate: NavigateFunction) {
|
||||
// CLI: `--player audio-device set …` (forwarded on Linux via single-instance).
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | undefined;
|
||||
listen<string>('cli:audio-device-set', async e => {
|
||||
const raw = typeof e.payload === 'string' ? e.payload : '';
|
||||
const deviceName = raw.length > 0 ? raw : null;
|
||||
try {
|
||||
await invoke('audio_set_device', { deviceName });
|
||||
useAuthStore.getState().setAudioOutputDevice(deviceName);
|
||||
} catch {
|
||||
/* device open failed — do not persist (same as Settings) */
|
||||
}
|
||||
}).then(u => { unlisten = u; });
|
||||
return () => { unlisten?.(); };
|
||||
}, []);
|
||||
|
||||
// CLI: `--player mix append|new` from the currently playing track.
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | undefined;
|
||||
listen<string>('cli:instant-mix', async e => {
|
||||
const mode = e.payload === 'append' ? 'append' : 'new';
|
||||
const state = usePlayerStore.getState();
|
||||
const song = state.currentTrack;
|
||||
if (!song) {
|
||||
showToast(i18n.t('contextMenu.cliMixNeedsTrack'), 5000, 'error');
|
||||
return;
|
||||
}
|
||||
const serverId = useAuthStore.getState().activeServerId;
|
||||
try {
|
||||
const similar = await getSimilarSongs(song.id, 50);
|
||||
if (serverId) useAuthStore.getState().setAudiomuseNavidromeIssue(serverId, false);
|
||||
const base = similar.filter(s => s.id !== song.id).map(s => songToTrack(s));
|
||||
if (mode === 'append') {
|
||||
const toAdd = shuffleArray(base.map(t => ({ ...t, autoAdded: true as const })));
|
||||
if (toAdd.length > 0) usePlayerStore.getState().enqueue(toAdd);
|
||||
} else {
|
||||
// New queue from seed: collapse to [song] first, then radio tail (not append onto old queue).
|
||||
usePlayerStore.getState().reseedQueueForInstantMix(song);
|
||||
const shuffled = shuffleArray(
|
||||
base.map(t => ({ ...t, radioAdded: true as const })),
|
||||
);
|
||||
if (shuffled.length > 0) {
|
||||
const aid = song.artistId?.trim() || undefined;
|
||||
usePlayerStore.getState().enqueueRadio(shuffled, aid);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('CLI instant mix failed', err);
|
||||
if (serverId) useAuthStore.getState().setAudiomuseNavidromeIssue(serverId, true);
|
||||
showToast(i18n.t('contextMenu.instantMixFailed'), 5000, 'error');
|
||||
}
|
||||
}).then(u => { unlisten = u; });
|
||||
return () => { unlisten?.(); };
|
||||
}, []);
|
||||
|
||||
// CLI: `--player library list` (Rust polls the JSON file) / `library set`.
|
||||
useEffect(() => {
|
||||
let u1: (() => void) | undefined;
|
||||
let u2: (() => void) | undefined;
|
||||
listen('cli:library-list', async () => {
|
||||
try {
|
||||
const folders = await getMusicFolders();
|
||||
const auth = useAuthStore.getState();
|
||||
const sid = auth.activeServerId;
|
||||
const selected = sid ? (auth.musicLibraryFilterByServer[sid] ?? 'all') : 'all';
|
||||
await invoke('cli_publish_library_list', {
|
||||
payload: {
|
||||
folders: folders.map(f => ({ id: f.id, name: f.name })),
|
||||
selected,
|
||||
active_server_id: sid,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('CLI library list failed', e);
|
||||
await invoke('cli_publish_library_list', {
|
||||
payload: { folders: [], selected: 'all', active_server_id: null },
|
||||
}).catch(() => {});
|
||||
}
|
||||
}).then(u => { u1 = u; });
|
||||
listen<string>('cli:library-set', e => {
|
||||
const raw = typeof e.payload === 'string' ? e.payload : '';
|
||||
if (raw === 'all') useAuthStore.getState().setMusicLibraryFilter('all');
|
||||
else if (raw.length > 0) useAuthStore.getState().setMusicLibraryFilter(raw);
|
||||
}).then(u => { u2 = u; });
|
||||
return () => {
|
||||
u1?.();
|
||||
u2?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// CLI: servers, search, transport extras, mute, star, rating, play-by-id, reload.
|
||||
useEffect(() => {
|
||||
const unsubs: Array<() => void> = [];
|
||||
listen('cli:server-list', async () => {
|
||||
const auth = useAuthStore.getState();
|
||||
await invoke('cli_publish_server_list', {
|
||||
payload: {
|
||||
active_server_id: auth.activeServerId,
|
||||
servers: auth.servers.map(s => ({ id: s.id, name: s.name })),
|
||||
},
|
||||
});
|
||||
}).then(u => unsubs.push(u));
|
||||
listen<string>('cli:server-set', async e => {
|
||||
const raw = typeof e.payload === 'string' ? e.payload : '';
|
||||
const id = raw.trim();
|
||||
if (!id) return;
|
||||
const server = useAuthStore.getState().servers.find(s => s.id === id);
|
||||
if (!server) {
|
||||
showToast(i18n.t('contextMenu.cliServerNotFound', { defaultValue: 'Server id not found.' }), 4000, 'error');
|
||||
return;
|
||||
}
|
||||
const ok = await switchActiveServer(server);
|
||||
if (!ok) {
|
||||
showToast(i18n.t('contextMenu.cliServerSwitchFailed', { defaultValue: 'Could not switch server (ping failed).' }), 5000, 'error');
|
||||
}
|
||||
}).then(u => unsubs.push(u));
|
||||
listen<{ scope: string; query: string }>('cli:search', async e => {
|
||||
const { scope, query } = e.payload;
|
||||
const base = { scope, query, ready: false };
|
||||
try {
|
||||
const r = await subsonicSearch(query, { songCount: 50, albumCount: 30, artistCount: 30 });
|
||||
const payload =
|
||||
scope === 'track'
|
||||
? {
|
||||
...base,
|
||||
songs: r.songs.map(s => ({ id: s.id, title: s.title, artist: s.artist })),
|
||||
albums: [] as { id: string; name: string; artist: string }[],
|
||||
artists: [] as { id: string; name: string }[],
|
||||
ready: true,
|
||||
}
|
||||
: scope === 'album'
|
||||
? {
|
||||
...base,
|
||||
songs: [] as { id: string; title: string; artist: string }[],
|
||||
albums: r.albums.map(a => ({ id: a.id, name: a.name, artist: a.artist })),
|
||||
artists: [] as { id: string; name: string }[],
|
||||
ready: true,
|
||||
}
|
||||
: {
|
||||
...base,
|
||||
songs: [] as { id: string; title: string; artist: string }[],
|
||||
albums: [] as { id: string; name: string; artist: string }[],
|
||||
artists: r.artists.map(a => ({ id: a.id, name: a.name })),
|
||||
ready: true,
|
||||
};
|
||||
await invoke('cli_publish_search_results', { payload });
|
||||
} catch (err) {
|
||||
console.error('CLI search failed', err);
|
||||
await invoke('cli_publish_search_results', {
|
||||
payload: {
|
||||
...base,
|
||||
songs: [],
|
||||
albums: [],
|
||||
artists: [],
|
||||
ready: true,
|
||||
error: err instanceof Error ? err.message : 'search failed',
|
||||
},
|
||||
}).catch(() => {});
|
||||
}
|
||||
}).then(u => unsubs.push(u));
|
||||
listen<any>('cli:player-command', async e => {
|
||||
await executeCliPlayerCommand({ payload: e.payload ?? {}, navigate });
|
||||
}).then(u => unsubs.push(u));
|
||||
return () => {
|
||||
unsubs.forEach(u => u());
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user