@@ -316,6 +316,58 @@ const { id } = Astro.params
316316 <textarea id =" ds-info-tpl" class =" fm-input ds-tpl-input" rows =" 7" placeholder =" {filament.material} {filament.color} Ext: {filament.extruder_temp}°C Bed: {filament.bed_temp}°C" ></textarea >
317317 <div class =" ds-token-hints" id =" ds-info-tokens" ></div >
318318 </div >
319+
320+ <!-- Second info column toggle -->
321+ <div class =" ds-info2-toggle" >
322+ <label class =" fm-checkbox-group" >
323+ <input type =" checkbox" id =" ds-info2-show" >
324+ <span id =" dsl-info2-show" >2nd Information Column</span >
325+ </label >
326+ </div >
327+
328+ <!-- Second info column controls (collapsed by default) -->
329+ <div id =" ds-info2-block" class =" ds-info2-block" style =" display:none" >
330+ <div class =" ds-info2-divider" ></div >
331+ <label class =" fm-checkbox-group" >
332+ <input type =" checkbox" id =" ds-info2-vsep" >
333+ <span id =" dsl-info2-vsep" >Vertical separator</span >
334+ </label >
335+ <div class =" ds-size-row" >
336+ <label class =" fm-label ds-size-label" id =" dsl-info2-size" >Text size (mm)</label >
337+ <input type =" range" id =" ds-info2-size-range" min =" 1" max =" 10" step =" 0.5" value =" 2.5" class =" ds-slider" >
338+ <input type =" number" id =" ds-info2-size" value =" 2.5" step =" 0.5" min =" 1" max =" 10" class =" fm-input ds-size-num" >
339+ </div >
340+ <div >
341+ <label class =" fm-label" id =" dsl-info2-halign" >Horizontal align</label >
342+ <div class =" ds-align-group" id =" ds-info2-align-group" data-value =" left" >
343+ <button class =" ds-align-btn active" data-align =" left" title =" Left" >
344+ <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" ><line x1 =" 3" y1 =" 6" x2 =" 21" y2 =" 6" /><line x1 =" 3" y1 =" 12" x2 =" 15" y2 =" 12" /><line x1 =" 3" y1 =" 18" x2 =" 18" y2 =" 18" /></svg >
345+ </button >
346+ <button class =" ds-align-btn" data-align =" center" title =" Center" >
347+ <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" ><line x1 =" 3" y1 =" 6" x2 =" 21" y2 =" 6" /><line x1 =" 6" y1 =" 12" x2 =" 18" y2 =" 12" /><line x1 =" 4" y1 =" 18" x2 =" 20" y2 =" 18" /></svg >
348+ </button >
349+ <button class =" ds-align-btn" data-align =" right" title =" Right" >
350+ <svg width =" 14" height =" 14" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" ><line x1 =" 3" y1 =" 6" x2 =" 21" y2 =" 6" /><line x1 =" 9" y1 =" 12" x2 =" 21" y2 =" 12" /><line x1 =" 6" y1 =" 18" x2 =" 21" y2 =" 18" /></svg >
351+ </button >
352+ </div >
353+ </div >
354+ <div >
355+ <label class =" fm-label" id =" dsl-info2-valign" >Vertical align</label >
356+ <div class =" ds-pill-group" id =" ds-info2-valign-group" data-value =" bottom" >
357+ <button class =" ds-pill-btn" data-val =" top" id =" dsip2-top" >Top</button >
358+ <button class =" ds-pill-btn" data-val =" center" id =" dsip2-mid" >Mid</button >
359+ <button class =" ds-pill-btn active" data-val =" bottom" id =" dsip2-bot" >Bot</button >
360+ </div >
361+ </div >
362+ <div >
363+ <div class =" ds-tpl-label-row" >
364+ <label class =" fm-label" id =" dsl-info2-tpl" >Template</label >
365+ <button class =" ds-syntax-btn" id =" ds-info2-syntax-btn" type =" button" title =" Template syntax help" >?</button >
366+ </div >
367+ <textarea id =" ds-info2-tpl" class =" fm-input ds-tpl-input" rows =" 5" placeholder =" {filament.weight}g {filament.color_hex}" ></textarea >
368+ <div class =" ds-token-hints" id =" ds-info2-tokens" ></div >
369+ </div >
370+ </div >
319371 </div >
320372 </details >
321373
@@ -976,6 +1028,24 @@ const { id } = Astro.params
9761028 gap: 12px 20px;
9771029 }
9781030
1031+ /* Info second-column toggle + sub-block */
1032+ .ds-info2-toggle {
1033+ border-top: 1px solid var(--border);
1034+ padding-top: 8px;
1035+ margin-top: 4px;
1036+ }
1037+
1038+ .ds-info2-block {
1039+ display: flex;
1040+ flex-direction: column;
1041+ gap: 12px;
1042+ }
1043+
1044+ .ds-info2-divider {
1045+ border-top: 1px dashed var(--border);
1046+ margin: 2px 0;
1047+ }
1048+
9791049 /* Show checkbox embedded in section header */
9801050 .ds-header-show {
9811051 display: flex;
@@ -1359,9 +1429,8 @@ const { id } = Astro.params
13591429 btn.addEventListener('click', () => activateTab(btn.dataset.tab as 'print' | 'designer'))
13601430 })
13611431
1362- // Restore last active tab
1432+ // Restore last active tab — deferred until after designer setup completes
13631433 const savedTab = localStorage.getItem(ACTIVE_TAB_KEY)
1364- if (savedTab === 'designer') activateTab('designer')
13651434
13661435 // ─── Label Designer ────────────────────────────────────────────────────────
13671436
@@ -1373,6 +1442,7 @@ const { id } = Astro.params
13731442 title: { show: boolean; sizeMm: number; fitToWidth: boolean; align: 'left'|'center'|'right'; template: string; dividerAbove: boolean; dividerBelow: boolean }
13741443 qr: { mode: 'none'|'simple'|'icon'; sizeMm: number; position: 'left'|'right'; vAlign: 'top'|'center'|'bottom'; linkMode: 'spool'|'url'; urlTemplate: string }
13751444 info: { show: boolean; sizeMm: number; hAlign: 'left'|'center'|'right'; vAlign: 'top'|'center'|'bottom'; template: string }
1445+ info2: { show: boolean; vsep: boolean; sizeMm: number; hAlign: 'left'|'center'|'right'; vAlign: 'top'|'center'|'bottom'; template: string }
13761446 }
13771447
13781448 const DESIGNER_DEFAULTS: LabelDesignerSettings = {
@@ -1382,6 +1452,7 @@ const { id } = Astro.params
13821452 qr: { mode: 'simple', sizeMm: 18, position: 'right', vAlign: 'bottom', linkMode: 'spool', urlTemplate: '' },
13831453 info: { show: true, sizeMm: 2.5, hAlign: 'left', vAlign: 'bottom',
13841454 template: '{filament.material}\n{filament.color}\nExt: {filament.extruder_temp}°C\nBed: {filament.bed_temp}°C' },
1455+ info2: { show: false, vsep: false, sizeMm: 2.5, hAlign: 'left', vAlign: 'bottom', template: '' },
13851456 }
13861457
13871458 // All available tokens for the token-chip hints
@@ -1427,6 +1498,14 @@ const { id } = Astro.params
14271498 vAlign: (document.getElementById('ds-info-valign-group') as HTMLElement).dataset.value as 'top'|'center'|'bottom',
14281499 template: (document.getElementById('ds-info-tpl') as HTMLTextAreaElement).value,
14291500 },
1501+ info2: {
1502+ show: (document.getElementById('ds-info2-show') as HTMLInputElement).checked,
1503+ vsep: (document.getElementById('ds-info2-vsep') as HTMLInputElement).checked,
1504+ sizeMm: Number((document.getElementById('ds-info2-size') as HTMLInputElement).value),
1505+ hAlign: (document.getElementById('ds-info2-align-group') as HTMLElement).dataset.value as 'left'|'center'|'right',
1506+ vAlign: (document.getElementById('ds-info2-valign-group') as HTMLElement).dataset.value as 'top'|'center'|'bottom',
1507+ template: (document.getElementById('ds-info2-tpl') as HTMLTextAreaElement).value,
1508+ },
14301509 }
14311510 localStorage.setItem(DESIGNER_KEY, JSON.stringify(s))
14321511 }
@@ -1445,6 +1524,7 @@ const { id } = Astro.params
14451524 title: { ...DESIGNER_DEFAULTS.title, ...parsed.title },
14461525 qr: { ...DESIGNER_DEFAULTS.qr, ...parsed.qr },
14471526 info: { ...DESIGNER_DEFAULTS.info, ...parsed.info },
1527+ info2: { ...DESIGNER_DEFAULTS.info2, ...parsed.info2 },
14481528 }
14491529 }
14501530 } catch { /* ignore */ }
@@ -1482,6 +1562,16 @@ const { id } = Astro.params
14821562 setAlignGroup('ds-info-align-group', s.info.hAlign)
14831563 setPillGroup('ds-info-valign-group', s.info.vAlign)
14841564 ;(document.getElementById('ds-info-tpl') as HTMLTextAreaElement).value = s.info.template
1565+
1566+ const info2 = s.info2 ?? DESIGNER_DEFAULTS.info2
1567+ ;(document.getElementById('ds-info2-show') as HTMLInputElement).checked = info2.show
1568+ ;(document.getElementById('ds-info2-block') as HTMLElement).style.display = info2.show ? '' : 'none'
1569+ ;(document.getElementById('ds-info2-vsep') as HTMLInputElement).checked = info2.vsep ?? false
1570+ ;(document.getElementById('ds-info2-size') as HTMLInputElement).value = String(info2.sizeMm)
1571+ ;(document.getElementById('ds-info2-size-range') as HTMLInputElement).value = String(info2.sizeMm)
1572+ setAlignGroup('ds-info2-align-group', info2.hAlign)
1573+ setPillGroup('ds-info2-valign-group', info2.vAlign)
1574+ ;(document.getElementById('ds-info2-tpl') as HTMLTextAreaElement).value = info2.template
14851575 }
14861576
14871577 // Helpers for button groups
@@ -1550,19 +1640,26 @@ const { id } = Astro.params
15501640 wireAlignGroup('ds-logo-align-group')
15511641 wireAlignGroup('ds-title-align-group')
15521642 wireAlignGroup('ds-info-align-group')
1643+ wireAlignGroup('ds-info2-align-group')
15531644 wirePillGroup('ds-qr-pos-group')
15541645 wirePillGroup('ds-qr-valign-group')
15551646 wirePillGroup('ds-info-valign-group')
1647+ wirePillGroup('ds-info2-valign-group')
15561648 wirePillGroup('ds-qr-link-group', (val) => {
15571649 ;(document.getElementById('ds-qr-url-wrap') as HTMLElement).style.display = val === 'url' ? '' : 'none'
15581650 })
15591651
1560- const dsInputIds = ['ds-width','ds-height','ds-logo-size','ds-title-size','ds-qr-size','ds-info-size','ds-title-tpl','ds-qr-url-tpl','ds-info-tpl']
1561- const dsCheckIds = ['ds-border','ds-logo-show','ds-title-show','ds-title-fit','ds-divider-above','ds-divider-below','ds-info-show']
1652+ const dsInputIds = ['ds-width','ds-height','ds-logo-size','ds-title-size','ds-qr-size','ds-info-size','ds-info2-size','ds- title-tpl','ds-qr-url-tpl','ds-info-tpl','ds-info2 -tpl']
1653+ const dsCheckIds = ['ds-border','ds-logo-show','ds-title-show','ds-title-fit','ds-divider-above','ds-divider-below','ds-info-show','ds-info2-show','ds-info2-vsep' ]
15621654 const dsSelectIds = ['ds-qr-mode']
15631655 ;[...dsInputIds, ...dsCheckIds, ...dsSelectIds].forEach(id => {
15641656 document.getElementById(id)?.addEventListener('change', () => { saveDesignerSettings(); updateDesignerPreview() })
15651657 })
1658+ // Toggle info2 block visibility when its show-checkbox changes
1659+ document.getElementById('ds-info2-show')?.addEventListener('change', (e) => {
1660+ const show = (e.target as HTMLInputElement).checked
1661+ ;(document.getElementById('ds-info2-block') as HTMLElement).style.display = show ? '' : 'none'
1662+ })
15661663 dsInputIds.forEach(id => {
15671664 const el = document.getElementById(id)
15681665 if (el && (el as HTMLInputElement).type !== 'checkbox') {
@@ -1576,6 +1673,7 @@ const { id } = Astro.params
15761673 ['ds-title-size-range', 'ds-title-size'],
15771674 ['ds-qr-size-range', 'ds-qr-size'],
15781675 ['ds-info-size-range', 'ds-info-size'],
1676+ ['ds-info2-size-range', 'ds-info2-size'],
15791677 ]
15801678 for (const [rangId, numId] of sliderPairs) {
15811679 const rang = document.getElementById(rangId) as HTMLInputElement
@@ -1650,10 +1748,12 @@ const { id } = Astro.params
16501748 }
16511749 document.getElementById('ds-title-syntax-btn')?.addEventListener('click', (e) => { e.stopPropagation(); openSyntaxPopover(e.currentTarget as HTMLElement) })
16521750 document.getElementById('ds-info-syntax-btn')?.addEventListener('click', (e) => { e.stopPropagation(); openSyntaxPopover(e.currentTarget as HTMLElement) })
1751+ document.getElementById('ds-info2-syntax-btn')?.addEventListener('click', (e) => { e.stopPropagation(); openSyntaxPopover(e.currentTarget as HTMLElement) })
16531752
16541753 // Token chips
16551754 buildTokenChips('ds-title-tokens', 'ds-title-tpl', DESIGNER_TOKENS)
16561755 buildTokenChips('ds-info-tokens', 'ds-info-tpl', DESIGNER_TOKENS)
1756+ buildTokenChips('ds-info2-tokens', 'ds-info2-tpl', DESIGNER_TOKENS)
16571757 buildTokenChips('ds-qr-tokens', 'ds-qr-url-tpl', ['{id}', '{filament.name}'])
16581758
16591759 // Load persisted designer settings
@@ -1826,6 +1926,7 @@ const { id } = Astro.params
18261926 title: { ...DESIGNER_DEFAULTS.title, ...preset.settings.title },
18271927 qr: { ...DESIGNER_DEFAULTS.qr, ...preset.settings.qr },
18281928 info: { ...DESIGNER_DEFAULTS.info, ...preset.settings.info },
1929+ info2: { ...DESIGNER_DEFAULTS.info2, ...(preset.settings.info2 ?? {}) },
18291930 }
18301931 // Apply to form
18311932 ;(document.getElementById('ds-logo-show') as HTMLInputElement).checked = s.logo.show
@@ -1857,6 +1958,15 @@ const { id } = Astro.params
18571958 setAlignGroup('ds-info-align-group', s.info.hAlign)
18581959 setPillGroup('ds-info-valign-group', s.info.vAlign)
18591960 ;(document.getElementById('ds-info-tpl') as HTMLTextAreaElement).value = s.info.template
1961+ const _pi2 = s.info2 ?? DESIGNER_DEFAULTS.info2
1962+ ;(document.getElementById('ds-info2-show') as HTMLInputElement).checked = _pi2.show
1963+ ;(document.getElementById('ds-info2-block') as HTMLElement).style.display = _pi2.show ? '' : 'none'
1964+ ;(document.getElementById('ds-info2-vsep') as HTMLInputElement).checked = _pi2.vsep ?? false
1965+ ;(document.getElementById('ds-info2-size') as HTMLInputElement).value = String(_pi2.sizeMm)
1966+ ;(document.getElementById('ds-info2-size-range') as HTMLInputElement).value = String(_pi2.sizeMm)
1967+ setAlignGroup('ds-info2-align-group', _pi2.hAlign)
1968+ setPillGroup('ds-info2-valign-group', _pi2.vAlign)
1969+ ;(document.getElementById('ds-info2-tpl') as HTMLTextAreaElement).value = _pi2.template
18601970 saveDesignerSettings()
18611971 _activePresetName = name
18621972 presetNameInput.value = name
@@ -1996,6 +2106,15 @@ const { id } = Astro.params
19962106 setAlignGroup('ds-info-align-group', d.hAlign)
19972107 setPillGroup('ds-info-valign-group', d.vAlign)
19982108 ;(document.getElementById('ds-info-tpl') as HTMLTextAreaElement).value = d.template
2109+ const d2 = DESIGNER_DEFAULTS.info2
2110+ ;(document.getElementById('ds-info2-show') as HTMLInputElement).checked = d2.show
2111+ ;(document.getElementById('ds-info2-block') as HTMLElement).style.display = d2.show ? '' : 'none'
2112+ ;(document.getElementById('ds-info2-vsep') as HTMLInputElement).checked = d2.vsep
2113+ ;(document.getElementById('ds-info2-size') as HTMLInputElement).value = String(d2.sizeMm)
2114+ ;(document.getElementById('ds-info2-size-range') as HTMLInputElement).value = String(d2.sizeMm)
2115+ setAlignGroup('ds-info2-align-group', d2.hAlign)
2116+ setPillGroup('ds-info2-valign-group', d2.vAlign)
2117+ ;(document.getElementById('ds-info2-tpl') as HTMLTextAreaElement).value = d2.template
19992118 })
20002119
20012120 // ─── End Label Designer ────────────────────────────────────────────────────
@@ -2161,6 +2280,15 @@ const { id } = Astro.params
21612280 document.getElementById('dsip-mid').textContent = t('spools.dsMid', 'Mid')
21622281 document.getElementById('dsip-bot').textContent = t('spools.dsBot', 'Bot')
21632282 document.getElementById('dsl-info-tpl').textContent = t('spools.dsTemplate', 'Template')
2283+ document.getElementById('dsl-info2-show').textContent = t('spools.dsInfo2Show', '2nd Information Column')
2284+ document.getElementById('dsl-info2-vsep').textContent = t('spools.dsInfo2Vsep', 'Vertical separator')
2285+ document.getElementById('dsl-info2-size').textContent = t('spools.dsInfoSize', 'Text size (mm)')
2286+ document.getElementById('dsl-info2-halign').textContent = t('spools.dsHAlign', 'Horizontal align')
2287+ document.getElementById('dsl-info2-valign').textContent = t('spools.dsVAlign', 'Vertical align')
2288+ document.getElementById('dsip2-top').textContent = t('spools.dsTop', 'Top')
2289+ document.getElementById('dsip2-mid').textContent = t('spools.dsMid', 'Mid')
2290+ document.getElementById('dsip2-bot').textContent = t('spools.dsBot', 'Bot')
2291+ document.getElementById('dsl-info2-tpl').textContent = t('spools.dsTemplate', 'Template')
21642292 document.getElementById('label-width-text').textContent = (window as any).__t('spools.labelWidth')
21652293 document.getElementById('label-height-text').textContent = (window as any).__t('spools.labelHeight')
21662294 document.getElementById('label-font-size-text').textContent = (window as any).__t('spools.fontSize')
@@ -2422,7 +2550,19 @@ const { id } = Astro.params
24222550 const s = (() => {
24232551 try {
24242552 const raw = localStorage.getItem(DESIGNER_KEY)
2425- if (raw) return { ...DESIGNER_DEFAULTS, ...JSON.parse(raw) }
2553+ if (raw) {
2554+ const parsed = JSON.parse(raw)
2555+ return {
2556+ ...DESIGNER_DEFAULTS,
2557+ ...parsed,
2558+ logo: { ...DESIGNER_DEFAULTS.logo, ...parsed.logo },
2559+ label: { ...DESIGNER_DEFAULTS.label, ...parsed.label },
2560+ title: { ...DESIGNER_DEFAULTS.title, ...parsed.title },
2561+ qr: { ...DESIGNER_DEFAULTS.qr, ...parsed.qr },
2562+ info: { ...DESIGNER_DEFAULTS.info, ...parsed.info },
2563+ info2: { ...DESIGNER_DEFAULTS.info2, ...parsed.info2 },
2564+ }
2565+ }
24262566 } catch { /* ignore */ }
24272567 return DESIGNER_DEFAULTS
24282568 })()
@@ -2525,12 +2665,16 @@ const { id } = Astro.params
25252665 mainRow.style.padding = '1mm'
25262666 mainRow.style.gap = '1.5mm'
25272667 mainRow.style.flexDirection = s.qr.position === 'left' ? 'row-reverse' : 'row'
2528- mainRow.style.alignItems = (s.info.show && s.info.template.trim())
2529- ? (s.info.vAlign === 'top' ? 'flex-start' : s.info.vAlign === 'center' ? 'center' : 'flex-end')
2530- : 'stretch'
2668+ mainRow.style.alignItems = (() => {
2669+ const v = (s.info.show && s.info.template.trim()) ? s.info.vAlign
2670+ : (s.info2?.show && s.info2.template.trim()) ? s.info2.vAlign
2671+ : null
2672+ if (!v) return 'stretch'
2673+ return v === 'top' ? 'flex-start' : v === 'center' ? 'center' : 'flex-end'
2674+ })()
25312675 labelPreview.appendChild(mainRow)
25322676
2533- // Information block
2677+ // Information block (primary)
25342678 if (s.info.show && s.info.template.trim()) {
25352679 const infoEl = document.createElement('div')
25362680 infoEl.style.flex = '1'
@@ -2540,12 +2684,38 @@ const { id } = Astro.params
25402684 infoEl.style.color = 'black'
25412685 infoEl.style.overflow = 'hidden'
25422686 infoEl.style.lineHeight = '1.4'
2543-
25442687 const frag = parseTemplate(s.info.template, data)
25452688 infoEl.appendChild(frag)
25462689 mainRow.appendChild(infoEl)
25472690 }
25482691
2692+ // Information block 2 (second column)
2693+ const hasInfo2 = s.info2?.show && s.info2.template.trim()
2694+ if (hasInfo2) {
2695+ // Vertical separator between info1 and info2
2696+ if (s.info2.vsep) {
2697+ const sep = document.createElement('div')
2698+ sep.style.width = '1px'
2699+ sep.style.alignSelf = 'stretch'
2700+ sep.style.minHeight = '100%'
2701+ sep.style.background = 'black'
2702+ sep.style.flexShrink = '0'
2703+ mainRow.appendChild(sep)
2704+ }
2705+ const info2El = document.createElement('div')
2706+ info2El.style.flex = '1'
2707+ info2El.style.minWidth = '0'
2708+ info2El.style.fontSize = (s.info2.sizeMm * 3.78) + 'px'
2709+ info2El.style.textAlign = s.info2.hAlign
2710+ info2El.style.color = 'black'
2711+ info2El.style.overflow = 'hidden'
2712+ info2El.style.lineHeight = '1.4'
2713+ info2El.style.alignSelf = s.info2.vAlign === 'top' ? 'flex-start' : s.info2.vAlign === 'center' ? 'center' : 'flex-end'
2714+ const frag2 = parseTemplate(s.info2.template, data)
2715+ info2El.appendChild(frag2)
2716+ mainRow.appendChild(info2El)
2717+ }
2718+
25492719 // QR Code block
25502720 if (s.qr.mode !== 'none') {
25512721 const qrWrap = document.createElement('div')
@@ -2647,6 +2817,9 @@ const { id } = Astro.params
26472817 updatePreview()
26482818 })
26492819
2820+ // Restore active tab now that all designer state is fully initialized
2821+ if (savedTab === 'designer') activateTab('designer')
2822+
26502823 // Initialize: load logo then render whichever tab is active
26512824 loadLabelLogo().then(() => {
26522825 const activeTab = localStorage.getItem(ACTIVE_TAB_KEY)
0 commit comments