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 && (
<AgentSettings
agent={openAgent}
targets={targets}
onClose={() => setSettingsFor(null)}
onCommand={runCommand}
notify={notify}

View File

@@ -3,18 +3,29 @@ import type {
AgentAction,
AgentView,
BrowserSettings,
StreamState,
WatchSettings,
WatchTarget,
} from '@stream-control/shared';
import { api } from '../api';
interface Props {
agent: AgentView;
/** Streamers déjà suivis : ils alimentent la sélection ci-dessous. */
targets: WatchTarget[];
onClose: () => void;
onCommand: (id: string, action: AgentAction, params?: Record<string, unknown>) => Promise<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 [host, setHost] = useState(agent.obs.host);
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 [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>) {
setBrowser((current) => ({ ...current, ...patch }));
}
@@ -241,12 +258,56 @@ export function AgentSettings({ agent, onClose, onCommand, notify }: Props) {
</select>
</label>
<label className="field grow">
<span>Pseudo du streamer</span>
<input
value={watch.username}
onChange={(event) => patchWatch({ username: event.target.value })}
placeholder="tel qu'il apparaît dans l'URL"
/>
<span>Streamer surveillé</span>
{targets.length > 0 && !manualUsername ? (
<select
value={watch.username}
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>
</div>

View File

@@ -380,6 +380,19 @@ fieldset.group {
border: 1px solid var(--border);
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 {
padding: 0 6px;
font-size: 12px;