Update VPN proxy client behavior
This commit is contained in:
@@ -55,22 +55,48 @@ function InstructionBlock({
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
}) {
|
||||
const [copyFeedback, setCopyFeedback] = useState<{ id: string; failed: boolean } | null>(null);
|
||||
const copyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [copyFeedback, setCopyFeedback] = useState<Record<string, { failed: boolean }>>({});
|
||||
const [copyAnnouncement, setCopyAnnouncement] = useState<{ id: string; message: string } | null>(null);
|
||||
const copyTimers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
||||
const copyAttempts = useRef(new Map<string, object>());
|
||||
|
||||
useEffect(() => () => {
|
||||
if (copyTimer.current) clearTimeout(copyTimer.current);
|
||||
for (const timer of copyTimers.current.values()) clearTimeout(timer);
|
||||
copyTimers.current.clear();
|
||||
copyAttempts.current.clear();
|
||||
}, []);
|
||||
|
||||
async function copyInstruction(action: InstructionCopyAction) {
|
||||
if (copyTimer.current) clearTimeout(copyTimer.current);
|
||||
const activeTimer = copyTimers.current.get(action.id);
|
||||
if (activeTimer) clearTimeout(activeTimer);
|
||||
const attempt = {};
|
||||
copyAttempts.current.set(action.id, attempt);
|
||||
let feedback: { failed: boolean };
|
||||
try {
|
||||
await copyText(action.text);
|
||||
setCopyFeedback({ id: action.id, failed: false });
|
||||
feedback = { failed: false };
|
||||
} catch {
|
||||
setCopyFeedback({ id: action.id, failed: true });
|
||||
feedback = { failed: true };
|
||||
}
|
||||
copyTimer.current = setTimeout(() => setCopyFeedback(null), 800);
|
||||
if (copyAttempts.current.get(action.id) !== attempt) return;
|
||||
const announcement = {
|
||||
id: action.id,
|
||||
message: feedback.failed ? `Не удалось скопировать ${action.label}` : `${action.label} скопировано`,
|
||||
};
|
||||
const pendingTimer = copyTimers.current.get(action.id);
|
||||
if (pendingTimer) clearTimeout(pendingTimer);
|
||||
setCopyFeedback((current) => ({ ...current, [action.id]: feedback }));
|
||||
setCopyAnnouncement(announcement);
|
||||
copyTimers.current.set(action.id, setTimeout(() => {
|
||||
setCopyFeedback((current) => {
|
||||
const next = { ...current };
|
||||
delete next[action.id];
|
||||
return next;
|
||||
});
|
||||
setCopyAnnouncement((current) => current?.id === action.id ? null : current);
|
||||
copyTimers.current.delete(action.id);
|
||||
copyAttempts.current.delete(action.id);
|
||||
}, 800));
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -106,7 +132,7 @@ function InstructionBlock({
|
||||
: <code>{block.code}</code>)}
|
||||
{block.copies && <div className="client-instruction-copies">
|
||||
{block.copies.map((action) => {
|
||||
const feedback = copyFeedback?.id === action.id ? copyFeedback : null;
|
||||
const feedback = copyFeedback[action.id];
|
||||
return <div className="client-instruction-copy" key={action.id}>
|
||||
<span>{action.label}</span>
|
||||
<button
|
||||
@@ -122,7 +148,7 @@ function InstructionBlock({
|
||||
</div>;
|
||||
})}
|
||||
<span className="client-live-region" role="status" aria-live="polite">
|
||||
{copyFeedback ? copyFeedback.failed ? 'Не удалось скопировать' : 'Скопировано' : ''}
|
||||
{copyAnnouncement?.message || ''}
|
||||
</span>
|
||||
</div>}
|
||||
{block.note && <p className="client-instruction-note">{block.note}</p>}
|
||||
|
||||
Reference in New Issue
Block a user