links-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 9e2af813edac
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 3151231032497ed786b88a0a
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/Qr.tsx
// A link's QR code: live preview in a chosen color, PNG and SVG downloads.
import { useMemo, useState } from "react";
import { qrMatrix, qrSvg, QR_COLORS, scansWell, type QrMatrix } from "../functions/lib/qr";
import { shortUrl } from "../functions/lib/links";
import { download, toast } from "./ui";

/** About 2,000 pixels square: sharp enough for print. */
function png(m: QrMatrix, color: string): Promise<Blob> {
  const n = m.length + 8;
  const px = Math.ceil(2000 / n);
  const canvas = document.createElement("canvas");
  canvas.width = canvas.height = n * px;
  const g = canvas.getContext("2d")!;
  g.fillStyle = "#ffffff";
  g.fillRect(0, 0, canvas.width, canvas.height);
  g.fillStyle = color;
  m.forEach((row, r) => row.forEach((dark, c) => { if (dark) g.fillRect((c + 4) * px, (r + 4) * px, px, px); }));
  return new Promise((resolve, reject) => canvas.toBlob((b) => (b ? resolve(b) : reject(new Error("Could not make the image."))), "image/png"));
}

function remembered(code: string): string {
  try { return localStorage.getItem(`links:color:${code}`) || QR_COLORS[0].hex; } catch { return QR_COLORS[0].hex; }
}

export function QrPanel({ code }: { code: string }) {
  const [color, setColor] = useState(() => remembered(code));
  const matrix = useMemo(() => qrMatrix(shortUrl(code)), [code]);
  const svg = useMemo(() => qrSvg(matrix, color), [matrix, color]);
  const custom = !QR_COLORS.some((c) => c.hex === color);
  const readable = scansWell(color);
  const pick = (hex: string) => {
    setColor(hex);
    try { localStorage.setItem(`links:color:${code}`, hex); } catch { /* the color is only remembered when storage works */ }
  };
  const savePng = async () => {
    try { download(await png(matrix, color), `links-${code}.png`); } catch (err) { toast((err as Error).message); }
  };

  return (
    <div className="qr">
      <div className="qr-preview" role="img" aria-label={`QR code for ${shortUrl(code)}`} dangerouslySetInnerHTML={{ __html: svg }} />
      <div className="qr-side">
        <fieldset className="field qr-colors">
          <legend className="label">Color</legend>
          <div className="swatches">
            {QR_COLORS.map((c) => (
              <label key={c.hex} className="swatch" style={{ background: c.hex }} title={c.name}>
                <input type="radio" name={`qr-color-${code}`} value={c.hex} checked={color === c.hex} onChange={() => pick(c.hex)} aria-label={c.name} />
              </label>
            ))}
            <label className={`swatch swatch-custom${custom ? " is-on" : ""}`} title="Your own color" style={custom ? { background: color } : undefined}>
              <input type="color" value={color} onChange={(e) => pick(e.target.value)} aria-label="Pick your own color" />
            </label>
          </div>
          {!readable && <small className="field-error">This color is too light for phones to read. Pick a darker one to download.</small>}
        </fieldset>
        <div className="qr-downloads">
          <button className="btn btn-sm" type="button" onClick={savePng} disabled={!readable}>Download PNG</button>
          <button className="btn btn-sm btn-line" type="button" disabled={!readable} onClick={() => download(new Blob([svg], { type: "image/svg+xml" }), `links-${code}.svg`)}>Download SVG</button>
        </div>
      </div>
    </div>
  );
}

Change history

Quality pass: clicks per day, CSV export, links on other devices
9e2af813ed · 6 hours ago
Bounded source sync
29eee7dafa · 1 day ago
Bounded source sync
c5e634046a · 1 day ago
Bounded source sync
12abf93e7e · 1 day ago
Bounded source sync
d0d6a654b1 · 1 day ago
Bounded source sync
e03049f905 · 1 day ago
Bounded source sync
eb5c793bfa · 1 day ago
Bounded source sync
5dd8ec26e1 · 1 day ago