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:
Psychotoxical
2026-04-23 02:49:42 +02:00
parent 7fe492d233
commit 2b97a7b01e
19 changed files with 1647 additions and 213 deletions
+82
View File
@@ -1438,4 +1438,86 @@ export const enTranslation = {
liveSearch: 'Live',
randomAlbumsLabel: 'Random Albums',
},
orbit: {
triggerLabel: 'Psy Orbit',
triggerTooltip: 'Start a shared listening session',
closeAria: 'Close',
heroTitlePrefix: 'Listen together with',
heroTitleBrand: 'Psy Orbit',
heroSub: 'Start a session — other users on {{server}} will listen along and can suggest tracks.',
fallbackServer: 'this server',
tipLan: "You're currently connected via a home-network address. Guests outside your network can't join — switch to a public server address in settings before you start.",
tipRemote: 'For guests outside your home network to join, your server address has to be publicly reachable. If your server is internal only, switch before you start.',
factSameServer: 'Only users signed in to the same server can join.',
factHost: "You're the host — you control playback, your guests suggest tracks.",
factShuffle: 'The queue is reshuffled automatically every 15 minutes.',
labelName: 'Session name',
namePlaceholder: 'Velvet Orbit',
reshuffleTooltip: 'New suggestion',
reshuffleAria: 'Suggest a new name',
helperName: 'How the session shows up to your guests — keep it or type your own.',
labelMax: 'Max guests',
helperMax: "You don't count — this only caps incoming guests.",
labelLink: 'Invite link',
tooltipCopied: 'Copied',
tooltipCopy: 'Copy',
ariaCopyLink: 'Copy invite link',
helperLink: 'Send this link to your guests. They can paste it anywhere in Psysonic with Ctrl+V.',
btnCancel: 'Cancel',
btnStarting: 'Starting…',
btnStart: 'Start Orbit',
btnCopyAndStart: 'Copy link & start',
errNameRequired: 'Please give your session a name.',
errStartFailed: "Couldn't start.",
hostLabel: 'Host: {{name}}',
participantsTooltip: 'Participants',
settingsTooltip: 'Session settings',
shuffleLabel: 'Queue reshuffles in',
catchUpLabel: 'catch up',
catchUpTooltip: "Jump to the host's current position",
endTooltip: 'End session',
leaveTooltip: 'Leave session',
participantsInviteLabel: 'Invite link',
participantsCountLabel: '{{count}} in session',
participantsHost: 'host',
participantsEmpty: 'No guests yet',
participantsKickTooltip: 'Remove from session',
participantsKickAria: 'Remove {{user}}',
settingsTitle: 'Session settings',
settingAutoApprove: 'Auto-approve suggestions',
settingAutoApproveHint: "Guest suggestions land in your queue instantly. Off: they stay in the session list for you to review later.",
settingAutoShuffle: 'Reshuffle queue every 15 minutes',
settingAutoShuffleHint: "Automatic FisherYates shuffle. Off: the order only changes when you rearrange it.",
settingShuffleNow: 'Shuffle now',
toastSuggested: '{{user}} suggested "{{title}}"',
toastSuggestedMany: '{{count}} new suggestions in the queue',
toastShuffled: 'Queue shuffled',
toastJoined: 'Joined session',
toastLoginFirst: 'Log in before joining a session',
toastSwitchServer: 'Switch to {{url}} first, then paste again',
toastJoinFail: "Couldn't join session",
joinErrNotFound: 'Session not found',
joinErrEnded: 'Session has ended',
joinErrFull: 'Session is full',
joinErrKicked: "You can't rejoin this session",
joinErrNoUser: 'No active server',
joinErrServerError: "Couldn't join",
ctxAddToSession: 'Add to Orbit session',
ctxSuggestedToast: 'Suggested to the session',
ctxSuggestFailed: "Couldn't suggest — not joined",
guestLive: 'Live',
guestPlaying: 'Playing now',
guestPaused: 'Paused',
guestLoading: 'Loading…',
guestSuggestions: 'Suggestions',
guestHost: 'Host: {{name}}',
guestSubmitter: 'by {{user}}',
guestEmpty: 'No suggestions yet. Open a track\'s context menu and pick "Add to Orbit session".',
guestFooter: "The host controls playback — you can't change the list.",
exitKickedTitle: 'You were removed from the session',
exitEndedTitle: 'The host ended the session',
exitKickedBody: '{{host}} removed you from "{{name}}".',
exitEndedBody: '"{{name}}" has ended. Hope you had fun.',
exitOk: 'OK',
},
};