@@ -42,7 +42,7 @@ import {
4242 MoonIcon ,
4343 GlobeAltIcon ,
4444} from "@heroicons/react/16/solid" ;
45- import { useLabelStore , useHistory , selectLabelaryNoticeRequired , selectEditorFrozen , selectSourceEditing , selectSourceEditDirty , selectDocumentEmits , selectBatchPrintCount } from "../store/labelStore" ;
45+ import { useLabelStore , useHistory , selectLabelaryNoticeRequired , selectEffectivePreviewProvider , selectEditorFrozen , selectSourceEditing , selectSourceEditDirty , selectDocumentEmits , selectBatchPrintCount } from "../store/labelStore" ;
4646import { datasetTimestamp } from "@zplab/core/types/DataSource" ;
4747import { isCurrentDataContext , settleDatasetReplace } from "../store/datasetActions" ;
4848import { ConfirmDialog } from "./ui/ConfirmDialog" ;
@@ -54,6 +54,8 @@ import { isDesktopShell, isMacDesktop } from "../lib/platform";
5454import { acceptAttr , DESIGN_FILTER , CSV_FILTER } from "../lib/fileDialogs" ;
5555import { openExternal , REPO_URL } from "../lib/openExternal" ;
5656import { LabelaryNoticeModal } from "./Output/LabelaryNoticeModal" ;
57+ import { PdfExportDialog } from "./Output/PdfExportDialog" ;
58+ import { usePdfExport } from "../hooks/usePdfExport" ;
5759import { PrinterSettingsModal } from "./PrinterSettings/PrinterSettingsModal" ;
5860import { Gs1ContentModal } from "./Barcode/Gs1ContentModal" ;
5961import { ContentBuilderModal } from "./Barcode/ContentBuilderModal" ;
@@ -106,6 +108,8 @@ const MENU_ICONS: Partial<Record<MenuItemId, ComponentType<SVGProps<SVGSVGElemen
106108 settings : Cog6ToothIcon ,
107109 exportZpl : ArrowDownTrayIcon ,
108110 exportBatch : ArrowDownTrayIcon ,
111+ exportPdf : ArrowDownTrayIcon ,
112+ exportBatchPdf : ArrowDownTrayIcon ,
109113 openDesign : FolderOpenIcon ,
110114 saveDesign : DocumentArrowDownIcon ,
111115 importCsv : TableCellsIcon ,
@@ -135,9 +139,12 @@ export function AppShell() {
135139 const restoreReplacedDesign = useLabelStore ( ( s ) => s . restoreReplacedDesign ) ;
136140 const dismissReplacedDesign = useLabelStore ( ( s ) => s . dismissReplacedDesign ) ;
137141 const sourceEditDirty = useLabelStore ( selectSourceEditDirty ) ;
138- const labelaryEnabled = useLabelStore ( ( s ) => s . thirdParty . labelary ) ;
139142 const noticeRequired = useLabelStore ( selectLabelaryNoticeRequired ) ;
140- const [ showPrintNotice , setShowPrintNotice ] = useState ( false ) ;
143+ const renderer = useLabelStore ( selectEffectivePreviewProvider ) ;
144+ const pageCount = useLabelStore ( ( s ) => s . pages . length ) ;
145+ // The Labelary notice gates every Labelary render, so the action waits here until the user continues.
146+ const [ afterNotice , setAfterNotice ] = useState < ( ( ) => void ) | null > ( null ) ;
147+ const withNotice = ( action : ( ) => void ) => ( renderer === "labelary" && noticeRequired ? setAfterNotice ( ( ) => action ) : action ( ) ) ;
141148 const appUpdate = useLabelStore ( ( s ) => s . appUpdate ) ;
142149 const checkForAppUpdate = useLabelStore ( ( s ) => s . checkForAppUpdate ) ;
143150 const installAppUpdate = useLabelStore ( ( s ) => s . installAppUpdate ) ;
@@ -205,6 +212,9 @@ export function AppShell() {
205212 // timestamp is unique per import/fetch, so it also catches re-loading the
206213 // same file or table (which a name key would miss).
207214 const datasetKey = useLabelStore ( ( s ) => s . dataset && datasetTimestamp ( s . dataset . source ) ) ;
215+ // Imperative handle to the canvas: live render bboxes for the properties panel and the label capture for print and PDF.
216+ const canvasRef = useRef < LabelCanvasHandle > ( null ) ;
217+ const { progress : pdfProgress , exportPdf, exportBatchPdf, cancel : cancelPdfExport } = usePdfExport ( canvasRef ) ;
208218 const {
209219 showZplImport,
210220 openZplImport,
@@ -218,7 +228,7 @@ export function AppShell() {
218228 canBatchExport,
219229 batchRowCount,
220230 handlePrint,
221- } = useZplImportExport ( ) ;
231+ } = useZplImportExport ( canvasRef ) ;
222232 const outputPanel = useOutputPanel ( OUTPUT_DEFAULT_H , sourceEditing ) ;
223233 const leftPanel = useCollapsiblePanel ( "zpl-panel-left" ) ;
224234 const rightPanel = useCollapsiblePanel ( "zpl-panel-right" ) ;
@@ -233,7 +243,8 @@ export function AppShell() {
233243 batchRowCount,
234244 batchPrintCount,
235245 connectDataWizard : isDesktopShell ,
236- labelaryEnabled,
246+ canBatchPdf : renderer !== "none" ,
247+ pdfCurrentPageOnly : renderer === "none" && pageCount > 1 ,
237248 canUndo,
238249 canRedo,
239250 // On macOS quit lives in the app submenu (Cmd+Q), not the File section.
@@ -246,24 +257,20 @@ export function AppShell() {
246257 settings : ( ) => setPrinterSettingsTab ( "appSettings" ) ,
247258 exportZpl : handleDownload ,
248259 exportBatch : handleExportBatch ,
260+ exportPdf : ( ) => withNotice ( exportPdf ) ,
261+ exportBatchPdf : ( ) => withNotice ( exportBatchPdf ) ,
249262 openDesign : handleOpen ,
250263 saveDesign : handleSave ,
251264 importCsv : openCsvPicker ,
252265 connectData : openConnectWizard ,
253- // Print routes through Labelary; clicking before the notice has been
254- // acknowledged opens the disclosure first, then prints.
255- print : ( ) => ( noticeRequired ? setShowPrintNotice ( true ) : void handlePrint ( ) ) ,
266+ print : ( ) => withNotice ( ( ) => void handlePrint ( ) ) ,
256267 sendToZebra : openZebraPrint ,
257268 undo : ( ) => undo ( ) ,
258269 redo : ( ) => redo ( ) ,
259270 github : ( ) => openExternal ( REPO_URL ) ,
260271 // Desktop-only item (includeQuit); never reached on web.
261272 quit : ( ) => void quitApp ( ) ,
262273 } ;
263- // Imperative handle to the canvas for actions PropertiesPanel needs live
264- // render bboxes for (e.g. align-to-label centring).
265- const canvasRef = useRef < LabelCanvasHandle > ( null ) ;
266-
267274 return (
268275 < div className = "flex flex-col h-screen overflow-hidden bg-bg text-text font-sans" >
269276 < SourceShadowSync />
@@ -595,15 +602,16 @@ export function AppShell() {
595602 { showZebraPrint && (
596603 < PrintToZebraDialog zpl = { currentZpl ( ) } onClose = { closeZebraPrint } />
597604 ) }
598- { showPrintNotice && (
605+ { afterNotice && (
599606 < LabelaryNoticeModal
600- onClose = { ( ) => setShowPrintNotice ( false ) }
607+ onClose = { ( ) => setAfterNotice ( null ) }
601608 onContinue = { ( ) => {
602- setShowPrintNotice ( false ) ;
603- handlePrint ( ) ;
609+ setAfterNotice ( null ) ;
610+ afterNotice ( ) ;
604611 } }
605612 />
606613 ) }
614+ { pdfProgress && < PdfExportDialog progress = { pdfProgress } onCancel = { cancelPdfExport } /> }
607615 < PrinterSettingsModal />
608616 < Gs1ContentModal />
609617 < ContentBuilderModal />
0 commit comments