{THEME_SELECTIONS.filter(
(selection) => pageHasCustomTheme || !selection.needsCustomTheme,
).map((selection) => (
setThemeSelection(selection.value)}
>
{t(selection.translationKey)}
))}
{t("common:imageExport.qrCode")}
{settings}
:
}
onPress={handleExport}
isDisabled={isDownloading}
className="mx-auto"
>
{isDownloading
? t("common:actions.loading")
: isMobile
? t("common:actions.share")
: t("common:imageExport.download")}
);
}
/**
* Opens the share sheet when sharing is allowed (mobile) and the platform supports it,
* otherwise downloads the image
*/
async function saveImage(
blob: Blob,
filename: string,
{ canShare }: { canShare: boolean },
) {
const file = new File([blob], filename, { type: blob.type });
if (canShare && navigator.canShare?.({ files: [file] })) {
try {
await navigator.share({ files: [file], title: filename });
return;
} catch (e) {
if (e instanceof Error && e.name === "AbortError") return;
}
}
// a blob url is used over a data url because iOS Safari fails to save big data urls
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
URL.revokeObjectURL(url);
}
function subscribeToPointerQuery(callback: () => void) {
const mediaQueryList = window.matchMedia(COARSE_POINTER_QUERY);
mediaQueryList.addEventListener("change", callback);
return () => mediaQueryList.removeEventListener("change", callback);
}
function useIsMobile() {
return React.useSyncExternalStore(
subscribeToPointerQuery,
() => window.matchMedia(COARSE_POINTER_QUERY).matches,
() => false,
);
}
function usePageHasCustomTheme() {
const matches = useMatches();
return matches.some((match) =>
Boolean(
(match.loaderData as { customTheme?: unknown } | undefined)?.customTheme,
),
);
}