저장 훅
save 옵션은 "완료" 를 눌렀을 때 팝업 안에서 호출되는 훅이다. 서버에 올리고 문서에 넣을 주소를 돌려주는 자리이며, 실패를 조용히 넘기지 않는 것이 이 훅의 핵심 규칙이다.
계약
ts
save?: (png: Blob, data: Drawing) => Promise<string | void>;| 것 | 설명 |
|---|---|
png | 방금 구운 PNG Blob |
data | 도형이 살아 있는 Drawing JSON |
반환 string | 문서에 넣을 주소 등. OpenResult.saved 에 그대로 실린다 |
반환 void | 저장은 했지만 돌려줄 값이 없음. saved 는 undefined |
throw | 저장 실패. 팝업은 닫히지 않는다(아래) |
ts
const r = await openDrawing({
background: file,
save: async (png, data) => {
const form = new FormData();
form.append("png", png, "drawing.png");
form.append("data", new Blob([JSON.stringify(data)], { type: "application/json" }), "drawing.json");
const res = await fetch("/api/drawings", { method: "POST", body: form });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
return (await res.json()).url;
},
});
if (r) img.src = r.saved!;완료 시 순서
- 편집 중이던 글자가 있으면 확정한다.
- "완료" 버튼이 "저장 중…" 으로 바뀌고 비활성화된다. 이 동안 취소·그리기는 막힌다.
renderToPng(data)로 PNG 를 굽는다.save가 있으면await save(png, data).- 팝업을 닫고
{ data, png, saved }를 돌려준다.
실패했을 때
3 또는 4 에서 예외가 나면:
window.alert로 이유를 보인다. 메시지는saveFailed(reason)— 기본 한국어는 "그림을 저장하지 못했습니다. 다시 시도하거나 취소하세요." 뒤에Error.message가 붙는다.- 팝업은 열린 채 그대로다. 그린 것은 남아 있다.
- "완료" 버튼이 다시 살아나 재시도할 수 있고, 취소하면
null이 돌아간다. openDrawing의 Promise 는 아직 끝나지 않은 상태다.
즉 훅이 던지면 호출자 쪽에서 별도 재시도 로직을 짤 필요가 없다. 사용자가 팝업 안에서 결정한다.
훅이 없을 때
save 를 주지 않으면 PNG 만 굽고 바로 닫는다. saved 는 undefined. 저장은 호출자가 r.png·r.data 로 직접 한다.
재편집 흐름 — initial
data 를 곁파일로 저장해 두었다면 그것을 initial 로 넘겨 도형째 다시 연다.
ts
// 처음
const r1 = await openDrawing({ background: file, save });
await saveSidecar(r1.saved, r1.data); // PNG 주소 옆에 JSON 을 둔다
// 나중에 그 그림을 고칠 때
const data = await loadSidecar(src); // Drawing | null
const r2 = data
? await openDrawing({ initial: data, save })
: await openDrawing({ background: src, save, notice: "도형 데이터 없이 PNG 만으로 열었습니다." });initial이 있으면background는 무시된다. 배경은initial.background에서 온다.initial은 안에서 복제(structuredClone)해 쓰므로 넘긴 객체는 바뀌지 않는다.- 곁파일을 잃었다면 PNG 를
background로 열 수밖에 없다. 이전 도형은 배경에 박혀 있고 새 도형만 얹을 수 있다.notice로 그 사실을 알려 주는 것이@newtil/editor의 방식이다.