Feat : Link streamers and recorders
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user