mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
chore(orbit): participant strip at the top of the queue for host and guest
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Radio, Users } from 'lucide-react';
|
import { Radio } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '../store/orbitStore';
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
type SubsonicSong,
|
type SubsonicSong,
|
||||||
} from '../api/subsonic';
|
} from '../api/subsonic';
|
||||||
import CachedImage from './CachedImage';
|
import CachedImage from './CachedImage';
|
||||||
|
import OrbitQueueHead from './OrbitQueueHead';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orbit — guest-side queue view.
|
* Orbit — guest-side queue view.
|
||||||
@@ -67,12 +68,7 @@ export default function OrbitGuestQueue() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="orbit-guest-queue">
|
<div className="orbit-guest-queue">
|
||||||
<div className="orbit-guest-queue__head">
|
<OrbitQueueHead state={state} />
|
||||||
<h2 className="orbit-guest-queue__title">{state.name}</h2>
|
|
||||||
<div className="orbit-guest-queue__meta">
|
|
||||||
<Users size={11} /> {state.participants.length + 1} · {t('orbit.guestHost', { name: state.host })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentTrack && (
|
{currentTrack && (
|
||||||
<div className="orbit-guest-queue__current">
|
<div className="orbit-guest-queue__current">
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Users } from 'lucide-react';
|
||||||
|
import type { OrbitState } from '../api/orbit';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
state: OrbitState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared Orbit head strip rendered at the top of the queue for both host
|
||||||
|
* and guest. Shows the session name and a comma-separated list of every
|
||||||
|
* participant (host first, then guests in join order).
|
||||||
|
*/
|
||||||
|
export default function OrbitQueueHead({ state }: Props) {
|
||||||
|
const names = [state.host, ...state.participants.map(p => p.user)];
|
||||||
|
return (
|
||||||
|
<div className="orbit-queue-head">
|
||||||
|
<h2 className="orbit-queue-head__title">{state.name}</h2>
|
||||||
|
<div className="orbit-queue-head__meta">
|
||||||
|
<Users size={11} />
|
||||||
|
<span className="orbit-queue-head__names">{names.join(', ')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
|
|||||||
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '../store/orbitStore';
|
||||||
import OrbitGuestQueue from './OrbitGuestQueue';
|
import OrbitGuestQueue from './OrbitGuestQueue';
|
||||||
|
import OrbitQueueHead from './OrbitQueueHead';
|
||||||
import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, MoveRight, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react';
|
import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, MoveRight, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react';
|
||||||
import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist, SubsonicPlaylist } from '../api/subsonic';
|
import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist, SubsonicPlaylist } from '../api/subsonic';
|
||||||
import { usePlaylistStore } from '../store/playlistStore';
|
import { usePlaylistStore } from '../store/playlistStore';
|
||||||
@@ -247,6 +248,8 @@ export default function QueuePanel() {
|
|||||||
function QueuePanelHostOrSolo() {
|
function QueuePanelHostOrSolo() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const orbitRole = useOrbitStore(s => s.role);
|
||||||
|
const orbitState = useOrbitStore(s => s.state);
|
||||||
const queue = usePlayerStore(s => s.queue);
|
const queue = usePlayerStore(s => s.queue);
|
||||||
const queueIndex = usePlayerStore(s => s.queueIndex);
|
const queueIndex = usePlayerStore(s => s.queueIndex);
|
||||||
const currentTrack = usePlayerStore(s => s.currentTrack);
|
const currentTrack = usePlayerStore(s => s.currentTrack);
|
||||||
@@ -465,6 +468,9 @@ function QueuePanelHostOrSolo() {
|
|||||||
borderLeftWidth: isQueueVisible ? 1 : 0,
|
borderLeftWidth: isQueueVisible ? 1 : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{orbitRole === 'host' && orbitState && (
|
||||||
|
<OrbitQueueHead state={orbitState} />
|
||||||
|
)}
|
||||||
<QueueHeader
|
<QueueHeader
|
||||||
queue={queue}
|
queue={queue}
|
||||||
queueIndex={queueIndex}
|
queueIndex={queueIndex}
|
||||||
|
|||||||
@@ -1552,7 +1552,6 @@ export const deTranslation = {
|
|||||||
guestUpNext: 'Als Nächstes',
|
guestUpNext: 'Als Nächstes',
|
||||||
guestUpNextEmpty: 'Nichts in der Warteschlange. Öffne bei einem Song das Kontextmenü und wähle „Zur Orbit-Session hinzufügen", um etwas vorzuschlagen.',
|
guestUpNextEmpty: 'Nichts in der Warteschlange. Öffne bei einem Song das Kontextmenü und wähle „Zur Orbit-Session hinzufügen", um etwas vorzuschlagen.',
|
||||||
guestUpNextMore: '+ {{count}} weitere in der Host-Warteschlange',
|
guestUpNextMore: '+ {{count}} weitere in der Host-Warteschlange',
|
||||||
guestHost: 'Gastgeber: {{name}}',
|
|
||||||
guestSubmitter: 'von {{user}}',
|
guestSubmitter: 'von {{user}}',
|
||||||
guestEmpty: 'Noch keine Vorschläge. Öffne bei einem Song das Kontextmenü und wähle „Zur Orbit-Session hinzufügen".',
|
guestEmpty: 'Noch keine Vorschläge. Öffne bei einem Song das Kontextmenü und wähle „Zur Orbit-Session hinzufügen".',
|
||||||
guestFooter: 'Die Wiedergabe wird vom Gastgeber gesteuert — du kannst die Liste nicht ändern.',
|
guestFooter: 'Die Wiedergabe wird vom Gastgeber gesteuert — du kannst die Liste nicht ändern.',
|
||||||
|
|||||||
@@ -1555,7 +1555,6 @@ export const enTranslation = {
|
|||||||
guestUpNext: 'Up next',
|
guestUpNext: 'Up next',
|
||||||
guestUpNextEmpty: 'Nothing queued. Open a track\'s context menu and pick "Add to Orbit session" to suggest one.',
|
guestUpNextEmpty: 'Nothing queued. Open a track\'s context menu and pick "Add to Orbit session" to suggest one.',
|
||||||
guestUpNextMore: '+ {{count}} more in the host\'s queue',
|
guestUpNextMore: '+ {{count}} more in the host\'s queue',
|
||||||
guestHost: 'Host: {{name}}',
|
|
||||||
guestSubmitter: 'by {{user}}',
|
guestSubmitter: 'by {{user}}',
|
||||||
guestEmpty: 'No suggestions yet. Open a track\'s context menu and pick "Add to Orbit session".',
|
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.",
|
guestFooter: "The host controls playback — you can't change the list.",
|
||||||
|
|||||||
@@ -12267,12 +12267,12 @@ html[data-psy-native-hidden="true"] *::after {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.orbit-guest-queue__head {
|
.orbit-queue-head {
|
||||||
padding: 14px 14px 10px;
|
padding: 14px 14px 10px;
|
||||||
border-bottom: 1px solid var(--border-subtle);
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.orbit-guest-queue__title {
|
.orbit-queue-head__title {
|
||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -12281,14 +12281,21 @@ html[data-psy-native-hidden="true"] *::after {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.orbit-guest-queue__meta {
|
.orbit-queue-head__meta {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.orbit-queue-head__meta svg { color: var(--accent); flex-shrink: 0; }
|
||||||
|
.orbit-queue-head__names {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.orbit-guest-queue__meta svg { color: var(--accent); }
|
|
||||||
|
|
||||||
.orbit-guest-queue__current {
|
.orbit-guest-queue__current {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
Reference in New Issue
Block a user