Files
jobtrackingapp/job-tracker-ui/src/components/cv/CvTemplatePreviewDialog.tsx
T

63 lines
3.6 KiB
TypeScript

import React, { useEffect, useId, useRef, useState } from "react";
import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Skeleton, Stack, Typography } from "@mui/material";
import { cvBuilderApi, CvRender, CvTheme, getCvPageMetrics, getCvPreviewFitZoom } from "../../cvBuilder";
import { useI18n } from "../../i18n/I18nProvider";
import { getCvTemplateLabels } from "../CvTemplateGallery";
type Props = { theme: CvTheme; onClose: () => void; onApply: (theme: CvTheme) => void };
export default function CvTemplatePreviewDialog({ theme, onClose, onApply }: Props) {
const { t, language } = useI18n();
const labels = getCvTemplateLabels(theme, language, t);
const id = useId();
const host = useRef<HTMLDivElement>(null);
const [sample, setSample] = useState<CvRender | null>(null);
const [failed, setFailed] = useState(false);
const [attempt, setAttempt] = useState(0);
const [zoom, setZoom] = useState(0.5);
const metrics = getCvPageMetrics("a4");
const locked = theme.available === false;
const currentSample = sample?.themeId === theme.id ? sample : null;
useEffect(() => {
let alive = true;
setSample(null);
setFailed(false);
if (!locked) cvBuilderApi.themePreview(theme.id).then(render => {
if (alive) setSample(render);
}).catch(() => { if (alive) setFailed(true); });
return () => { alive = false; };
}, [theme.id, locked, attempt]);
useEffect(() => {
const element = host.current;
if (!element) return;
const fit = () => setZoom(getCvPreviewFitZoom(element.clientWidth, metrics.widthPx, 16, 0.2));
fit();
const observer = new ResizeObserver(fit);
observer.observe(element);
return () => observer.disconnect();
}, [metrics.widthPx, currentSample]);
return <Dialog open onClose={onClose} fullWidth maxWidth="md" aria-labelledby={`${id}-title`} aria-describedby={`${id}-help`}>
<DialogTitle id={`${id}-title`}>{t("cvEditorPreviewDesign")} · {labels.name}</DialogTitle>
<DialogContent dividers role="region" tabIndex={0} aria-label={t("cvEditorTemplateSampleTitle")}>
<Stack spacing={1.5}>
<Typography id={`${id}-help`} variant="body2" color="text.secondary">{t("cvEditorTemplateSampleHelp")}</Typography>
<Typography variant="body2">{labels.description}</Typography>
<Typography variant="body2">{t("cvEditorTemplateApplyHelp")}</Typography>
{locked ? <Alert severity="warning">{t("cvEditorPro")}</Alert> : failed ? <Alert severity="error" action={<Button onClick={() => setAttempt(value => value + 1)}>{t("cvEditorRetryPreview")}</Button>}>{t("cvEditorTemplatePreviewFailed")}</Alert> : null}
<Box ref={host} aria-busy={!currentSample && !failed && !locked} sx={{ minWidth: 0, bgcolor: "background.default", p: 1, overflow: "auto" }}>
{currentSample ? <Box sx={{ width: metrics.widthPx * zoom, height: metrics.heightPx * zoom, overflow: "hidden", mx: "auto", bgcolor: "common.white", boxShadow: 1 }}>
<iframe title={t("cvEditorTemplateSampleTitle")} srcDoc={currentSample.html} sandbox="" style={{ display: "block", border: 0, width: metrics.widthPx, height: metrics.heightPx, transform: `scale(${zoom})`, transformOrigin: "top left", background: "white" }} />
</Box> : !failed && !locked ? <Skeleton variant="rectangular" height={400} /> : null}
</Box>
</Stack>
</DialogContent>
<DialogActions sx={{ flexWrap: "wrap", px: 3, py: 2 }}>
<Button autoFocus onClick={onClose}>{t("cancel")}</Button>
<Button variant="contained" disabled={locked || !currentSample || failed} onClick={() => onApply(theme)}>{t("cvEditorApplyTemplate")}</Button>
</DialogActions>
</Dialog>;
}