78fe2b097b
Removing a saved frame is not like removing a note: its history goes with it, and nothing on screen said so. A shared modal now names the photo, warns how many looks ride along, offers the stored file as a download first, and only then deletes — from MY PHOTOS in the studio and from the folder page alike.
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { useI18n } from '../i18n/I18nProvider';
|
|
import { download } from '../engine/imageOps';
|
|
|
|
// Removing a saved photo is not like removing a note: the looks it carried
|
|
// before its last saves live in its history, and the API drops them with the
|
|
// row. So the removal asks twice — a download first, then the delete — and says
|
|
// plainly what goes. Shared by the studio's MY PHOTOS and the folder page.
|
|
export function DeletePhotoModal({
|
|
id,
|
|
name,
|
|
historyCount,
|
|
onConfirm,
|
|
onClose,
|
|
}: {
|
|
id: number;
|
|
name: string;
|
|
historyCount: number;
|
|
onConfirm: () => void;
|
|
onClose: () => void;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const [problem, setProblem] = useState<string | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
// The file is the clean render the folder stored, so "download first" gives
|
|
// back the same photo the delete is about to take.
|
|
const fetchFirst = async () => {
|
|
setBusy(true);
|
|
setProblem(null);
|
|
try {
|
|
const res = await fetch(`/api/photos/${id}/file`);
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
const slug = name.replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'photo';
|
|
download(bytes, `${slug}.jpg`);
|
|
} catch (err) {
|
|
setProblem(t('err.generic', { msg: err instanceof Error ? err.message : String(err) }));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="modal-backdrop" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
|
|
<div className="modal" data-key="del-modal">
|
|
<h2>{t('del.title')}</h2>
|
|
<p className="hint" data-key="del-warning">
|
|
{t('del.warning', { n: historyCount })}
|
|
</p>
|
|
{problem ? (
|
|
<p className="err" data-key="del-error">
|
|
{problem}
|
|
</p>
|
|
) : null}
|
|
<button type="button" className="btn" data-key="del-download" disabled={busy} onClick={() => void fetchFirst()}>
|
|
{t('del.download')}
|
|
</button>
|
|
<button type="button" className="btn adm-danger" data-key="del-confirm" disabled={busy} onClick={onConfirm}>
|
|
{t('del.confirm')}
|
|
</button>
|
|
<button type="button" className="btn ghost" data-key="del-cancel" onClick={onClose}>
|
|
{t('del.cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|