@@ -2,9 +2,10 @@ import { useState, type ReactNode } from 'react';
22import { CheckIcon , ClipboardDocumentIcon , ChevronDownIcon , ChevronUpIcon , EyeIcon } from '@heroicons/react/16/solid' ;
33import { useLabelStore , selectLabelaryNoticeRequired } from '../../store/labelStore' ;
44import { generateMultiPageZPL } from '../../lib/zplGenerator' ;
5- import { generateSetupScript } from '../../lib/zplSetupScript ' ;
5+ import { useCopyToClipboard } from '../../lib/useCopyToClipboard ' ;
66import { useT } from '../../lib/useT' ;
77import { LabelaryNoticeModal } from './LabelaryNoticeModal' ;
8+ import { ZplLine } from './ZplLine' ;
89
910interface Props {
1011 collapsed ?: boolean ;
@@ -26,7 +27,6 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
2627
2728 const hasObjects = pages . some ( ( p ) => p . objects . length > 0 ) ;
2829 const zpl = hasObjects ? generateMultiPageZPL ( label , pages , variables ) : '' ;
29- const setupScript = generateSetupScript ( label ) ;
3030
3131 const previewActive =
3232 previewMode . status === 'loading' || previewMode . status === 'active' ;
@@ -46,19 +46,12 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
4646 return (
4747 < div className = "flex flex-col h-full" >
4848 < OutputSection
49- variant = "primary"
5049 heading = { t . output . zplHeading }
5150 content = { zpl }
5251 emptyMessage = { t . output . noObjects }
5352 collapsed = { collapsed }
5453 onCollapseToggle = { collapsed ? onExpand : onCollapse }
5554 collapseLabel = { collapsed ? t . app . expand : t . app . collapse }
56- /* Hidden-secondary indicator: when the panel is collapsed,
57- the Setup-Script section is also hidden but still has
58- emit-worthy content. A small accent dot in the header
59- signals "there is more output below" so users do not
60- silently lose visibility of their printer-state edits. */
61- hiddenSecondaryActive = { ! ! collapsed && ! ! setupScript }
6255 extraActions = { labelaryEnabled && (
6356 < button
6457 onClick = { togglePreview }
@@ -77,20 +70,6 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
7770 ) }
7871 />
7972
80- { /* Setup-Script section only renders when at least one EEPROM-
81- persistent printer-state field is set. The Printer Settings
82- Modal is where users enable those (^JZ, ^JT, ~TA, plus the
83- upcoming clock/encoding/identity commands); the output here
84- shows what would be sent once to provision the printer. */ }
85- { ! collapsed && setupScript && (
86- < OutputSection
87- variant = "secondary"
88- heading = { t . output . setupScriptHeading }
89- content = { setupScript }
90- emptyMessage = ""
91- />
92- ) }
93-
9473 { showNotice && (
9574 < LabelaryNoticeModal
9675 onClose = { ( ) => setShowNotice ( false ) }
@@ -105,62 +84,32 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
10584}
10685
10786/** Single output pane: header (collapse toggle + heading + extra
108- * actions + copy button) and a `<pre>` body. Reused for the main
109- * per-label ZPL and the Setup-Script output so both sections share
110- * the same look (and any future tweaks land in one place).
111- *
112- * `variant='primary'` fills the remaining flex space (used by the
113- * main per-label ZPL). `variant='secondary'` gets a top border + a
114- * bounded max-height so it sits compactly under the primary pane
115- * (used by the Setup-Script output). The variant collapses two
116- * separate booleans (`borderTop` / `maxHeightClass`) into one
117- * honest discriminator. */
87+ * actions + copy button) and a `<pre>` body. The Setup-Script
88+ * output moved into the Printer Settings modal's docked preview
89+ * pane, so this component no longer needs to host a secondary
90+ * variant — it just shows the per-label ZPL. */
11891function OutputSection ( {
119- variant,
12092 heading,
12193 content,
12294 emptyMessage,
12395 collapsed,
12496 onCollapseToggle,
12597 collapseLabel,
12698 extraActions,
127- hiddenSecondaryActive,
12899} : {
129- variant : 'primary' | 'secondary' ;
130100 heading : string ;
131101 content : string ;
132102 emptyMessage : string ;
133103 collapsed ?: boolean ;
134104 onCollapseToggle ?: ( ) => void ;
135105 collapseLabel ?: string ;
136106 extraActions ?: ReactNode ;
137- /** When `true`, render a small accent dot next to the heading so
138- * users know a hidden secondary section has emit-worthy content
139- * even while this pane is collapsed. */
140- hiddenSecondaryActive ?: boolean ;
141107} ) {
142- const isSecondary = variant === 'secondary' ;
143- const containerCls = isSecondary
144- ? 'border-t border-border'
145- : 'flex-1 min-h-0' ;
146- const bodyCls = isSecondary ? 'max-h-48' : 'flex-1' ;
147108 const t = useT ( ) ;
148- const [ copied , setCopied ] = useState ( false ) ;
149-
150- const handleCopy = ( ) => {
151- if ( ! content ) return ;
152- // `navigator.clipboard` is undefined in non-secure contexts
153- // (plain HTTP, file://) and in some embedded WebViews. Bail
154- // out instead of throwing — the copy button is non-essential.
155- if ( ! navigator . clipboard ) return ;
156- navigator . clipboard . writeText ( content ) . then ( ( ) => {
157- setCopied ( true ) ;
158- setTimeout ( ( ) => setCopied ( false ) , 1500 ) ;
159- } ) . catch ( ( ) => { /* swallow: user-cancel or permission-denied */ } ) ;
160- } ;
109+ const { copy, copied } = useCopyToClipboard ( ( ) => content ) ;
161110
162111 return (
163- < div className = { ` flex flex-col ${ containerCls } ` } >
112+ < div className = " flex flex-col flex-1 min-h-0" >
164113 < div className = "flex items-center justify-between px-3 py-1.5 border-b border-border shrink-0" >
165114 < div className = "flex items-center gap-2" >
166115 { onCollapseToggle && (
@@ -174,18 +123,11 @@ function OutputSection({
174123 </ button >
175124 ) }
176125 < span className = "font-mono text-[10px] text-muted uppercase tracking-widest" > { heading } </ span >
177- { hiddenSecondaryActive && (
178- < span
179- className = "w-1.5 h-1.5 rounded-full bg-accent"
180- title = { t . output . hiddenSecondaryHint }
181- aria-label = { t . output . hiddenSecondaryHint }
182- />
183- ) }
184126 </ div >
185127 < div className = "flex items-center gap-3" >
186128 { extraActions }
187129 < button
188- onClick = { handleCopy }
130+ onClick = { copy }
189131 disabled = { ! content }
190132 title = { t . output . copy }
191133 className = "flex items-center gap-1 font-mono text-[10px] text-muted hover:text-accent disabled:opacity-25 disabled:cursor-not-allowed transition-colors"
@@ -198,7 +140,7 @@ function OutputSection({
198140 </ div >
199141
200142 { ! collapsed && (
201- < pre className = { ` overflow-auto p-3 font-mono text-xs leading-relaxed text-text m-0 bg-surface ${ bodyCls } ` } >
143+ < pre className = " overflow-auto p-3 font-mono text-xs leading-relaxed text-text m-0 bg-surface flex-1" >
202144 { content
203145 ? content . split ( '\n' ) . map ( ( line , i ) => (
204146 < ZplLine key = { i } line = { line } />
@@ -211,16 +153,3 @@ function OutputSection({
211153 ) ;
212154}
213155
214- function ZplLine ( { line } : { line : string } ) {
215- // highlight ^CMD tokens in amber, rest in default text color
216- const parts = line . split ( / ( [ \^ ] [ A - Z 0 - 9 ] + ) / g) ;
217- return (
218- < span className = "block" >
219- { parts . map ( ( part , i ) =>
220- / ^ \^ [ A - Z 0 - 9 ] + $ / . test ( part )
221- ? < span key = { i } className = "text-accent" > { part } </ span >
222- : < span key = { i } className = "text-text" > { part } </ span >
223- ) }
224- </ span >
225- ) ;
226- }
0 commit comments