Feat : Link streamers and recorders
Some checks failed
release / build (push) Successful in 25s
release / verify-windows (push) Failing after 1m3s

This commit is contained in:
jeanotx32
2026-08-11 21:25:06 +02:00
parent 37e0c73ab5
commit eb2c741038
3 changed files with 82 additions and 7 deletions

View File

@@ -251,6 +251,7 @@ export function App() {
{openAgent && ( {openAgent && (
<AgentSettings <AgentSettings
agent={openAgent} agent={openAgent}
targets={targets}
onClose={() => setSettingsFor(null)} onClose={() => setSettingsFor(null)}
onCommand={runCommand} onCommand={runCommand}
notify={notify} notify={notify}

View File

@@ -3,18 +3,29 @@ import type {
AgentAction, AgentAction,
AgentView, AgentView,
BrowserSettings, BrowserSettings,
StreamState,
WatchSettings, WatchSettings,
WatchTarget,
} from '@stream-control/shared'; } from '@stream-control/shared';
import { api } from '../api'; import { api } from '../api';
interface Props { interface Props {
agent: AgentView; agent: AgentView;
/** Streamers déjà suivis : ils alimentent la sélection ci-dessous. */
targets: WatchTarget[];
onClose: () => void; onClose: () => void;
onCommand: (id: string, action: AgentAction, params?: Record<string, unknown>) => Promise<void>; onCommand: (id: string, action: AgentAction, params?: Record<string, unknown>) => Promise<void>;
notify: (message: string, tone?: 'info' | 'error') => void; notify: (message: string, tone?: 'info' | 'error') => void;
} }
export function AgentSettings({ agent, onClose, onCommand, notify }: Props) { const STATE_HINTS: Record<StreamState, string> = {
public: 'en direct',
private: 'show privé',
offline: 'hors-ligne',
unknown: '',
};
export function AgentSettings({ agent, targets, onClose, onCommand, notify }: Props) {
const [name, setName] = useState(agent.name); const [name, setName] = useState(agent.name);
const [host, setHost] = useState(agent.obs.host); const [host, setHost] = useState(agent.obs.host);
const [port, setPort] = useState(String(agent.obs.port)); const [port, setPort] = useState(String(agent.obs.port));
@@ -27,6 +38,12 @@ export function AgentSettings({ agent, onClose, onCommand, notify }: Props) {
const [watch, setWatch] = useState<WatchSettings>(agent.watch); const [watch, setWatch] = useState<WatchSettings>(agent.watch);
const [browser, setBrowser] = useState<BrowserSettings>(agent.browser); const [browser, setBrowser] = useState<BrowserSettings>(agent.browser);
// Saisie libre : soit l'opérateur la demande, soit le pseudo déjà configuré
// ne fait pas partie des streamers suivis.
const [manualUsername, setManualUsername] = useState(
() => Boolean(agent.watch.username) && !targets.some((t) => t.username === agent.watch.username),
);
function patchBrowser(patch: Partial<BrowserSettings>) { function patchBrowser(patch: Partial<BrowserSettings>) {
setBrowser((current) => ({ ...current, ...patch })); setBrowser((current) => ({ ...current, ...patch }));
} }
@@ -241,12 +258,56 @@ export function AgentSettings({ agent, onClose, onCommand, notify }: Props) {
</select> </select>
</label> </label>
<label className="field grow"> <label className="field grow">
<span>Pseudo du streamer</span> <span>Streamer surveillé</span>
<input {targets.length > 0 && !manualUsername ? (
value={watch.username} <select
onChange={(event) => patchWatch({ username: event.target.value })} value={watch.username}
placeholder="tel qu'il apparaît dans l'URL" onChange={(event) => {
/> if (event.target.value === '__manual__') {
setManualUsername(true);
patchWatch({ username: '' });
} else {
patchWatch({ username: event.target.value });
}
}}
>
<option value="">— aucun —</option>
{targets.map((target) => {
const hint = STATE_HINTS[target.state];
return (
<option key={target.id} value={target.username}>
{target.label ?? target.username}
{hint ? ` — ${hint}` : ''}
</option>
);
})}
<option value="__manual__">Autre pseudo…</option>
</select>
) : (
<input
value={watch.username}
onChange={(event) => patchWatch({ username: event.target.value })}
placeholder="tel qu'il apparaît dans l'URL"
autoFocus={manualUsername}
/>
)}
{targets.length === 0 ? (
<span className="muted small">
Aucun streamer suivi — ajoute-le dans l'onglet Streamers pour le
retrouver ici.
</span>
) : manualUsername ? (
<button
type="button"
className="link-button"
onClick={() => {
setManualUsername(false);
patchWatch({ username: '' });
}}
>
revenir à la liste
</button>
) : null}
</label> </label>
</div> </div>

View File

@@ -380,6 +380,19 @@ fieldset.group {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
} }
.link-button {
align-self: flex-start;
padding: 2px 0;
background: transparent;
border: none;
color: var(--accent);
font-size: 12px;
cursor: pointer;
}
.link-button:hover {
text-decoration: underline;
}
fieldset.group > legend { fieldset.group > legend {
padding: 0 6px; padding: 0 6px;
font-size: 12px; font-size: 12px;