From 151b50c300606af3e90b84fef996b9bfb2d3ddee Mon Sep 17 00:00:00 2001 From: Kyle Zheng Date: Sun, 13 Oct 2024 04:03:13 -0400 Subject: [PATCH] chore: mobile drag n drop + increase png size + remove js media query --- src/components/SplitButton.tsx | 45 +++---- src/components/editor/ParamsEditor.tsx | 2 +- src/components/editor/QrEditor.tsx | 2 +- src/components/preview/QrPreview.tsx | 166 ++++++++++++------------- src/routes/index.tsx | 40 ++---- 5 files changed, 115 insertions(+), 140 deletions(-) diff --git a/src/components/SplitButton.tsx b/src/components/SplitButton.tsx index dc1a593..89f1dc8 100644 --- a/src/components/SplitButton.tsx +++ b/src/components/SplitButton.tsx @@ -3,18 +3,17 @@ import { NumberField } from "@kobalte/core/number-field"; import { Popover } from "@kobalte/core/popover"; import ChevronDown from "lucide-solid/icons/chevron-down"; import Download from "lucide-solid/icons/download"; -import { createSignal, Show } from "solid-js"; +import { createSignal } from "solid-js"; import { FillButton } from "./Button"; type Props = { onPng: (resizeWidth, resizeHeight) => void; onSvg: () => void; - compact: boolean; disabled: boolean; }; export function SplitButton(props: Props) { - const [customWidth, setCustomWidth] = createSignal(1000); - const [customHeight, setCustomHeight] = createSignal(1000); + const [customWidth, setCustomWidth] = createSignal(2000); + const [customHeight, setCustomHeight] = createSignal(2000); const onPng = (resizeWidth, resizeHeight) => { props.onPng(resizeWidth, resizeHeight); @@ -33,7 +32,8 @@ export function SplitButton(props: Props) { disabled={props.disabled} > - {props.compact ? "Download" : "PNG"} + Download +
- -
Select size
- onPng(300, 300)} - > - 300x300 - - onPng(500, 500)} - > - 500x500 - - - } - > + +
Alternate file type
SVG - +

Custom size
diff --git a/src/components/editor/ParamsEditor.tsx b/src/components/editor/ParamsEditor.tsx index 73b60fe..e1deb7d 100644 --- a/src/components/editor/ParamsEditor.tsx +++ b/src/components/editor/ParamsEditor.tsx @@ -125,7 +125,7 @@ function ArrayParam({ label, other }) { value={v()} setValue={(v: any) => setParams(label, i, v)} /> -
+
diff --git a/src/components/editor/QrEditor.tsx b/src/components/editor/QrEditor.tsx index 3eb7b7f..a9facde 100644 --- a/src/components/editor/QrEditor.tsx +++ b/src/components/editor/QrEditor.tsx @@ -498,7 +498,7 @@ function Preview(props: PreviewProps) { >
["classList"]; - compact: boolean; ref: HTMLDivElement; }; @@ -27,7 +26,7 @@ export function QrPreview(props: Props) { return (
-
+
- - - - + +
); } function RenderedQrCode() { - const { render, error, svgParentRefs, addSvgParentRef, canvasRefs, addCanvasRef } = useRenderContext(); + const { + render, + error, + svgParentRefs, + addSvgParentRef, + canvasRefs, + addCanvasRef, + } = useRenderContext(); - let i = svgParentRefs.length - let j = canvasRefs.length + let i = svgParentRefs.length; + let j = canvasRefs.length; onCleanup(() => { - svgParentRefs.splice(i, 1) - canvasRefs.splice(j, 1) - }) + svgParentRefs.splice(i, 1); + canvasRefs.splice(j, 1); + }); return ( <> @@ -107,11 +111,14 @@ function RenderedQrCode() { ); } -function Metadata() { - const { output } = useQrContext(); +type MetadataProps = { + class?: string; +}; +function Metadata(props: MetadataProps) { + const { output } = useQrContext(); return ( -
+
QR Metadata
@@ -146,20 +153,15 @@ function Metadata() { ); } -type DownloadProps = { - title?: boolean; - compact: boolean; -}; - -function DownloadButtons(props: DownloadProps) { +function DownloadButtons() { const { output } = useQrContext(); const { render, svgParentRefs, canvasRefs } = useRenderContext(); - const filename = () => output().qr!.text.slice(0, 32); + const disabled = () => output().state !== QrState.Ready; const pngBlob = async (resizeWidth, resizeHeight) => { - // 10px per module assuming 2 module margin - const minWidth = (output().qr!.version * 4 + 17 + 4) * 10; + // roughly 20px per module, ranges from 500 to 3620px + const minWidth = (output().qr!.version * 4 + 17 + 4) * 20; let outCanvas: HTMLCanvasElement; if (render()?.type === "canvas") { @@ -217,17 +219,12 @@ function DownloadButtons(props: DownloadProps) { URL.revokeObjectURL(url); }; - const disabled = () => output().state !== QrState.Ready; - return (
- -
Downloads
-
-
+
Downloads
+
{ try { const blob = await pngBlob(resizeWidth, resizeHeight); @@ -243,9 +240,9 @@ function DownloadButtons(props: DownloadProps) { }} onSvg={downloadSvg} /> - + @@ -253,60 +250,59 @@ function DownloadButtons(props: DownloadProps) { SVG - - { + let blob; + try { + blob = await pngBlob(0, 0); + if (blob == null) throw "toBlob returned null"; + } catch (e) { + toastError( + "Failed to create image", + typeof e === "string" ? e : "pngBlob failed" + ); + return; + } + try { + const shareData = { + files: [ + new File([blob], `${filename()}.png`, { + type: "image/png", + }), + ], + }; + if (!navigator.canShare(shareData)) { + throw new Error(); + } + navigator.share(shareData); + } catch (e) { + console.log(e); + toastError( + "Native sharing failed", + "File sharing not supported by browser" + ); + } + }} + > + + + + { - let blob; - try { - blob = await pngBlob(0, 0); - if (blob == null) throw "toBlob returned null"; - } catch (e) { - toastError("Failed to create image", e as string); - return; - } - try { - const shareData = { - files: [ - new File([blob], `${filename()}.png`, { - type: "image/png", - }), - ], - }; - if (!navigator.canShare(shareData)) { - throw new Error(); - } - navigator.share(shareData); - } catch (e) { - console.log(e); - toastError( - "Native sharing failed", - "File sharing not supported by browser" - ); - } - }} + title="QR Metadata" > - - - - - - - - - - - - - - - + + + + + + + +
); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 6f30c01..df7349c 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,11 +1,10 @@ -import { isServer, Portal } from "solid-js/web"; - +import { createSignal, onCleanup, onMount } from "solid-js"; +import { Portal } from "solid-js/web"; import { Editor } from "~/components/editor/QrEditor"; import { ErrorToasts } from "~/components/ErrorToasts"; import { QrPreview } from "~/components/preview/QrPreview"; -import { RenderContextProvider } from "~/lib/RenderContext"; -import { createSignal, onCleanup, onMount } from "solid-js"; import { QrContextProvider } from "~/lib/QrContext"; +import { RenderContextProvider } from "~/lib/RenderContext"; export default function Home() { return ( @@ -18,21 +17,13 @@ export default function Home() { } function Temp() { - // tracking mediaquery in js b/c rendering step draws to all mounted elements - const [desktop, setDesktop] = createSignal(false); - onMount(() => { - const mql = window.matchMedia("(min-width: 768px)"); - const callback = (e) => setDesktop(e.matches); - mql.addEventListener("change", callback); - setDesktop(mql.matches); - const viewport = window.visualViewport!; let prevHeight = viewport.height; const detectMobileKeyboard = () => { const prev = prevHeight; prevHeight = viewport.height; - if (desktop() || !textFocused()) return; + if (!textFocused()) return; if (viewport.height === prev) return; if (viewport.height > prev) { // closing mobile keyboard @@ -40,9 +31,7 @@ function Temp() { } }; viewport.addEventListener("resize", detectMobileKeyboard); - onCleanup(() => { - mql.removeEventListener("change", callback); window.removeEventListener("resize", detectMobileKeyboard); }); }); @@ -55,12 +44,12 @@ function Temp() { setTextFocused(true); }; const onBlur = () => { - if (!desktop()) { - // firefox scrolls input behind sticky QrPreview - // adding/removing sticky + this scroll + animation - // gives roughly equivalent ux as chrome default - const before = `${qrPreview.getBoundingClientRect().top}px`; - qrPreview.animate([{ top: before }, { top: 0 }], { + // firefox scrolls input behind sticky QrPreview + // adding/removing sticky + this scroll + animation + // gives roughly equivalent ux as chrome default + const before = qrPreview.getBoundingClientRect().top; + if (before !== 0) { + qrPreview.animate([{ top: `${before}px` }, { top: 0 }], { // slow animation to prevent bounce duration: 1000, easing: "ease-out", @@ -86,13 +75,10 @@ function Temp() {