diff --git a/src/components/a4-canvas.tsx b/src/components/a4-canvas.tsx index e971e03..9e93d82 100644 --- a/src/components/a4-canvas.tsx +++ b/src/components/a4-canvas.tsx @@ -17,7 +17,7 @@ const Container = styled.div<{ $cursor?: string }>` cursor: ${({ $cursor }) => $cursor || ""}; - image-rendering: -webkit-optimize-contrast; + image-rendering: -webkit-optimize-contrast; backface-visibility: hidden; perspective: 1000; `; @@ -50,8 +50,10 @@ export default function A4Canvas({ handler }: CanvasHandlerProps) { useEffect(() => { const container = containerRef.current; if (!container) return; - panRef.current.offsetX = (container.offsetWidth - (settings.landscape ? settings.paperHeight : settings.paperWidth) * settings.scale) / 2; - panRef.current.offsetY = (container.offsetHeight - (settings.landscape ? settings.paperWidth : settings.paperHeight) * settings.scale) / 2; + panRef.current.offsetX = + (container.offsetWidth - (settings.landscape ? settings.paperHeight : settings.paperWidth) * settings.scale) / 2; + panRef.current.offsetY = + (container.offsetHeight - (settings.landscape ? settings.paperWidth : settings.paperHeight) * settings.scale) / 2; applyTransform(); const unsub = handler.toolHandler.emitter.on("select", (tool) => { @@ -123,7 +125,7 @@ export default function A4Canvas({ handler }: CanvasHandlerProps) { break; case "Delete": { const copy = [...handler.selected]; - if (copy.length && await handler.popup.confirm("Title", "Are you sure you want to delete")) { + if (copy.length && (await handler.popup.confirm("Title", "Are you sure you want to delete"))) { for (const id of copy) { handler.deleteImage(id); } @@ -135,7 +137,6 @@ export default function A4Canvas({ handler }: CanvasHandlerProps) { handler.selected.forEach((e) => { handler.move(e, x, y); }); - } }; @@ -145,7 +146,6 @@ export default function A4Canvas({ handler }: CanvasHandlerProps) { }; }); - const handleMouseDown = (e: React.MouseEvent) => { if (!isDragging.current && handler.toolHandler.isToolSelected(TOOL_CANVAS_MOVE)) { setCursor("grabbing"); @@ -174,16 +174,12 @@ export default function A4Canvas({ handler }: CanvasHandlerProps) { // } // const mx = e.clientX - panRef.current.offsetX; // const my = e.clientY - panRef.current.offsetY; - // const dx = mx - dragStart.current.x; // const dy = my - dragStart.current.y; - // const x = dx / settings.scale; // const y = dy / settings.scale; - // dragStart.current.x = mx; // dragStart.current.y = my; - // handler.selected.forEach((e) => { // handler.move(e, x, y); // }); diff --git a/src/components/download-buttons.tsx b/src/components/download-buttons.tsx index 7d3be06..e8fd9bc 100644 --- a/src/components/download-buttons.tsx +++ b/src/components/download-buttons.tsx @@ -26,79 +26,132 @@ const Column = styled.div` height: 100%; `; -const Box = styled.div<{ $width: number, $height: number }>` +const Box = styled.div<{ $width: number; $height: number }>` width: ${({ $width }) => $width}px; height: ${({ $height }) => $height}px; padding: 10px; `; - export function ImportExportButtons({ handler, selected }: CanvasHandlerProps & { selected: ImageEditor[] }) { const boxSize = 90; - return <> - - - - - { downloadZip(handler); }}> - ZIP - - { createPdf(handler, true).save(`${handler.projectName}.pdf`); }}> - PDF - - { downloadBlob(await createSVGDoc(handler), `${handler.projectName}.svg`); }} > - SVG - - - - { - const v = handler.imagesRenders.find(e => e.id === selected[0].id); - if (v) { - const canvas = document.createElement("canvas"); - canvas.width = v.image.naturalWidth * v.scale; - canvas.height = v.image.naturalHeight * v.scale; - const ctx = canvas.getContext("2d")!; - ctx.drawImage(v.image, 0, 0, canvas.width, canvas.height); - canvas.toBlob(blob => { - if (blob) { - downloadBlob(blob, `${v.name}.png`); + return ( + <> + + + + { + downloadZip(handler); + }} + > + + ZIP + + { + createPdf(handler, true).save(`${handler.projectName}.pdf`); + }} + > + + PDF + + { + downloadBlob(await createSVGDoc(handler), `${handler.projectName}.svg`); + }} + > + + SVG + + + + { + const v = handler.imagesRenders.find((e) => e.id === selected[0].id); + if (v) { + const canvas = document.createElement("canvas"); + canvas.width = v.image.naturalWidth * v.scale; + canvas.height = v.image.naturalHeight * v.scale; + const ctx = canvas.getContext("2d")!; + ctx.drawImage(v.image, 0, 0, canvas.width, canvas.height); + canvas.toBlob((blob) => { + if (blob) { + downloadBlob(blob, `${v.name}.png`); + } else { + handler.popup.alert("Error", "Cannot export image"); + } + }, "image/png"); } else { - handler.popup.alert("Error", "Cannot export image"); + handler.popup.alert("Error", "Image not found"); + } + }} + > + + Image + + { + const v = handler.imagesRenders.find((e) => e.id === selected[0].id); + if (v) { + if (v.svg.scale !== v.scale || v.svg.dirty) { + handler.redrawSvg(v.id); + } + downloadBlob(new Blob([v.svg.data], { type: "image/svg+xml" }), `${v.name}.svg`); + } else { + handler.popup.alert("Error", "Image not found"); + } + }} + > + {" "} + + SVG + + + + + + + + { + handler.exportProject(); + }} + > + + Export + + { + handler.importProject(); + }} + > + + Import + + + { + { + if ( + await handler.popup.confirm( + "Clear", + "Are you sure you want to clear project? Any unsaved changes will be discarded", + ) + ) { + handler.clear(); } - }, "image/png"); - } else { - handler.popup.alert("Error", "Image not found"); - } - }}>Image - { - const v = handler.imagesRenders.find(e => e.id === selected[0].id); - if (v) { - if (v.svg.scale !== v.scale || v.svg.dirty) { - handler.redrawSvg(v.id); } - downloadBlob(new Blob([v.svg.data], { type: "image/svg+xml" }), `${v.name}.svg`); - } else { - handler.popup.alert("Error", "Image not found"); - } - - }}> SVG - - - - - - - { handler.exportProject(); }}>Export - { handler.importProject(); }} >Import - - { - { - if (await handler.popup.confirm("Clear", "Are you sure you want to clear project? Any unsaved changes will be discarded")) { - handler.clear(); - } - } - }} >Clear - - - >; + }} + > + + Clear + + + + > + ); } diff --git a/src/components/image-tools/paper-view.tsx b/src/components/image-tools/paper-view.tsx index 4a0bac6..b29313e 100644 --- a/src/components/image-tools/paper-view.tsx +++ b/src/components/image-tools/paper-view.tsx @@ -6,7 +6,6 @@ import { FreeTransform } from "./selection-box"; import type { GlobalSettings, GridType } from "../../interface"; import { SvgRenderer } from "./svg-view"; - const CanvasEl = styled.div` background: white; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); @@ -17,10 +16,9 @@ const Img = styled.img<{ $selected: boolean }>` position: absolute; display: block; user-select: none; - opacity: ${({ $selected }) => $selected ? 0.5 : 1}; + opacity: ${({ $selected }) => ($selected ? 0.5 : 1)}; `; - const Box = styled.div` position: absolute; top: 0; @@ -31,9 +29,9 @@ const Box = styled.div` function getGridLines(settings: GridType, width: number, height: number, strokeWidth: number, strokeWidthHalf: number) { if (settings === "none") return null; const configs: Record = { - "none": { + none: { horizontal: [1], - vertical: [1] + vertical: [1], }, "2x2": { vertical: [1 / 2], @@ -48,21 +46,25 @@ function getGridLines(settings: GridType, width: number, height: number, strokeW const config = configs[settings]; if (!config) return null; - return <> - {config.vertical.map((fraction, i) => ( - - ))} - {config.horizontal.map((fraction, i) => ( - - ))} - >; -}; + return ( + <> + {config.vertical.map((fraction, i) => ( + + ))} + {config.horizontal.map((fraction, i) => ( + + ))} + > + ); +} export function PaperView({ handler, settings }: CanvasHandlerProps & { settings: GlobalSettings }) { const { images } = useImages(handler); const dpi = settings.DPI; - const width = Math.round(((settings.landscape ? settings.paperHeight : settings.paperWidth) / MM_TO_INCH) * dpi) * settings.scale; - const height = Math.round(((settings.landscape ? settings.paperWidth : settings.paperHeight) / MM_TO_INCH) * dpi) * settings.scale; + const width = + Math.round(((settings.landscape ? settings.paperHeight : settings.paperWidth) / MM_TO_INCH) * dpi) * settings.scale; + const height = + Math.round(((settings.landscape ? settings.paperWidth : settings.paperHeight) / MM_TO_INCH) * dpi) * settings.scale; const renderGuides = () => { const strokeWidth = 1; @@ -70,37 +72,50 @@ export function PaperView({ handler, settings }: CanvasHandlerProps & { settings return getGridLines(settings.grid, width, height, strokeWidth, strokeWidthHalf); }; - return - {images.map(e => { - if (!e.visible) return; - const width = e.image.naturalWidth * e.scale * settings.scale; - const height = e.image.naturalHeight * e.scale * settings.scale; - const x = e.x * settings.scale; - const y = e.y * settings.scale; - return - - - {e.selected ? - { - const scale = Math.round(width / e.image.width / settings.scale * 100) / 100; - e.onPropsChange({ x: Math.round(x / settings.scale), y: Math.round(y / settings.scale), scale }); - }} /> : null} - ; - })} - {renderGuides()} - ; + return ( + + {images.map((e) => { + if (!e.visible) return; + const width = e.image.naturalWidth * e.scale * settings.scale; + const height = e.image.naturalHeight * e.scale * settings.scale; + const x = e.x * settings.scale; + const y = e.y * settings.scale; + return ( + + + + {e.selected ? ( + { + const scale = Math.round((width / e.image.width / settings.scale) * 100) / 100; + e.onPropsChange({ + x: Math.round(x / settings.scale), + y: Math.round(y / settings.scale), + scale, + }); + }} + /> + ) : null} + + ); + })} + {renderGuides()} + + ); } diff --git a/src/components/image-tools/selection-box.tsx b/src/components/image-tools/selection-box.tsx index d99cead..44de414 100644 --- a/src/components/image-tools/selection-box.tsx +++ b/src/components/image-tools/selection-box.tsx @@ -105,18 +105,14 @@ export type FreeTransformProps = { onTransformChange: (t: Transform) => void; }; -export function FreeTransform({ - transform, - scale = 1, - onTransformChange, -}: FreeTransformProps) { +export function FreeTransform({ transform, scale = 1, onTransformChange }: FreeTransformProps) { const dragRef = useRef({ type: "none" }); const apply = useCallback( (updater: (prev: Transform) => Transform) => { onTransformChange(updater(transform)); }, - [transform, onTransformChange] + [transform, onTransformChange], ); const onMovePointerDown = useCallback( @@ -131,7 +127,7 @@ export function FreeTransform({ originY: transform.y, }; }, - [transform.x, transform.y] + [transform.x, transform.y], ); const onResizePointerDown = useCallback( @@ -146,7 +142,7 @@ export function FreeTransform({ originTransform: { ...transform }, }; }, - [transform] + [transform], ); // const onRotatePointerDown = useCallback( @@ -184,8 +180,7 @@ export function FreeTransform({ } if (drag.type === "rotate") { - const currentAngle = - Math.atan2(e.clientY - drag.cy, e.clientX - drag.cx) * (180 / Math.PI); + const currentAngle = Math.atan2(e.clientY - drag.cy, e.clientX - drag.cx) * (180 / Math.PI); apply(() => ({ ...transform, rotation: drag.originRotation + (currentAngle - drag.startAngle), @@ -218,7 +213,7 @@ export function FreeTransform({ apply(() => ({ ...transform, x, y, width, height })); } }, - [transform, scale, apply] + [transform, scale, apply], ); const onPointerUp = useCallback(() => { @@ -235,7 +230,7 @@ export function FreeTransform({ top: `${y}px`, width: `${width}px`, height: `${height}px`, - transform: `rotate(${rotation}deg)` + transform: `rotate(${rotation}deg)`, }} onPointerDown={onMovePointerDown} onPointerMove={onPointerMove} @@ -266,4 +261,4 @@ export function FreeTransform({ ); -}; +} diff --git a/src/components/image-tools/svg-view.tsx b/src/components/image-tools/svg-view.tsx index e9ec1b5..93491fd 100644 --- a/src/components/image-tools/svg-view.tsx +++ b/src/components/image-tools/svg-view.tsx @@ -1,4 +1,3 @@ - import styled from "styled-components"; const Wrapper = styled.div` @@ -6,12 +5,11 @@ const Wrapper = styled.div` display: inline-block; line-height: 0; - - svg { - width: 100%; - height: 100%; - display: block; - } + svg { + width: 100%; + height: 100%; + display: block; + } `; type Props = { @@ -20,19 +18,20 @@ type Props = { y: number; width: number; height: number; -} - +}; export function SvgRenderer(props: Props) { const { svg, x, y, width, height } = props; return ( - ); -} \ No newline at end of file +} diff --git a/src/components/info-bar.tsx b/src/components/info-bar.tsx index 99b0fef..3496acc 100644 --- a/src/components/info-bar.tsx +++ b/src/components/info-bar.tsx @@ -30,7 +30,7 @@ export function InfoBar({ handler }: CanvasHandlerProps) { const [projectName, setProjectName] = useState(handler.projectName); useEffect(() => { - return handler.emitter.on("name", name => { + return handler.emitter.on("name", (name) => { setProjectName(name); }); }, [handler.projectName]); @@ -81,9 +81,13 @@ export function InfoBar({ handler }: CanvasHandlerProps) { - { - handler.promptSetName(); - }}> {projectName} + { + handler.promptSetName(); + }} + > + {" "} + {projectName} setDPI(false)}> diff --git a/src/components/layer-item.tsx b/src/components/layer-item.tsx index 31bf22a..780494f 100644 --- a/src/components/layer-item.tsx +++ b/src/components/layer-item.tsx @@ -31,7 +31,7 @@ const Icon = styled.span` export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { const selectLayer = (image: ImageEditor, ctrl: boolean) => { if (!ctrl) { - const selected = handler.selected.filter(e => e !== image.id); + const selected = handler.selected.filter((e) => e !== image.id); for (const sel of selected) { handler.setSelect(sel, false); } @@ -41,7 +41,7 @@ export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { return ( { + onClick={(ev) => { ev.stopPropagation(); ev.preventDefault(); selectLayer(image, ev.ctrlKey); @@ -49,7 +49,7 @@ export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { > { + onClick={(ev) => { ev.stopPropagation(); ev.preventDefault(); image.setVisible(!image.visible); @@ -60,7 +60,7 @@ export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { { + onClick={(ev) => { ev.stopPropagation(); ev.preventDefault(); selectLayer(image, ev.ctrlKey); @@ -71,7 +71,7 @@ export function LayerItem({ image, handler }: CanvasHandleImageEditorProps) { { + onClick={(ev) => { ev.stopPropagation(); ev.preventDefault(); image.setLock(!image.locked); diff --git a/src/components/layer-name.tsx b/src/components/layer-name.tsx index f31cd6f..9ae252d 100644 --- a/src/components/layer-name.tsx +++ b/src/components/layer-name.tsx @@ -44,13 +44,17 @@ export function NamePlate({ image }: ImageEditorProps) { }} /> ) : ( - { - if (!image.locked) { - ev.stopPropagation(); - ev.preventDefault(); - setEditing(true); - } - }}>{image.name} + { + if (!image.locked) { + ev.stopPropagation(); + ev.preventDefault(); + setEditing(true); + } + }} + > + {image.name} + )} ); diff --git a/src/components/layer-panel.tsx b/src/components/layer-panel.tsx index 6680ef1..dbf407b 100644 --- a/src/components/layer-panel.tsx +++ b/src/components/layer-panel.tsx @@ -22,15 +22,12 @@ const Version = styled.span` margin: 0 5px; `; - const ToolBar = styled.div` display: flex; flex-direction: row; border-top: 1px solid var(--secondary-color); `; - - const Wrapper = styled.div` width: 200px; padding: 24px; @@ -62,7 +59,7 @@ const Value = styled.div` `; const Link = styled.a` - color: #4AF626; + color: #4af626; text-decoration: none; &:hover { @@ -80,34 +77,37 @@ export function LayerPanel({ handler }: CanvasHandlerProps) { ))} + { + handler.popup.custom( + "About", + "", + + Vectrace {handler.VERSION}v - { - handler.popup.custom("About", "", - Vectrace {handler.VERSION}v + + Author + {handler.AUTHOR.name} + - - Author - {handler.AUTHOR.name} - + + Email + {handler.AUTHOR.email} + - - - Email - {handler.AUTHOR.email} - - - - URL - - - {handler.AUTHOR.url} - - - - ); - }}> + + URL + + + {handler.AUTHOR.url} + + + + , + ); + }} + > {handler.VERSION}v - ` - padding: 1px; - font-size: 1rem; - width: 80px; - border: 1px solid - ${({ $isValid }) => - $isValid === null - ? "#888" - : $isValid - ? "#ffffff" - : "#ef4444"}; - outline: none; - background-color: ${({ $isValid }) => - $isValid === null - ? "transparent" - : $isValid - ? "#000000" - : "#7e0000"}; - color: ${({ $isValid }) => - $isValid === null ? "inherit" : $isValid ? "#15803d" : "#b91c1c"}; - transition: border-color 0.2s ease, background-color 0.2s ease; + padding: 1px; + font-size: 1rem; + width: 80px; + border: 1px solid ${({ $isValid }) => ($isValid === null ? "#888" : $isValid ? "#ffffff" : "#ef4444")}; + outline: none; + background-color: ${({ $isValid }) => ($isValid === null ? "transparent" : $isValid ? "#000000" : "#7e0000")}; + color: ${({ $isValid }) => ($isValid === null ? "inherit" : $isValid ? "#15803d" : "#b91c1c")}; + transition: + border-color 0.2s ease, + background-color 0.2s ease; - &:focus { - box-shadow: 0 0 0 3px - ${({ $isValid }) => - $isValid === null - ? "#88888844" - : $isValid - ? "#22c55e44" - : "#ef444444"}; - } + &:focus { + box-shadow: 0 0 0 3px ${({ $isValid }) => ($isValid === null ? "#88888844" : $isValid ? "#22c55e44" : "#ef444444")}; + } `; - export interface NumberInputProps { value?: number; id?: string; @@ -43,18 +26,9 @@ export interface NumberInputProps { placeholder?: string; } - -export function NumberInput({ - value, - onChange, - id, - placeholder = "Enter a number...", -}: NumberInputProps) { - const [raw, setRaw] = useState(() => value !== undefined ? String(value) : "" - ); - const [isValid, setIsValid] = useState( - value !== undefined ? true : null - ); +export function NumberInput({ value, onChange, id, placeholder = "Enter a number..." }: NumberInputProps) { + const [raw, setRaw] = useState(() => (value !== undefined ? String(value) : "")); + const [isValid, setIsValid] = useState(value !== undefined ? true : null); const isValidNumber = (val: string): boolean => { if (val.trim() === "") return false; @@ -68,7 +42,6 @@ export function NumberInput({ setRaw(val); if (val.trim() === "") { - setIsValid(null); return; } @@ -88,7 +61,6 @@ export function NumberInput({ } }; - const executeEvaluate = () => { if (canEvaluateMathExpression(raw)) { const value = calculateMathExpression(raw); @@ -116,7 +88,7 @@ export function NumberInput({ inputMode="numeric" value={raw} onBlur={executeEvaluate} - onKeyUp={ev => { + onKeyUp={(ev) => { switch (ev.key) { case "Enter": executeEvaluate(); @@ -139,5 +111,4 @@ export function NumberInput({ id={id} /> ); -}; - +} diff --git a/src/components/numeric-labeld-input.tsx b/src/components/numeric-labeld-input.tsx index 8006fbd..ef7efa5 100644 --- a/src/components/numeric-labeld-input.tsx +++ b/src/components/numeric-labeld-input.tsx @@ -5,38 +5,23 @@ const OptionDiv = styled.div` margin: 2px; `; -export function NumericInputWithLabel({ - value, - onChange, - placeholder, - name, - id - -}: NumberInputProps & { name: string }) { - return - {name}: - - ; -} -export function NumericInputWithLabelTable({ - value, - onChange, - placeholder, - name, - id - -}: NumberInputProps & { name: string }) { - return - +export function NumericInputWithLabel({ value, onChange, placeholder, name, id }: NumberInputProps & { name: string }) { + return ( + {name}: - - - - - - ; + + + ); +} +export function NumericInputWithLabelTable({ value, onChange, placeholder, name, id }: NumberInputProps & { name: string }) { + return ( + + + {name}: + + + + + + ); } diff --git a/src/components/paper-guide-selector.tsx b/src/components/paper-guide-selector.tsx index 937a90d..ad4ea67 100644 --- a/src/components/paper-guide-selector.tsx +++ b/src/components/paper-guide-selector.tsx @@ -17,16 +17,20 @@ const Wrapper = styled.div` } `; - export function PaperGuideSelector({ setSettings, settings }: UseSettings) { - return - {GRID.map((e, i) => { - setSettings({ grid: e }); - }} - >{e} - )} - ; + return ( + + {GRID.map((e, i) => ( + { + setSettings({ grid: e }); + }} + > + {e} + + ))} + + ); } diff --git a/src/components/paper-orentation.tsx b/src/components/paper-orentation.tsx index ce01787..d3646c4 100644 --- a/src/components/paper-orentation.tsx +++ b/src/components/paper-orentation.tsx @@ -3,11 +3,13 @@ import { RibbonButton } from "./buttons/button-ribbon"; import type { UseSettings } from "../use/use-settings"; export function PaperOrientation({ setSettings, settings }: UseSettings) { - - return } - onClick={() => { - setSettings({ landscape: !settings.landscape }); - }} - name={settings.landscape ? "Landscape" : "Portrait"} />; + return ( + } + onClick={() => { + setSettings({ landscape: !settings.landscape }); + }} + name={settings.landscape ? "Landscape" : "Portrait"} + /> + ); } diff --git a/src/components/paper-size-selector.tsx b/src/components/paper-size-selector.tsx index 168302f..69ef3dc 100644 --- a/src/components/paper-size-selector.tsx +++ b/src/components/paper-size-selector.tsx @@ -1,17 +1,27 @@ import styled from "styled-components"; import { - A0_HEIGHT, A0_WIDTH, - A1_HEIGHT, A1_WIDTH, - A2_HEIGHT, A2_WIDTH, - A3_HEIGHT, A3_WIDTH, - A4_HEIGHT, A4_WIDTH, - A5_HEIGHT, A5_WIDTH, - A6_HEIGHT, A6_WIDTH, - LETTER_HEIGHT, LETTER_WIDTH, - LEGAL_HEIGHT, LEGAL_WIDTH, - TABLOID_HEIGHT, TABLOID_WIDTH, - LEDGER_HEIGHT, LEDGER_WIDTH, - + A0_HEIGHT, + A0_WIDTH, + A1_HEIGHT, + A1_WIDTH, + A2_HEIGHT, + A2_WIDTH, + A3_HEIGHT, + A3_WIDTH, + A4_HEIGHT, + A4_WIDTH, + A5_HEIGHT, + A5_WIDTH, + A6_HEIGHT, + A6_WIDTH, + LETTER_HEIGHT, + LETTER_WIDTH, + LEGAL_HEIGHT, + LEGAL_WIDTH, + TABLOID_HEIGHT, + TABLOID_WIDTH, + LEDGER_HEIGHT, + LEDGER_WIDTH, } from "../constants"; import { Button } from "../styles"; import type { UseSettings } from "../use/use-settings"; @@ -52,38 +62,44 @@ const papers = [ createPaper("Ledger", LEDGER_WIDTH, LEDGER_HEIGHT), ]; - export function PaperSizeSelector({ setSettings, settings, popup }: UseSettings & { popup: Popup }) { - const custom = papers.find(e => settings.paperWidth === e.width && settings.paperHeight === e.height); - return - {papers.map((e, i) => { - setSettings({ paperWidth: e.width, paperHeight: e.height }); - }} - >{e.name} - )} - { - const result = await popup.prompt( - "Paper size", - `Enter paper size like x (${A4_WIDTH}x${A4_HEIGHT})`, - `${settings.paperWidth}x${settings.paperHeight}` - ); - if (result) { - const [width, height] = result.split("x").map(e => Math.max(parseInt(e, 10), 1)); - if (isNaN(width) || isNaN(height)) { - popup.alert("Paper size", "Invalid size"); - } else { - setSettings({ - paperWidth: width, - paperHeight: height - }); + const custom = papers.find((e) => settings.paperWidth === e.width && settings.paperHeight === e.height); + return ( + + {papers.map((e, i) => ( + { + setSettings({ paperWidth: e.width, paperHeight: e.height }); + }} + > + {e.name} + + ))} + { + const result = await popup.prompt( + "Paper size", + `Enter paper size like x (${A4_WIDTH}x${A4_HEIGHT})`, + `${settings.paperWidth}x${settings.paperHeight}`, + ); + if (result) { + const [width, height] = result.split("x").map((e) => Math.max(parseInt(e, 10), 1)); + if (isNaN(width) || isNaN(height)) { + popup.alert("Paper size", "Invalid size"); + } else { + setSettings({ + paperWidth: width, + paperHeight: height, + }); + } } - } - } - }>Custom - ; + }} + > + Custom + + + ); } diff --git a/src/components/svg-tracer-options.tsx b/src/components/svg-tracer-options.tsx index 89b37e0..e056609 100644 --- a/src/components/svg-tracer-options.tsx +++ b/src/components/svg-tracer-options.tsx @@ -18,13 +18,13 @@ const Table = styled.table` const WrapperColumn = styled.div` display: flex; flex-direction: column; - `; +`; const WrapperRow = styled.div` display: flex; flex-direction: row; height: 100px; - `; +`; const LayerPanel = styled.div` display: flex; @@ -35,7 +35,7 @@ const LayerPanel = styled.div` `; const ToggleButton = styled.button<{ $enabled: boolean }>` - background-color: ${({ $enabled }) => $enabled ? "var(--secondary-color)" : "var(--primary-color)"}; + background-color: ${({ $enabled }) => ($enabled ? "var(--secondary-color)" : "var(--primary-color)")}; color: white; border: 1px solid white; &:hover { @@ -44,19 +44,17 @@ const ToggleButton = styled.button<{ $enabled: boolean }>` } `; - - -export function SvgTracerOptions({ selected, handler }: { selected: ImageEditor[], handler: VectraceHandler }) { +export function SvgTracerOptions({ selected, handler }: { selected: ImageEditor[]; handler: VectraceHandler }) { const [options, setOptions] = useState(selected[0]?.tracerOptions ?? {}); const [updated, setUpdated] = useState(false); useEffect(() => { setOptions(selected[0]?.tracerOptions ?? {}); setUpdated(true); - }, [selected.map(e => `${e.id}${JSON.stringify(e.svgData.options)}`).join("")]); + }, [selected.map((e) => `${e.id}${JSON.stringify(e.svgData.options)}`).join("")]); const redraw = () => { - selected.forEach(e => { + selected.forEach((e) => { e.setTracerSvg({ ...options }); }); setUpdated(true); @@ -67,69 +65,83 @@ export function SvgTracerOptions({ selected, handler }: { selected: ImageEditor[ setUpdated(false); }; - return - { - const value = await handler.popup.prompt( - "Transparency threshold", - "Enter transparency threshold between 0-255", - (options.transparencyThreshold ?? TRANSPARENCY_THRESHOLD_DEFAULT).toString()); - if (value) { - const int = parseInt(value, 10); - if (!isNaN(int)) { - setValue("transparencyThreshold", clamp(int, 0, 255)); + return ( + + { + const value = await handler.popup.prompt( + "Transparency threshold", + "Enter transparency threshold between 0-255", + (options.transparencyThreshold ?? TRANSPARENCY_THRESHOLD_DEFAULT).toString(), + ); + if (value) { + const int = parseInt(value, 10); + if (!isNaN(int)) { + setValue("transparencyThreshold", clamp(int, 0, 255)); + } } - } - }} - onChange={value => { - setValue("transparencyThreshold", value); - }} /> - - { redraw(); }}> - - - - setValue("ltres", value)} - /> - setValue("qtres", value)} - /> - setValue("pathomit", value)} - /> - setValue("roundcoords", value)} - /> - - - - {(options.allowedPaths || []).map((e, i) => { - return { - const array = options?.allowedPaths ? [...options.allowedPaths] : []; - array[i] = !array[i]; - setValue("allowedPaths", array); - }} - > - {`${i + 1} Layer`} - ; - })} - - - - ; -} \ No newline at end of file + }} + onChange={(value) => { + setValue("transparencyThreshold", value); + }} + /> + + { + redraw(); + }} + > + + + + + + setValue("ltres", value)} + /> + setValue("qtres", value)} + /> + setValue("pathomit", value)} + /> + setValue("roundcoords", value)} + /> + + + + {(options.allowedPaths || []).map((e, i) => { + return ( + { + const array = options?.allowedPaths ? [...options.allowedPaths] : []; + array[i] = !array[i]; + setValue("allowedPaths", array); + }} + > + {`${i + 1} Layer`} + + ); + })} + + + + + ); +} diff --git a/src/components/vertical-slider.tsx b/src/components/vertical-slider.tsx index 4ae88d9..547eab6 100644 --- a/src/components/vertical-slider.tsx +++ b/src/components/vertical-slider.tsx @@ -1,15 +1,14 @@ import React, { useState } from "react"; import styled from "styled-components"; - const SliderWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 120px; - width: 40px; - gap: 8px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 120px; + width: 40px; + gap: 8px; `; interface SliderProps { @@ -20,73 +19,73 @@ interface SliderProps { onChange?: (value: number) => void; } - interface TrackTransientProps { $fillPercent: number; } -const Track = styled.input.attrs({ type: "range" }) ` - appearance: none; - -webkit-appearance: none; - writing-mode: vertical-lr; - direction: rtl; - width: 6px; - height: 100%; - background: linear-gradient( - to top, - white ${({ $fillPercent }) => $fillPercent}%, - var(--secondary-color) ${({ $fillPercent }) => $fillPercent}% - ); - border-radius: 0; - outline: none; - cursor: pointer; - transition: background 0.15s ease; - - &::-webkit-slider-thumb { - -webkit-appearance: none; +const Track = styled.input.attrs({ type: "range" })` appearance: none; - width: 22px; - height: 22px; + -webkit-appearance: none; + writing-mode: vertical-lr; + direction: rtl; + width: 6px; + height: 100%; + background: linear-gradient( + to top, + white ${({ $fillPercent }) => $fillPercent}%, + var(--secondary-color) ${({ $fillPercent }) => $fillPercent}% + ); border-radius: 0; - background: #ffffff; - border: 3px solid var(--secondary-color); - box-shadow: 0 0 6px rgba(255, 68, 68, 0.6); - cursor: grab; - transition: transform 0.1s ease, box-shadow 0.1s ease; + outline: none; + cursor: pointer; + transition: background 0.15s ease; - &:active { - cursor: grabbing; - transform: scale(1.2); - box-shadow: 0 0 12px rgba(255, 68, 68, 0.9); + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 22px; + height: 22px; + border-radius: 0; + background: #ffffff; + border: 3px solid var(--secondary-color); + box-shadow: 0 0 6px rgba(255, 68, 68, 0.6); + cursor: grab; + transition: + transform 0.1s ease, + box-shadow 0.1s ease; + + &:active { + cursor: grabbing; + transform: scale(1.2); + box-shadow: 0 0 12px rgba(255, 68, 68, 0.9); + } } - } - &::-moz-range-thumb { - width: 22px; - height: 22px; - border-radius: 0; - background: #ffffff; - border: 3px solid #ff4444; - box-shadow: 0 0 6px rgba(255, 68, 68, 0.6); - cursor: grab; - transition: transform 0.1s ease; + &::-moz-range-thumb { + width: 22px; + height: 22px; + border-radius: 0; + background: #ffffff; + border: 3px solid #ff4444; + box-shadow: 0 0 6px rgba(255, 68, 68, 0.6); + cursor: grab; + transition: transform 0.1s ease; - &:active { - cursor: grabbing; - transform: scale(1.2); + &:active { + cursor: grabbing; + transform: scale(1.2); + } } - } - &::-moz-range-track { - background: transparent; - } + &::-moz-range-track { + background: transparent; + } `; - const ValueLabel = styled.span` - font-size: 13px; - color: white; - letter-spacing: 0.05em; + font-size: 13px; + color: white; + letter-spacing: 0.05em; `; interface SliderProps { @@ -98,17 +97,8 @@ interface SliderProps { onChange?: (value: number) => void; } -export function VerticalSlider({ - min = 0, - max = 100, - step = 1, - onLabelClick, - value: controlledValue, - onChange, -}: SliderProps) { - const [internalValue, setInternalValue] = useState( - controlledValue ?? Math.floor((max - min) / 2) - ); +export function VerticalSlider({ min = 0, max = 100, step = 1, onLabelClick, value: controlledValue, onChange }: SliderProps) { + const [internalValue, setInternalValue] = useState(controlledValue ?? Math.floor((max - min) / 2)); const value = controlledValue ?? internalValue; const fillPercent = ((value - min) / (max - min)) * 100; @@ -121,17 +111,14 @@ export function VerticalSlider({ return ( - { - onLabelClick?.(); - }}>{value} - + { + onLabelClick?.(); + }} + > + {value} + + ); -}; +}