Skip to content

Commit db3e0b6

Browse files
committed
feat(source): applying an emptied buffer starts a new design after a confirmation
1 parent 2b0d867 commit db3e0b6

37 files changed

Lines changed: 126 additions & 6 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ While you edit, the canvas shows a live preview. Canvas editing and the **Proper
101101

102102
- **Apply** or leaving the panel commits your changes.
103103
- `Esc` or **Cancel** discards them.
104-
- ZPLab asks for confirmation when parsing finds problems, when editor settings would be lost, or when fonts or graphics move into the printer profile. It also asks when you discard a changed source.
104+
- ZPLab asks for confirmation when parsing finds problems, when editor settings would be lost, or when fonts or graphics move into the printer profile. It also asks when you discard a changed source. **Apply** on an emptied source asks whether to start a new design.
105105
- Applying re-imports the ZPL under the [Import guarantees](#import-guarantees).
106106

107107
### Importing existing ZPL

‎src/components/Output/ZPLOutput.sourceEdit.test.tsx‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,30 @@ describe("authoring into an empty document", () => {
389389
expect(useLabelStore.getState().sourceEdit.status).toBe("editing");
390390
expect(useLabelStore.getState().pages).toBe(pages);
391391
});
392+
393+
it("replaces the document with an empty design on the same media when the new-design question is confirmed", () => {
394+
useLabelStore.setState({ label: { widthMm: 70, heightMm: 40, dpmm: 8, darkness: 22 }, variables: [{ id: "v1", name: "n", fnNumber: 1 }] as never });
395+
render(<ZPLOutput onResizeMouseDown={vi.fn()} />);
396+
typeDraft("");
397+
fireEvent.click(screen.getByRole("button", { name: t().output.editSourceApply }));
398+
expect(useLabelStore.getState().pages).toBe(pages);
399+
fireEvent.click(within(screen.getByRole("alertdialog")).getByRole("button", { name: t().app.newDesign }));
400+
const state = useLabelStore.getState();
401+
expect(state.sourceEdit.status).toBe("off");
402+
expect(state.pages.flatMap((p) => p.objects)).toEqual([]);
403+
expect(state.variables).toEqual([]);
404+
expect(state.label).toEqual({ widthMm: 70, heightMm: 40, dpmm: 8 });
405+
expect(useLabelStore.temporal.getState().pastStates).toHaveLength(0);
406+
});
407+
408+
it("keeps the document when the new-design question is declined", () => {
409+
render(<ZPLOutput onResizeMouseDown={vi.fn()} />);
410+
typeDraft("");
411+
fireEvent.click(screen.getByRole("button", { name: t().output.editSourceApply }));
412+
fireEvent.click(within(screen.getByRole("alertdialog")).getByRole("button", { name: t().app.cancel }));
413+
expect(useLabelStore.getState().sourceEdit.status).toBe("editing");
414+
expect(useLabelStore.getState().pages).toBe(pages);
415+
});
392416
});
393417

394418
describe("inline diagnostics", () => {

‎src/components/Output/ZplSourceEditor.tsx‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,13 @@ function SessionChrome({
159159
}) {
160160
const t = useT();
161161
const cancelSourceEdit = useLabelStore((s) => s.cancelSourceEdit);
162+
const applyEmptySource = useLabelStore((s) => s.applyEmptySource);
162163
const applyZplSource = useLabelStore((s) => s.applyZplSource);
163164
// Modal-local like the import modal's result/pending: only the buffer lives
164165
// in the store, the in-flight plan does not survive its session anyway.
165166
const [pendingPlan, setPendingPlan] = useState<SourceApplyOk | null>(null);
166167
const [confirmDiscard, setConfirmDiscard] = useState(false);
168+
const [confirmNew, setConfirmNew] = useState(false);
167169
const liveRefusal = useLabelStore(selectShadowRefusal);
168170
const liveDraft = useLabelStore(selectShadowDraft);
169171
// A located imbalance names bytes, so it may only be quoted while the parse
@@ -195,13 +197,18 @@ function SessionChrome({
195197
};
196198

197199
/** Returns whether the session ended (a held session swallows the click). */
198-
const handleApply = (): boolean => {
200+
const handleApply = (explicit = false): boolean => {
199201
// An untouched buffer must not commit: the reparse would still rename
200202
// variables and replace ids, pure loss for a no-op edit.
201203
if (!dirty) {
202204
cancelSourceEdit();
203205
return true;
204206
}
207+
// A blur is not consent for replacing the document.
208+
if (explicit && session.draft.trim() === '') {
209+
setConfirmNew(true);
210+
return false;
211+
}
205212
const state = useLabelStore.getState();
206213
const plan = prepareSourceApply({
207214
text: session.draft,
@@ -230,7 +237,7 @@ function SessionChrome({
230237
useSessionExit(panelRef, {
231238
onExit: handleApply,
232239
onEscape: requestCancel,
233-
suspended: pendingPlan !== null || confirmDiscard,
240+
suspended: pendingPlan !== null || confirmDiscard || confirmNew,
234241
});
235242

236243
return (
@@ -256,10 +263,10 @@ function SessionChrome({
256263
// Refocus like Cancel: a clean apply unmounts this button. On the
257264
// dialog path the trap's initial focus wins afterwards, harmless.
258265
onClick={() => {
259-
handleApply();
266+
handleApply(true);
260267
focusEditor();
261268
}}
262-
disabled={session.draft.trim() === ''}
269+
disabled={!dirty && session.draft.trim() === ''}
263270
className="px-3 py-1.5 rounded text-xs font-mono bg-accent text-bg hover:opacity-90 disabled:opacity-25 disabled:cursor-not-allowed transition-opacity"
264271
>
265272
{t.output.editSourceApply}
@@ -284,6 +291,24 @@ function SessionChrome({
284291
/>
285292
)}
286293

294+
{confirmNew && (
295+
<ConfirmDialog
296+
message={t.output.editSourceEmptyBody}
297+
confirmLabel={t.app.newDesign}
298+
cancelLabel={t.app.cancel}
299+
destructive
300+
onConfirm={() => {
301+
setConfirmNew(false);
302+
applyEmptySource();
303+
focusEditor();
304+
}}
305+
onCancel={() => {
306+
setConfirmNew(false);
307+
focusEditor();
308+
}}
309+
/>
310+
)}
311+
287312
{confirmDiscard && (
288313
<ConfirmDialog
289314
message={t.output.editSourceDiscardBody}

‎src/lib/sourceEditText.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function sourceRefusalText(
1313
): string {
1414
switch (reason) {
1515
case 'empty':
16-
return t.importModal.errPasteFirst;
16+
return t.output.editSourceEmpty;
1717
case 'noContent':
1818
return t.importModal.errNoObjects;
1919
case 'blobLine':

‎src/locales/ar.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ const ar = {
10571057
lintStillOpenHereFmt: '{cmd} لا يزال مفتوحًا هنا',
10581058
lintInsertCmdFmt: 'إدراج {cmd}',
10591059
editSourceApply: 'تطبيق',
1060+
editSourceEmpty: 'تطبيق يبدأ تصميمًا جديدًا.',
1061+
editSourceEmptyBody: 'هل تريد بدء تصميم جديد؟ يبقى فقط حجم الملصق وملف الطابعة.',
10601062
editSourceGateBlob: 'غير متاح: أحد أسطر الكود طويل جدًا بحيث لا يمكن تحريره كنص (عادةً رسم مضمَّن).',
10611063
editSourceGateSize: 'غير متاح: الكود المُنشأ أكبر من أن يُحرَّر كنص.',
10621064
editSourceGateRecall: 'غير متاح: تستدعي الصفحة تنسيقها المخزّن بدلاً من تخزينه، لذا لا يحتوي الكود على تخطيط للتحرير. اضبط أولاً تسليم هذه الصفحة على كل مهمة طباعة.',

‎src/locales/bg.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ const bg = {
10571057
lintStillOpenHereFmt: '{cmd} все още е отворен тук',
10581058
lintInsertCmdFmt: 'Вмъкни {cmd}',
10591059
editSourceApply: 'Прилагане',
1060+
editSourceEmpty: 'Прилагане започва нов дизайн.',
1061+
editSourceEmptyBody: 'Да се започне ли нов дизайн? Остават само размерът на етикета и профилът на принтера.',
10601062
editSourceGateBlob: 'Недостъпно: един ред от кода е твърде дълъг, за да се редактира като текст (обикновено вградена графика).',
10611063
editSourceGateSize: 'Недостъпно: генерираният код е твърде голям, за да се редактира като текст.',
10621064
editSourceGateRecall: 'Недостъпно: страница извиква съхранения си формат, вместо да го съхрани, затова кодът не съдържа оформление за редактиране. Задайте първо доставката на тази страница на всяко задание за печат.',

‎src/locales/cs.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ const cs = {
10571057
lintStillOpenHereFmt: '{cmd} je zde stále otevřený',
10581058
lintInsertCmdFmt: 'Vložit {cmd}',
10591059
editSourceApply: 'Použít',
1060+
editSourceEmpty: 'Tlačítko Použít spouští nový návrh.',
1061+
editSourceEmptyBody: 'Začít nový návrh? Zůstane jen velikost štítku a profil tiskárny.',
10601062
editSourceGateBlob: 'Nedostupné: jeden řádek kódu je příliš dlouhý na úpravu jako text (obvykle vložená grafika).',
10611063
editSourceGateSize: 'Nedostupné: vygenerovaný kód je příliš velký na úpravu jako text.',
10621064
editSourceGateRecall: 'Nedostupné: stránka vyvolává svůj uložený formát místo toho, aby ho uložila, takže kód neobsahuje žádné rozvržení k úpravě. Nejprve nastavte doručení této stránky na každou tiskovou úlohu.',

‎src/locales/da.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ const da = {
10571057
lintStillOpenHereFmt: '{cmd} er stadig åbent her',
10581058
lintInsertCmdFmt: 'Indsæt {cmd}',
10591059
editSourceApply: 'Anvend',
1060+
editSourceEmpty: 'Knappen Anvend starter et nyt design.',
1061+
editSourceEmptyBody: 'Start et nyt design? Kun etiketstørrelsen og printerprofilen bevares.',
10601062
editSourceGateBlob: 'Ikke tilgængelig: en linje i koden er for lang til at redigere som tekst (som regel en indlejret grafik).',
10611063
editSourceGateSize: 'Ikke tilgængelig: den genererede kode er for stor til at redigere som tekst.',
10621064
editSourceGateRecall: 'Ikke tilgængelig: en side kalder sit gemte format i stedet for at gemme det, så koden indeholder intet layout at redigere. Indstil først sidens levering til hvert printjob.',

‎src/locales/de.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,8 @@ const de = {
10941094
lintStillOpenHereFmt: '{cmd} ist hier noch offen',
10951095
lintInsertCmdFmt: '{cmd} einfügen',
10961096
editSourceApply: 'Übernehmen',
1097+
editSourceEmpty: 'Übernehmen beginnt ein neues Design.',
1098+
editSourceEmptyBody: 'Neues Design beginnen? Nur die Etikettgröße und das Druckerprofil bleiben.',
10971099
editSourceGateBlob: 'Nicht verfügbar: Eine Zeile des Codes ist zu lang, um sie als Text zu bearbeiten (meist eine eingebettete Grafik).',
10981100
editSourceGateSize: 'Nicht verfügbar: Der erzeugte Code ist zu groß, um ihn als Text zu bearbeiten.',
10991101
editSourceGateRecall: 'Nicht verfügbar: Eine Seite ruft ihr gespeichertes Format auf, statt es zu speichern, daher enthält der Code kein Layout zum Bearbeiten. Stelle zuerst die Bereitstellung dieser Seite auf jeden Druckjob um.',

‎src/locales/el.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ const el = {
10571057
lintStillOpenHereFmt: '{cmd} είναι ακόμα ανοιχτό εδώ',
10581058
lintInsertCmdFmt: 'Εισαγωγή {cmd}',
10591059
editSourceApply: 'Εφαρμογή',
1060+
editSourceEmpty: 'Η Εφαρμογή ξεκινά νέο σχέδιο.',
1061+
editSourceEmptyBody: 'Να ξεκινήσει νέο σχέδιο; Παραμένουν μόνο το μέγεθος ετικέτας και το προφίλ εκτυπωτή.',
10601062
editSourceGateBlob: 'Μη διαθέσιμο: μια γραμμή του κώδικα είναι πολύ μεγάλη για επεξεργασία ως κείμενο (συνήθως ενσωματωμένο γραφικό).',
10611063
editSourceGateSize: 'Μη διαθέσιμο: ο κώδικας που δημιουργήθηκε είναι πολύ μεγάλος για επεξεργασία ως κείμενο.',
10621064
editSourceGateRecall: 'Μη διαθέσιμο: μια σελίδα ανακαλεί την αποθηκευμένη μορφή της αντί να την αποθηκεύσει, οπότε ο κώδικας δεν περιέχει διάταξη για επεξεργασία. Ορίστε πρώτα την παράδοση αυτής της σελίδας σε κάθε εργασία εκτύπωσης.',

0 commit comments

Comments
 (0)