diff --git a/src/App.css b/src/App.css deleted file mode 100644 index f90339d8..00000000 --- a/src/App.css +++ /dev/null @@ -1,184 +0,0 @@ -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) - scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) - scale(0.8); - } -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } -} diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index 172059ec..1fc88930 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -65,6 +65,11 @@ export function BarcodeObject({ if (opts) { const canvas = document.createElement("canvas"); try { + // buildBwipOptions returns Record on purpose: the + // option fields differ across barcode types (ean13 vs code128 vs …) + // and per-type narrowing would duplicate the switch already in + // buildBwipOptions. bwip-js' toCanvas signature uses a strict + // literal-string union, so the structural cast bridges the two. bwipjs.toCanvas(canvas, opts as unknown as Parameters[1]); barcodeCanvas = canvas; } catch (e) { diff --git a/src/components/Canvas/KonvaObject.tsx b/src/components/Canvas/KonvaObject.tsx index 44ff1492..852e9b20 100644 --- a/src/components/Canvas/KonvaObject.tsx +++ b/src/components/Canvas/KonvaObject.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { getFontFamily } from "../../lib/fontCache"; import { useFontCacheVersion } from "../../hooks/useFontCacheVersion"; import { @@ -246,11 +246,18 @@ function ImageObject({ const y = offsetY + dotsToPx(obj.y, scale, dpmm); const [htmlImg, setHtmlImg] = useState(null); + // Reset the cached HTMLImageElement during render when the source changes, + // instead of inside an effect. The "set state during render on prop change" + // pattern is the official React workaround for what would otherwise be a + // setState-in-effect anti-pattern. The next render observes prevDataUrl + // already updated, so this does not loop. + const prevDataUrlRef = useRef(cached?.dataUrl); + if (prevDataUrlRef.current !== cached?.dataUrl) { + prevDataUrlRef.current = cached?.dataUrl; + setHtmlImg(null); + } useEffect(() => { - if (!cached) { - setHtmlImg(null); // eslint-disable-line react-hooks/set-state-in-effect - return; - } + if (!cached) return; let active = true; const img = new window.Image(); img.src = cached.dataUrl; @@ -260,7 +267,7 @@ function ImageObject({ return () => { active = false; }; - }, [cached?.dataUrl, cached]); + }, [cached]); const handleDragMove = (e: Konva.KonvaEventObject) => { onChange({ @@ -376,7 +383,7 @@ function KonvaObjectInner({ let displayY = obj.y; if (obj.positionType === "FT") { if (obj.type === "text" || obj.type === "serial") { - const p = obj.props as { fontHeight: number; rotation: string }; + const p = obj.props; // ^FT places the origin at the baseline of the first character. // The Konva anchor point after rotation sits at a different corner // of the visual bounding box than the ZPL FT baseline origin: @@ -402,7 +409,7 @@ function KonvaObjectInner({ // Konva rotates text around its top-left corner, but ZPL's ^FO anchor // shifts with rotation. 15 dots is an empirically determined fixed offset. if (obj.type === "text" || obj.type === "serial") { - const p = obj.props as { fontHeight: number; rotation: string }; + const p = obj.props; const ROTATION_OFFSET = 15; // dots — empirical canvas/ZPL alignment correction if (p.rotation === "I") { displayY -= ROTATION_OFFSET; @@ -435,7 +442,7 @@ function KonvaObjectInner({ let finalY = pxToDots(e.target.y() - offsetY, scale, dpmm); if (obj.type === "text" || obj.type === "serial") { - const p = obj.props as { fontHeight: number; rotation: string }; + const p = obj.props; const ROTATION_OFFSET = 15; if (p.rotation === "I") { finalY += ROTATION_OFFSET; @@ -451,7 +458,7 @@ function KonvaObjectInner({ // instead of the ZPL baseline coordinate, causing a vertical jump on re-render. if (obj.positionType === "FT") { if (obj.type === "text" || obj.type === "serial") { - const p = obj.props as { fontHeight: number; rotation: string }; + const p = obj.props; const renderedH = p.fontHeight / 1.3; if (p.rotation === "N") { finalY += p.fontHeight;