mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
exp(orbit): host merge, guest queue view, settings popover, i18n
Guest suggestions now auto-merge into the host's play queue at random positions inside the upcoming range, so they actually surface alongside host-picked tracks instead of piling up at the end. Per-item dedupe via addedBy:addedAt:trackId keys survives reshuffles. Guests get a read-only mirror of the session in place of their queue panel — live-card for the host's current track, suggestion list with submitter attribution, and a footer reminding them the host owns playback. Track metadata is resolved lazily and cached locally. New host-only settings popover (gear in the bar) with auto-approve and auto-shuffle toggles plus a "Shuffle now" action. Settings live in the OrbitState blob and push immediately to Navidrome; missing fields on older blobs default to "both on". 15-min shuffle now touches the real playerStore queue via a new shuffleUpcomingQueue action — previous behaviour only reshuffled the guest-facing suggestion history. A manual shuffle is available from the settings popover so the interval can be verified without waiting. Start-modal reworked into a single step: share-link is visible and copy-able from the start, auto-copied on Start if the host hasn't already hit Copy. Link carries a live name-derived slug (parser strips it, SID is authoritative). LAN/remote-server hint above the facts. Random session-name suggester pulls from three pattern pools for ~10k unique combinations. All user-visible Orbit strings moved to a dedicated orbit i18n namespace (en + de); other locales fall back to en via i18next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,31 +37,32 @@ export default function PasteClipboardHandler() {
|
||||
if (orbit) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!isLoggedIn) { showToast('Log in before joining a session', 4000, 'info'); return; }
|
||||
if (!isLoggedIn) { showToast(t('orbit.toastLoginFirst'), 4000, 'info'); return; }
|
||||
const active = useAuthStore.getState().getActiveServer();
|
||||
const activeUrl = (active?.url ?? '').replace(/\/+$/, '');
|
||||
const wantUrl = orbit.serverBase.replace(/\/+$/, '');
|
||||
if (activeUrl !== wantUrl) {
|
||||
showToast(`Switch to ${wantUrl} first, then paste again`, 5000, 'info');
|
||||
showToast(t('orbit.toastSwitchServer', { url: wantUrl }), 5000, 'info');
|
||||
return;
|
||||
}
|
||||
if (busy.current) return;
|
||||
busy.current = true;
|
||||
joinOrbitSession(orbit.sid)
|
||||
.then(() => showToast('Joined session', 2500, 'info'))
|
||||
.then(() => showToast(t('orbit.toastJoined'), 2500, 'info'))
|
||||
.catch(err => {
|
||||
if (err instanceof OrbitJoinError) {
|
||||
const msg: Record<string, string> = {
|
||||
'not-found': 'Session not found',
|
||||
'ended': 'Session has ended',
|
||||
'full': 'Session is full',
|
||||
'kicked': 'You can\'t rejoin this session',
|
||||
'no-user': 'No active server',
|
||||
'server-error': 'Couldn\'t join',
|
||||
const key: Record<string, string> = {
|
||||
'not-found': 'orbit.joinErrNotFound',
|
||||
'ended': 'orbit.joinErrEnded',
|
||||
'full': 'orbit.joinErrFull',
|
||||
'kicked': 'orbit.joinErrKicked',
|
||||
'no-user': 'orbit.joinErrNoUser',
|
||||
'server-error': 'orbit.joinErrServerError',
|
||||
};
|
||||
showToast(msg[err.reason] ?? err.message, 4000, 'error');
|
||||
const i18nKey = key[err.reason];
|
||||
showToast(i18nKey ? t(i18nKey) : err.message, 4000, 'error');
|
||||
} else {
|
||||
showToast('Couldn\'t join session', 4000, 'error');
|
||||
showToast(t('orbit.toastJoinFail'), 4000, 'error');
|
||||
}
|
||||
})
|
||||
.finally(() => { busy.current = false; });
|
||||
|
||||
Reference in New Issue
Block a user