up
This commit is contained in:
@@ -28,6 +28,14 @@ export function AutoEQSL({ onSent, onError }: Props) {
|
||||
const busy = useRef(false);
|
||||
const [current, setCurrent] = useState<{ job: Job; model: RenderModel; assets: CardAssets } | null>(null);
|
||||
const svgEl = useRef<SVGSVGElement | null>(null);
|
||||
const sentForRef = useRef<number | null>(null); // qsoId we've already fired SendEQSL for
|
||||
|
||||
// Keep the callbacks in refs so they never change the effects' identity — a
|
||||
// toast/grid re-render from onSent must NOT re-run the send effect (that
|
||||
// re-sent the same eQSL many times in a row).
|
||||
const onSentRef = useRef(onSent);
|
||||
const onErrorRef = useRef(onError);
|
||||
useEffect(() => { onSentRef.current = onSent; onErrorRef.current = onError; });
|
||||
|
||||
// Pull the next job, fetch its render model + assets, then mount it (the
|
||||
// effect below rasterizes once the DOM has it).
|
||||
@@ -41,14 +49,16 @@ export function AutoEQSL({ onSent, onError }: Props) {
|
||||
const assets = await loadCardAssets(model.template, job.templateId);
|
||||
setCurrent({ job, model, assets });
|
||||
} catch (e) {
|
||||
onError?.(`Auto eQSL: ${e}`);
|
||||
onErrorRef.current?.(`Auto eQSL: ${e}`);
|
||||
busy.current = false;
|
||||
void pump();
|
||||
}
|
||||
}, [onError]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const off = EventsOn('qsl:autosend', (p: { qsoId: number; templateId: number; callsign: string }) => {
|
||||
// Dedupe: ignore a repeat event for a QSO we're already handling/handled.
|
||||
if (sentForRef.current === p.qsoId || queue.current.some((j) => j.qsoId === p.qsoId)) return;
|
||||
queue.current.push({ qsoId: p.qsoId, templateId: p.templateId, callsign: p.callsign });
|
||||
void pump();
|
||||
});
|
||||
@@ -56,20 +66,23 @@ export function AutoEQSL({ onSent, onError }: Props) {
|
||||
}, [pump]);
|
||||
|
||||
// Once a job is mounted off-screen, wait for fonts + paint, rasterize, send.
|
||||
// Sends exactly once per job (guarded by sentForRef), independent of renders.
|
||||
useEffect(() => {
|
||||
if (!current) return;
|
||||
if (sentForRef.current === current.job.qsoId) return; // already sent this one
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
try {
|
||||
await document.fonts.ready;
|
||||
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))));
|
||||
if (cancelled || !svgEl.current) return;
|
||||
sentForRef.current = current.job.qsoId;
|
||||
const card = current.model.template.card;
|
||||
const jpeg = await rasterizeCard(svgEl.current, card.w, card.h, 'image/jpeg');
|
||||
await SendEQSL(current.job.qsoId, current.job.templateId, jpeg);
|
||||
onSent?.(current.job.callsign);
|
||||
onSentRef.current?.(current.job.callsign);
|
||||
} catch (e) {
|
||||
onError?.(`Auto eQSL: ${e}`);
|
||||
onErrorRef.current?.(`Auto eQSL: ${e}`);
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setCurrent(null);
|
||||
@@ -79,7 +92,7 @@ export function AutoEQSL({ onSent, onError }: Props) {
|
||||
}
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [current, pump, onSent, onError]);
|
||||
}, [current, pump]);
|
||||
|
||||
if (!current) return null;
|
||||
// Off-screen at full card resolution so the rasterized output matches the
|
||||
|
||||
Reference in New Issue
Block a user