kopia lustrzana https://github.com/zhengkyl/qrframe
run user code yippie
rodzic
7f7873b26f
commit
f7f5b094be
|
@ -3,21 +3,33 @@ import { defineConfig } from "@solidjs/start/config";
|
|||
import UnoCSS from "unocss/vite";
|
||||
import wasmpack from "vite-plugin-wasm-pack";
|
||||
|
||||
// b/c I'm using vite() instead of static config object
|
||||
// getting a new plugin each time vite() runs messes up unocss styles on the `body`
|
||||
const SharedUnoCss = UnoCSS();
|
||||
|
||||
export default defineConfig({
|
||||
server: { preset: "vercel" },
|
||||
ssr: true,
|
||||
vite: {
|
||||
plugins: [UnoCSS(), wasmpack(["./fuqr"])],
|
||||
resolve: {
|
||||
alias: {
|
||||
// https://christopher.engineering/en/blog/lucide-icons-with-vite-dev-server/
|
||||
"lucide-solid/icons": fileURLToPath(
|
||||
new URL(
|
||||
"./node_modules/lucide-solid/dist/source/icons",
|
||||
import.meta.url
|
||||
)
|
||||
),
|
||||
vite({ router }) {
|
||||
|
||||
return {
|
||||
plugins:
|
||||
// https://github.com/nksaraf/vinxi/issues/262
|
||||
// wasmpack copies pkg to node_modules asynchronously so triggering buildStart 3 times causes errors
|
||||
router === "client"
|
||||
? [SharedUnoCss, wasmpack(["./fuqr"])]
|
||||
: [SharedUnoCss],
|
||||
resolve: {
|
||||
alias: {
|
||||
// https://christopher.engineering/en/blog/lucide-icons-with-vite-dev-server/
|
||||
"lucide-solid/icons": fileURLToPath(
|
||||
new URL(
|
||||
"./node_modules/lucide-solid/dist/source/icons",
|
||||
import.meta.url
|
||||
)
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,27 +15,11 @@ export function get_matrix(input: string, qr_options: QrOptions): Matrix;
|
|||
export function get_svg(input: string, qr_options: QrOptions, svg_options: SvgOptions): SvgResult;
|
||||
/**
|
||||
*/
|
||||
export enum Module {
|
||||
DataOFF = 0,
|
||||
DataON = 1,
|
||||
FinderOFF = 2,
|
||||
FinderON = 3,
|
||||
AlignmentOFF = 4,
|
||||
AlignmentON = 5,
|
||||
TimingOFF = 6,
|
||||
TimingON = 7,
|
||||
FormatOFF = 8,
|
||||
FormatON = 9,
|
||||
VersionOFF = 10,
|
||||
VersionON = 11,
|
||||
Unset = 12,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export enum Toggle {
|
||||
Background = 0,
|
||||
BackgroundPixels = 1,
|
||||
ForegroundPixels = 2,
|
||||
export enum ECL {
|
||||
Low = 0,
|
||||
Medium = 1,
|
||||
Quartile = 2,
|
||||
High = 3,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
|
@ -51,14 +35,6 @@ export enum Mask {
|
|||
}
|
||||
/**
|
||||
*/
|
||||
export enum ECL {
|
||||
Low = 0,
|
||||
Medium = 1,
|
||||
Quartile = 2,
|
||||
High = 3,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export enum Mode {
|
||||
Numeric = 0,
|
||||
Alphanumeric = 1,
|
||||
|
@ -66,12 +42,36 @@ export enum Mode {
|
|||
}
|
||||
/**
|
||||
*/
|
||||
export enum Toggle {
|
||||
Background = 0,
|
||||
BackgroundPixels = 1,
|
||||
ForegroundPixels = 2,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export enum QrError {
|
||||
InvalidEncoding = 0,
|
||||
ExceedsMaxCapacity = 1,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export enum Module {
|
||||
DataOFF = 0,
|
||||
DataON = 1,
|
||||
FinderOFF = 2,
|
||||
FinderON = 3,
|
||||
AlignmentOFF = 4,
|
||||
AlignmentON = 5,
|
||||
TimingOFF = 6,
|
||||
TimingON = 7,
|
||||
FormatOFF = 8,
|
||||
FormatON = 9,
|
||||
VersionOFF = 10,
|
||||
VersionON = 11,
|
||||
Unset = 12,
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export class Margin {
|
||||
free(): void;
|
||||
/**
|
||||
|
@ -193,11 +193,6 @@ export class SvgOptions {
|
|||
*/
|
||||
constructor();
|
||||
/**
|
||||
* @param {number} margin
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
margin(margin: number): SvgOptions;
|
||||
/**
|
||||
* @param {number} unit
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
|
@ -213,20 +208,20 @@ export class SvgOptions {
|
|||
*/
|
||||
background(background: string): SvgOptions;
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_x_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_x_matrix(scale_matrix: Uint8Array): SvgOptions;
|
||||
scale_x_matrix(scale_x_matrix?: Uint8Array): SvgOptions;
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_y_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_y_matrix(scale_matrix: Uint8Array): SvgOptions;
|
||||
scale_y_matrix(scale_y_matrix?: Uint8Array): SvgOptions;
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_matrix(scale_matrix: Uint8Array): SvgOptions;
|
||||
scale_matrix(scale_matrix?: Uint8Array): SvgOptions;
|
||||
/**
|
||||
* @param {Toggle} toggle
|
||||
* @returns {SvgOptions}
|
||||
|
@ -301,6 +296,10 @@ export interface InitOutput {
|
|||
readonly __wbg_set_matrix_mask: (a: number, b: number) => void;
|
||||
readonly matrix_width: (a: number) => number;
|
||||
readonly matrix_height: (a: number) => number;
|
||||
readonly __wbg_version_free: (a: number) => void;
|
||||
readonly __wbg_get_version_0: (a: number) => number;
|
||||
readonly __wbg_set_version_0: (a: number, b: number) => void;
|
||||
readonly version_new: (a: number) => number;
|
||||
readonly __wbg_qroptions_free: (a: number) => void;
|
||||
readonly qroptions_new: () => number;
|
||||
readonly qroptions_min_version: (a: number, b: number) => number;
|
||||
|
@ -310,7 +309,6 @@ export interface InitOutput {
|
|||
readonly qroptions_margin: (a: number, b: number) => number;
|
||||
readonly __wbg_svgoptions_free: (a: number) => void;
|
||||
readonly svgoptions_new: () => number;
|
||||
readonly svgoptions_margin: (a: number, b: number) => number;
|
||||
readonly svgoptions_unit: (a: number, b: number) => number;
|
||||
readonly svgoptions_foreground: (a: number, b: number, c: number) => number;
|
||||
readonly svgoptions_background: (a: number, b: number, c: number) => number;
|
||||
|
@ -331,10 +329,6 @@ export interface InitOutput {
|
|||
readonly __wbg_set_svgresult_mask: (a: number, b: number) => void;
|
||||
readonly get_matrix: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly get_svg: (a: number, b: number, c: number, d: number, e: number) => void;
|
||||
readonly __wbg_version_free: (a: number) => void;
|
||||
readonly __wbg_get_version_0: (a: number) => number;
|
||||
readonly __wbg_set_version_0: (a: number, b: number) => void;
|
||||
readonly version_new: (a: number) => number;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
|
|
|
@ -223,22 +223,22 @@ export function get_svg(input, qr_options, svg_options) {
|
|||
|
||||
/**
|
||||
*/
|
||||
export const Module = Object.freeze({ DataOFF:0,"0":"DataOFF",DataON:1,"1":"DataON",FinderOFF:2,"2":"FinderOFF",FinderON:3,"3":"FinderON",AlignmentOFF:4,"4":"AlignmentOFF",AlignmentON:5,"5":"AlignmentON",TimingOFF:6,"6":"TimingOFF",TimingON:7,"7":"TimingON",FormatOFF:8,"8":"FormatOFF",FormatON:9,"9":"FormatON",VersionOFF:10,"10":"VersionOFF",VersionON:11,"11":"VersionON",Unset:12,"12":"Unset", });
|
||||
/**
|
||||
*/
|
||||
export const Toggle = Object.freeze({ Background:0,"0":"Background",BackgroundPixels:1,"1":"BackgroundPixels",ForegroundPixels:2,"2":"ForegroundPixels", });
|
||||
export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", });
|
||||
/**
|
||||
*/
|
||||
export const Mask = Object.freeze({ M0:0,"0":"M0",M1:1,"1":"M1",M2:2,"2":"M2",M3:3,"3":"M3",M4:4,"4":"M4",M5:5,"5":"M5",M6:6,"6":"M6",M7:7,"7":"M7", });
|
||||
/**
|
||||
*/
|
||||
export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", });
|
||||
/**
|
||||
*/
|
||||
export const Mode = Object.freeze({ Numeric:0,"0":"Numeric",Alphanumeric:1,"1":"Alphanumeric",Byte:2,"2":"Byte", });
|
||||
/**
|
||||
*/
|
||||
export const Toggle = Object.freeze({ Background:0,"0":"Background",BackgroundPixels:1,"1":"BackgroundPixels",ForegroundPixels:2,"2":"ForegroundPixels", });
|
||||
/**
|
||||
*/
|
||||
export const QrError = Object.freeze({ InvalidEncoding:0,"0":"InvalidEncoding",ExceedsMaxCapacity:1,"1":"ExceedsMaxCapacity", });
|
||||
/**
|
||||
*/
|
||||
export const Module = Object.freeze({ DataOFF:0,"0":"DataOFF",DataON:1,"1":"DataON",FinderOFF:2,"2":"FinderOFF",FinderON:3,"3":"FinderON",AlignmentOFF:4,"4":"AlignmentOFF",AlignmentON:5,"5":"AlignmentON",TimingOFF:6,"6":"TimingOFF",TimingON:7,"7":"TimingON",FormatOFF:8,"8":"FormatOFF",FormatON:9,"9":"FormatON",VersionOFF:10,"10":"VersionOFF",VersionON:11,"11":"VersionON",Unset:12,"12":"Unset", });
|
||||
|
||||
const MarginFinalization = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
|
@ -635,15 +635,6 @@ export class SvgOptions {
|
|||
return this;
|
||||
}
|
||||
/**
|
||||
* @param {number} margin
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
margin(margin) {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
const ret = wasm.svgoptions_margin(ptr, margin);
|
||||
return SvgOptions.__wrap(ret);
|
||||
}
|
||||
/**
|
||||
* @param {number} unit
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
|
@ -675,35 +666,35 @@ export class SvgOptions {
|
|||
return SvgOptions.__wrap(ret);
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_x_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_x_matrix(scale_matrix) {
|
||||
scale_x_matrix(scale_x_matrix) {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
var ptr0 = isLikeNone(scale_x_matrix) ? 0 : passArray8ToWasm0(scale_x_matrix, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.svgoptions_scale_x_matrix(ptr, ptr0, len0);
|
||||
return SvgOptions.__wrap(ret);
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_y_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_y_matrix(scale_matrix) {
|
||||
scale_y_matrix(scale_y_matrix) {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
var ptr0 = isLikeNone(scale_y_matrix) ? 0 : passArray8ToWasm0(scale_y_matrix, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.svgoptions_scale_y_matrix(ptr, ptr0, len0);
|
||||
return SvgOptions.__wrap(ret);
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} scale_matrix
|
||||
* @param {Uint8Array | undefined} [scale_matrix]
|
||||
* @returns {SvgOptions}
|
||||
*/
|
||||
scale_matrix(scale_matrix) {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
var ptr0 = isLikeNone(scale_matrix) ? 0 : passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.svgoptions_scale_matrix(ptr, ptr0, len0);
|
||||
return SvgOptions.__wrap(ret);
|
||||
}
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -32,6 +32,10 @@ export function __wbg_get_matrix_mask(a: number): number;
|
|||
export function __wbg_set_matrix_mask(a: number, b: number): void;
|
||||
export function matrix_width(a: number): number;
|
||||
export function matrix_height(a: number): number;
|
||||
export function __wbg_version_free(a: number): void;
|
||||
export function __wbg_get_version_0(a: number): number;
|
||||
export function __wbg_set_version_0(a: number, b: number): void;
|
||||
export function version_new(a: number): number;
|
||||
export function __wbg_qroptions_free(a: number): void;
|
||||
export function qroptions_new(): number;
|
||||
export function qroptions_min_version(a: number, b: number): number;
|
||||
|
@ -41,7 +45,6 @@ export function qroptions_mask(a: number, b: number): number;
|
|||
export function qroptions_margin(a: number, b: number): number;
|
||||
export function __wbg_svgoptions_free(a: number): void;
|
||||
export function svgoptions_new(): number;
|
||||
export function svgoptions_margin(a: number, b: number): number;
|
||||
export function svgoptions_unit(a: number, b: number): number;
|
||||
export function svgoptions_foreground(a: number, b: number, c: number): number;
|
||||
export function svgoptions_background(a: number, b: number, c: number): number;
|
||||
|
@ -62,10 +65,6 @@ export function __wbg_get_svgresult_mask(a: number): number;
|
|||
export function __wbg_set_svgresult_mask(a: number, b: number): void;
|
||||
export function get_matrix(a: number, b: number, c: number, d: number): void;
|
||||
export function get_svg(a: number, b: number, c: number, d: number, e: number): void;
|
||||
export function __wbg_version_free(a: number): void;
|
||||
export function __wbg_get_version_0(a: number): number;
|
||||
export function __wbg_set_version_0(a: number, b: number): void;
|
||||
export function version_new(a: number): number;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_malloc(a: number, b: number): number;
|
||||
|
|
|
@ -10,8 +10,11 @@
|
|||
"@kobalte/core": "^0.13.1",
|
||||
"@solidjs/router": "^0.13.3",
|
||||
"@solidjs/start": "^1.0.0",
|
||||
"@srsholmes/solid-code-input": "^0.0.18",
|
||||
"@unocss/reset": "^0.59.4",
|
||||
"highlight.js": "^11.9.0",
|
||||
"lucide-solid": "^0.378.0",
|
||||
"qr-scanner-wechat": "^0.1.3",
|
||||
"solid-js": "^1.8.17",
|
||||
"unocss": "^0.59.4",
|
||||
"vinxi": "^0.3.11"
|
||||
|
|
6577
pnpm-lock.yaml
6577
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
|
@ -6,15 +6,21 @@ type Props = {
|
|||
onClick?: () => void;
|
||||
onMouseDown?: () => void;
|
||||
children: JSX.Element;
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
export function FlatButton(props: Props) {
|
||||
return (
|
||||
<Button
|
||||
class={`inline-flex justify-center items-center gap-1 border rounded-md hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) ${
|
||||
props.class || "px-3 py-2"
|
||||
}`}
|
||||
title={props.tooltip}
|
||||
classList={{
|
||||
"inline-flex justify-center items-center gap-1 border rounded-md hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) disabled:(pointer-events-none opacity-50)":
|
||||
true,
|
||||
[props.class ?? "px-3 py-2"]: true,
|
||||
}}
|
||||
onMouseDown={props.onMouseDown}
|
||||
onClick={props.onClick}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
|
|
|
@ -29,7 +29,7 @@ type ItemProps = {
|
|||
export function ButtonGroupItem(props: ItemProps) {
|
||||
return (
|
||||
<ToggleGroup.Item
|
||||
class="px-3 py-2 min-w-8 leading-tight border-l first:(border-none rounded-l-md) last:rounded-r-md text-fore-subtle aria-pressed:(bg-fore-base/10 text-fore-base) hover:bg-fore-base/10 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
|
||||
class="flex-grow-1 px-3 py-2 min-w-8 leading-tight border-l first:(border-none rounded-l-md) last:rounded-r-md text-fore-subtle aria-pressed:(bg-fore-base/10 text-fore-base) hover:bg-fore-base/10 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
|
||||
value={props.value}
|
||||
aria-label={props.value ?? props.ariaLabel}
|
||||
title={props.title ? props.value ?? props.ariaLabel : undefined}
|
||||
|
|
|
@ -4,7 +4,7 @@ type Props = {
|
|||
};
|
||||
export function ColorInput(props: Props) {
|
||||
return (
|
||||
<label class="border rounded-md font-mono inline-flex items-center p-1.5 gap-2 cursor-pointer hover:bg-fore-base/5">
|
||||
<label class="border rounded-md font-mono inline-flex items-center py-1.5 px-2 gap-2 cursor-pointer hover:bg-fore-base/5">
|
||||
{props.color}
|
||||
<input
|
||||
class="rounded-sm border-none w-6 h-6 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
import { For, Show, batch, type JSX } from "solid-js";
|
||||
import { useQrContext } from "~/lib/QrContext";
|
||||
import {
|
||||
ECL_NAMES,
|
||||
ECL_VALUE,
|
||||
MASK_KEY,
|
||||
MASK_NAMES,
|
||||
MASK_VALUE,
|
||||
MODE_KEY,
|
||||
MODE_NAMES,
|
||||
MODE_VALUE,
|
||||
} from "~/lib/options";
|
||||
import { FlatButton } from "./Button";
|
||||
import { ButtonGroup, ButtonGroupItem } from "./ButtonGroup";
|
||||
import { ColorInput } from "./ColorInput";
|
||||
import { ImageInput } from "./ImageInput";
|
||||
import { ModeTextInput } from "./ModeTextInput";
|
||||
import { NumberInput } from "./NumberInput";
|
||||
import { Select } from "./Select";
|
||||
import { Switch } from "./Switch";
|
||||
import { useSvgContext } from "~/lib/SvgContext";
|
||||
|
||||
export function Editor(props: any) {
|
||||
const { inputQr, setInputQr } = useQrContext();
|
||||
const {
|
||||
svgOptions,
|
||||
setSvgOptions,
|
||||
selections,
|
||||
scaleX,
|
||||
scaleY,
|
||||
setScaleXInPlace,
|
||||
setScaleYInPlace,
|
||||
} = useSvgContext();
|
||||
|
||||
// const [logoSize, setLogoSize] = createSignal(25);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="flex flex-col gap-2 flex-1 p-4">
|
||||
<ModeTextInput setValue={(s) => setInputQr("text", s)} />
|
||||
<Row title="Mode">
|
||||
<Select
|
||||
values={MODE_NAMES}
|
||||
value={MODE_KEY[inputQr.mode!]}
|
||||
setValue={(name) => setInputQr("mode", MODE_VALUE[name])}
|
||||
/>
|
||||
</Row>
|
||||
<Row title="Min version">
|
||||
<NumberInput
|
||||
min={1}
|
||||
max={40}
|
||||
value={inputQr.minVersion}
|
||||
setValue={(v) => setInputQr("minVersion", v)}
|
||||
/>
|
||||
</Row>
|
||||
<Row title="Min error tolerance">
|
||||
<ButtonGroup
|
||||
value={ECL_NAMES[inputQr.minEcl]}
|
||||
setValue={(v) => setInputQr("minEcl", ECL_VALUE[v])}
|
||||
>
|
||||
<For each={ECL_NAMES}>
|
||||
{(name) => <ButtonGroupItem value={name}>{name}</ButtonGroupItem>}
|
||||
</For>
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
<Row title="Mask pattern">
|
||||
<ButtonGroup
|
||||
value={MASK_KEY[inputQr.mask!]}
|
||||
setValue={(name) => setInputQr("mask", MASK_VALUE[name])}
|
||||
>
|
||||
<For each={MASK_NAMES}>
|
||||
{(value) => (
|
||||
<ButtonGroupItem value={value}>{value}</ButtonGroupItem>
|
||||
)}
|
||||
</For>
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
{/* <Row title="Margin">
|
||||
<NumberInput
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
value={inputQr.margin.top}
|
||||
setValue={(v) => setInputQr("margin", (prev) => prev.setTop(v))}
|
||||
/>
|
||||
</Row> */}
|
||||
<Row title="Foreground">
|
||||
<ColorInput
|
||||
color={svgOptions.fgColor}
|
||||
setColor={(v) => setSvgOptions("fgColor", v)}
|
||||
/>
|
||||
<Switch
|
||||
value={svgOptions.pixelateFgImg}
|
||||
setValue={(v) => setSvgOptions("pixelateFgImg", v)}
|
||||
label="Disable smoothing"
|
||||
/>
|
||||
<FlatButton
|
||||
class="text-sm px-2 py-2"
|
||||
onMouseDown={() => {
|
||||
batch(() => {
|
||||
let tmp = svgOptions.fgColor;
|
||||
setSvgOptions("fgColor", svgOptions.bgColor);
|
||||
setSvgOptions("bgColor", tmp);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Swap
|
||||
</FlatButton>
|
||||
</Row>
|
||||
<Row title="Background">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<ColorInput
|
||||
color={svgOptions.bgColor}
|
||||
setColor={(v) => setSvgOptions("bgColor", v)}
|
||||
/>
|
||||
<ImageInput
|
||||
value={svgOptions.bgImgFile}
|
||||
setValue={(v) => setSvgOptions("bgImgFile", v)}
|
||||
/>
|
||||
<Switch
|
||||
value={svgOptions.pixelateBgImg}
|
||||
setValue={(v) => setSvgOptions("pixelateBgImg", v)}
|
||||
label="Disable smoothing"
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
<Row title="Logo">
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<ImageInput
|
||||
value={svgOptions.fgImgFile}
|
||||
setValue={(v) => setSvgOptions("fgImgFile", v)}
|
||||
/>
|
||||
{/* TODO need better img management than just size */}
|
||||
{/* <NumberInput
|
||||
min={0}
|
||||
max={100}
|
||||
step={0.1}
|
||||
value={logoSize()}
|
||||
setValue={setLogoSize}
|
||||
/> */}
|
||||
</div>
|
||||
</Row>
|
||||
<Row title="Scale X">
|
||||
<NumberInput
|
||||
min={0}
|
||||
max={200}
|
||||
step={1}
|
||||
value={
|
||||
selections().length
|
||||
? scaleX()[
|
||||
selections()[0].top * Math.sqrt(scaleX().length) +
|
||||
selections()[0].left
|
||||
]
|
||||
: 100
|
||||
}
|
||||
setValue={(v) => {
|
||||
if (!selections().length) return;
|
||||
setScaleXInPlace((prev) => {
|
||||
let width = Math.sqrt(prev.length);
|
||||
selections().forEach((sel) => {
|
||||
for (let i = sel.top; i < sel.bot; i++) {
|
||||
for (let j = sel.left; j < sel.right; j++) {
|
||||
prev[i * width + j] = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
<Row title="Scale Y">
|
||||
<NumberInput
|
||||
min={0}
|
||||
max={200}
|
||||
step={1}
|
||||
value={
|
||||
selections().length
|
||||
? scaleY()[
|
||||
selections()[0].top * Math.sqrt(scaleY().length) +
|
||||
selections()[0].left
|
||||
]
|
||||
: 100
|
||||
}
|
||||
setValue={(v) => {
|
||||
if (!selections().length) return;
|
||||
setScaleYInPlace((prev) => {
|
||||
let width = Math.sqrt(prev.length);
|
||||
selections().forEach((sel) => {
|
||||
for (let i = sel.top; i < sel.bot; i++) {
|
||||
for (let j = sel.left; j < sel.right; j++) {
|
||||
prev[i * width + j] = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Row(props: {
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
sparkle?: boolean;
|
||||
}) {
|
||||
// This should be <label/> but clicking selects first button in buttongroup
|
||||
return (
|
||||
<div class="flex gap-2">
|
||||
<Show when={props.sparkle}>
|
||||
<AnimatedSparkle />
|
||||
</Show>
|
||||
<span class="w-30 py-2 text-left text-sm flex-shrink-0">
|
||||
{props.title}
|
||||
</span>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AnimatedSparkle() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 29 29"
|
||||
class="w-5 h-5 absolute -translate-x-full mt-2"
|
||||
>
|
||||
<path
|
||||
d="M14.6 5.8c.5 0 .8 7.4 1.2 7.8.4.4 7.9.5 7.9 1 0 .6-7.5.9-7.8 1.3-.4.4-.5 7.8-1 7.9-.6 0-1-7.5-1.3-7.9-.4-.4-7.9-.5-7.9-1 0-.6 7.5-.9 7.9-1.3.3-.4.4-7.8 1-7.8z"
|
||||
style="fill:#fca4a4"
|
||||
class="animate-pulse"
|
||||
/>
|
||||
<path
|
||||
d="M25.5 7.5c.1.3-2.3.6-2.4.8-.2.2-.3 2.6-.5 2.7-.3 0-.5-2.3-.7-2.5-.3-.2-2.7-.2-2.8-.5 0-.2 2.3-.5 2.5-.7.2-.2.2-2.7.5-2.7.2 0 .5 2.3.7 2.4.2.2 2.7.3 2.7.5z"
|
||||
style="fill:#fca4a4"
|
||||
class="animate-pulse"
|
||||
/>
|
||||
<path
|
||||
d="M11.3 21.9c0 .3-3 .1-3.2.3-.2.3 0 3.2-.3 3.2s-.1-3-.4-3.2c-.2-.2-3 0-3-.3s2.8-.1 3-.3c.3-.3 0-3.2.4-3.2.3 0 .1 3 .3 3.2.3.2 3.2 0 3.2.3z"
|
||||
style="fill:#fca4a4"
|
||||
class="animate-pulse"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -3,7 +3,7 @@ type Props = {
|
|||
};
|
||||
|
||||
/** No `value` prop b/c textarea cannot be controlled */
|
||||
export function ModeTextInput(props: Props) {
|
||||
export function TextInput(props: Props) {
|
||||
const onInput = debounce(props.setValue, 300);
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
|
|
|
@ -59,7 +59,7 @@ export function RenderGrid(props: Props) {
|
|||
|
||||
let selection: BoxSelection | null;
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
function onPointerMove(e: MouseEvent) {
|
||||
const { x, y } = getPos(e);
|
||||
|
||||
if (mode() === Mode.Select) {
|
||||
|
@ -142,7 +142,7 @@ export function RenderGrid(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
const onPointerUp = () => {
|
||||
if (selection != null) {
|
||||
setSelectionsInPlace((prev) => {
|
||||
// @ts-expect-error i'm right unless somehow this isn't synchronous
|
||||
|
@ -150,13 +150,13 @@ export function RenderGrid(props: Props) {
|
|||
return prev;
|
||||
});
|
||||
}
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
document.removeEventListener("pointermove", onPointerMove);
|
||||
document.removeEventListener("pointerup", onPointerUp);
|
||||
};
|
||||
|
||||
onCleanup(() => {
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
document.removeEventListener("pointermove", onPointerMove);
|
||||
document.removeEventListener("pointerup", onPointerUp);
|
||||
});
|
||||
|
||||
const [mode, setMode] = createSignal(Mode.Select);
|
||||
|
@ -229,8 +229,8 @@ export function RenderGrid(props: Props) {
|
|||
prevX = x;
|
||||
prevY = y;
|
||||
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("pointerup", onPointerUp);
|
||||
document.addEventListener("pointermove", onPointerMove);
|
||||
}}
|
||||
></canvas>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import { For, createSignal, type JSX } from "solid-js";
|
||||
import { useQrContext, type RenderFunc } from "~/lib/QrContext";
|
||||
import {
|
||||
ECL_NAMES,
|
||||
ECL_VALUE,
|
||||
MASK_KEY,
|
||||
MASK_NAMES,
|
||||
MASK_VALUE,
|
||||
MODE_KEY,
|
||||
MODE_NAMES,
|
||||
MODE_VALUE,
|
||||
} from "~/lib/options";
|
||||
import { FlatButton } from "../Button";
|
||||
import { ButtonGroup, ButtonGroupItem } from "../ButtonGroup";
|
||||
import { TextInput } from "../ModeTextInput";
|
||||
import { NumberInput } from "../NumberInput";
|
||||
import { Select } from "../Select";
|
||||
|
||||
import hljs from "highlight.js/lib/core";
|
||||
import javascript from "highlight.js/lib/languages/javascript";
|
||||
import { CodeInput } from "@srsholmes/solid-code-input";
|
||||
|
||||
import "../../styles/atom-one-dark.css";
|
||||
|
||||
hljs.registerLanguage("javascript", javascript);
|
||||
|
||||
export function Editor(props: any) {
|
||||
const { inputQr, setInputQr, setRenderFunc } = useQrContext();
|
||||
|
||||
const [code, setCode] = createSignal(` // qr, ctx are args
|
||||
const pixelSize = 10;
|
||||
ctx.canvas.width = qr.matrixWidth * pixelSize;
|
||||
ctx.canvas.height = qr.matrixHeight * pixelSize;
|
||||
|
||||
ctx.fillStyle = "rgb(255, 255, 255)";
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
ctx.fillStyle = "rgb(0, 0, 0)";
|
||||
|
||||
for (let y = 0; y < qr.matrixHeight; y++) {
|
||||
for (let x = 0; x < qr.matrixWidth; x++) {
|
||||
const module = qr.matrix[y * qr.matrixWidth + x];
|
||||
|
||||
if (module & 1) {
|
||||
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
const [prevCode, setPrevCode] = createSignal(code());
|
||||
|
||||
const saveCode = () => {
|
||||
setRenderFunc(() => new Function("qr", "ctx", code()) as RenderFunc);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-2 flex-1 p-4">
|
||||
<TextInput setValue={(s) => setInputQr("text", s)} />
|
||||
<Row title="Encoding" tooltip="Also known as Mode">
|
||||
<Select
|
||||
values={MODE_NAMES}
|
||||
value={MODE_KEY[inputQr.mode!]}
|
||||
setValue={(name) => setInputQr("mode", MODE_VALUE[name])}
|
||||
/>
|
||||
</Row>
|
||||
<Row title="Min symbol size" tooltip="Also known as Version">
|
||||
<NumberInput
|
||||
min={1}
|
||||
max={40}
|
||||
value={inputQr.minVersion}
|
||||
setValue={(v) => setInputQr("minVersion", v)}
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
title="Min error tolerance"
|
||||
tooltip="Also known as Error Correction Level"
|
||||
>
|
||||
<ButtonGroup
|
||||
value={ECL_NAMES[inputQr.minEcl]}
|
||||
setValue={(v) => setInputQr("minEcl", ECL_VALUE[v])}
|
||||
>
|
||||
<For each={ECL_NAMES}>
|
||||
{(name) => <ButtonGroupItem value={name}>{name}</ButtonGroupItem>}
|
||||
</For>
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
<Row title="Mask pattern">
|
||||
<ButtonGroup
|
||||
value={MASK_KEY[inputQr.mask!]}
|
||||
setValue={(name) => setInputQr("mask", MASK_VALUE[name])}
|
||||
>
|
||||
<For each={MASK_NAMES}>
|
||||
{(value) => (
|
||||
<ButtonGroupItem value={value}>{value}</ButtonGroupItem>
|
||||
)}
|
||||
</For>
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
<Row title="Margin">
|
||||
<NumberInput
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
value={inputQr.margin.top}
|
||||
setValue={(v) =>
|
||||
setInputQr("margin", { top: v, bottom: v, left: v, right: v })
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
<div>
|
||||
<div class="flex justify-between my-2">
|
||||
<div class="text-sm py-2">Render function</div>
|
||||
<FlatButton class="px-3 py-1" disabled={prevCode() === code()} onMouseDown={saveCode}>
|
||||
{prevCode() === code() ? "Saved" : "Save"}
|
||||
</FlatButton>
|
||||
</div>
|
||||
<div
|
||||
class="hljs-wrapper"
|
||||
onKeyDown={(e) => {
|
||||
if (e.ctrlKey && e.key === "s") {
|
||||
e.preventDefault();
|
||||
saveCode();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CodeInput
|
||||
autoHeight={true}
|
||||
value={code()}
|
||||
onChange={setCode}
|
||||
highlightjs={hljs}
|
||||
language="javascript"
|
||||
resize="both"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Row(props: {
|
||||
tooltip?: string;
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
}) {
|
||||
// This should be <label/> but clicking selects first button in buttongroup
|
||||
return (
|
||||
<div title={props.tooltip}>
|
||||
<div class="text-sm py-2">{props.title}</div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import { QrError } from "fuqr";
|
||||
|
||||
import { Match, Show, Switch, createEffect } from "solid-js";
|
||||
import { useQrContext, type OutputQr } from "~/lib/QrContext";
|
||||
import {
|
||||
ECL_LABELS,
|
||||
ECL_NAMES,
|
||||
MASK_KEY,
|
||||
MODE_KEY,
|
||||
MODE_NAMES,
|
||||
} from "~/lib/options";
|
||||
|
||||
export default function QrPreview() {
|
||||
const { inputQr, outputQr } = useQrContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show
|
||||
when={typeof outputQr() !== "number"}
|
||||
fallback={
|
||||
<div class="aspect-[1/1] border rounded-md flex justify-center items-center">
|
||||
<Switch>
|
||||
<Match when={outputQr() === QrError.ExceedsMaxCapacity}>
|
||||
Data exceeds max capacity
|
||||
</Match>
|
||||
<Match when={outputQr() === QrError.InvalidEncoding}>
|
||||
{`Input cannot be encoded in ${
|
||||
// @ts-expect-error props.mode not null b/c InvalidEncoding implies mode
|
||||
MODE_NAMES[inputQr.mode + 1]
|
||||
} mode`}
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RenderedQrCode />
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** This component assumes outputQr() is not QrError, this simplifies effects and types
|
||||
*
|
||||
* Original problem:
|
||||
* When using Show, effects tracking the when signal run before refs inside Show become valid.
|
||||
* The result was no effect running after initial mount, so no render.
|
||||
* Running the effect in the ref function caused double rendering for future mounts.
|
||||
*/
|
||||
function RenderedQrCode() {
|
||||
const { outputQr: _outputQr, renderFunc } = useQrContext();
|
||||
const outputQr = _outputQr as () => OutputQr
|
||||
|
||||
const fullWidth = () => {
|
||||
const output = outputQr();
|
||||
return output.version * 4 + 17 + output.margin.left + output.margin.right;
|
||||
};
|
||||
const fullHeight = () => {
|
||||
const output = outputQr();
|
||||
return output.version * 4 + 17 + output.margin.top + output.margin.bottom;
|
||||
};
|
||||
|
||||
let qrCanvas: HTMLCanvasElement;
|
||||
|
||||
createEffect(() => {
|
||||
const ctx = qrCanvas.getContext("2d")!;
|
||||
ctx.clearRect(0, 0, qrCanvas.width, qrCanvas.height);
|
||||
renderFunc()(outputQr(), ctx);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
class="aspect-[1/1] border rounded-md relative overflow-hidden"
|
||||
style={{
|
||||
"background-image":
|
||||
"repeating-conic-gradient(#ddd 0% 25%, #aaa 25% 50%)",
|
||||
"background-position": "50%",
|
||||
"background-size": `${(1 / fullWidth()) * 100}% ${
|
||||
(1 / fullHeight()) * 100
|
||||
}%`,
|
||||
}}
|
||||
>
|
||||
<canvas
|
||||
class="w-full h-full"
|
||||
ref={qrCanvas!}
|
||||
></canvas>
|
||||
</div>
|
||||
<div class="p-4 grid grid-cols-2 gap-y-2 text-sm text-left">
|
||||
<div class="">
|
||||
Symbol size{" "}
|
||||
<div class="font-bold text-base whitespace-pre">
|
||||
{outputQr().version} ({outputQr().version * 4 + 17}x
|
||||
{outputQr().version * 4 + 17} pixels)
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
Error tolerance{" "}
|
||||
<div class="font-bold text-base whitespace-pre">
|
||||
{ECL_NAMES[outputQr().ecl]} ({ECL_LABELS[outputQr().ecl]})
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
Encoding{" "}
|
||||
<span class="font-bold text-base">{MODE_KEY[outputQr().mode]}</span>
|
||||
</div>
|
||||
<div class="">
|
||||
Mask{" "}
|
||||
<span class="font-bold text-base">{MASK_KEY[outputQr().mask]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -112,17 +112,17 @@ export default function SvgPreview() {
|
|||
</div>
|
||||
<div class="p-4 grid grid-cols-2 gap-y-2 text-sm text-left">
|
||||
<div class="">
|
||||
Version{" "}
|
||||
<span class="font-bold text-base whitespace-pre">
|
||||
Symbol size{" "}
|
||||
<div class="font-bold text-base whitespace-pre">
|
||||
{svgResult()!.version["0"]} ({svgResult()!.version["0"] * 4 + 17}x
|
||||
{svgResult()!.version["0"] * 4 + 17} pixels)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
Error tolerance{" "}
|
||||
<span class="font-bold text-base whitespace-pre">
|
||||
<div class="font-bold text-base whitespace-pre">
|
||||
{ECL_NAMES[svgResult()!.ecl]} ({ECL_LABELS[svgResult()!.ecl]})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
Encoding{" "}
|
|
@ -11,7 +11,7 @@ export default createHandler(() => (
|
|||
<link rel="icon" href="/favicon.svg" />
|
||||
{assets}
|
||||
</head>
|
||||
<body class="bg-back-base text-fore-base my-8 [--un-default-border-color:fg-subtle] ">
|
||||
<body class="bg-back-base text-fore-base my-8 [--un-default-border-color:fg-subtle]">
|
||||
<div id="app">{children}</div>
|
||||
{scripts}
|
||||
</body>
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
useContext,
|
||||
type Accessor,
|
||||
type JSX,
|
||||
type Setter,
|
||||
} from "solid-js";
|
||||
import { ECL, Mode, Mask, QrError } from "fuqr";
|
||||
import {
|
||||
ECL,
|
||||
Mode,
|
||||
Mask,
|
||||
QrError,
|
||||
Module,
|
||||
QrOptions,
|
||||
Version,
|
||||
Margin,
|
||||
get_matrix,
|
||||
} from "fuqr";
|
||||
import { createStore, type SetStoreFunction } from "solid-js/store";
|
||||
|
||||
type InputQr = {
|
||||
|
@ -37,6 +48,10 @@ export type OutputQr = {
|
|||
bottom: number;
|
||||
left: number;
|
||||
};
|
||||
/** Stored as value b/c Matrix is a ptr which becomes null after use */
|
||||
matrix: Module[];
|
||||
matrixWidth: number;
|
||||
matrixHeight: number;
|
||||
};
|
||||
|
||||
export const QrContext = createContext<{
|
||||
|
@ -44,8 +59,12 @@ export const QrContext = createContext<{
|
|||
setInputQr: SetStoreFunction<InputQr>;
|
||||
outputQr: Accessor<OutputQr | QrError>;
|
||||
setOutputQr: Setter<OutputQr | QrError>;
|
||||
renderFunc: Accessor<RenderFunc>;
|
||||
setRenderFunc: Setter<RenderFunc>;
|
||||
}>();
|
||||
|
||||
export type RenderFunc = (qr: OutputQr, ctx: CanvasRenderingContext2D) => void;
|
||||
|
||||
export function QrContextProvider(props: { children: JSX.Element }) {
|
||||
const [inputQr, setInputQr] = createStore<InputQr>({
|
||||
text: "Greetings traveler",
|
||||
|
@ -65,8 +84,59 @@ export function QrContextProvider(props: { children: JSX.Element }) {
|
|||
QrError.InvalidEncoding
|
||||
);
|
||||
|
||||
const [renderFunc, setRenderFunc] = createSignal<RenderFunc>(defaultRender);
|
||||
|
||||
createEffect(() => {
|
||||
try {
|
||||
// NOTE: Version and Margin cannot be reused, so must be created each time
|
||||
let qrOptions = new QrOptions()
|
||||
.min_version(new Version(inputQr.minVersion))
|
||||
.min_ecl(inputQr.minEcl)
|
||||
.mask(inputQr.mask!) // null makes more sense than undefined
|
||||
.mode(inputQr.mode!) // null makes more sense than undefined
|
||||
.margin(new Margin(10))
|
||||
.margin(
|
||||
new Margin(0)
|
||||
.setTop(inputQr.margin.top)
|
||||
.setRight(inputQr.margin.right)
|
||||
.setBottom(inputQr.margin.bottom)
|
||||
.setLeft(inputQr.margin.left)
|
||||
);
|
||||
|
||||
let m = get_matrix(inputQr.text, qrOptions);
|
||||
|
||||
setOutputQr({
|
||||
text: inputQr.text,
|
||||
matrix: m.value,
|
||||
matrixWidth: m.version["0"] * 4 + 17 + m.margin.left + m.margin.right,
|
||||
matrixHeight: m.version["0"] * 4 + 17 + m.margin.top + m.margin.bottom,
|
||||
version: m.version["0"],
|
||||
ecl: m.ecl,
|
||||
mode: m.mode,
|
||||
mask: m.mask,
|
||||
margin: {
|
||||
top: m.margin.top,
|
||||
right: m.margin.right,
|
||||
bottom: m.margin.bottom,
|
||||
left: m.margin.left,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
setOutputQr(e as QrError);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<QrContext.Provider value={{ inputQr, setInputQr, outputQr, setOutputQr }}>
|
||||
<QrContext.Provider
|
||||
value={{
|
||||
inputQr,
|
||||
setInputQr,
|
||||
outputQr,
|
||||
setOutputQr,
|
||||
renderFunc,
|
||||
setRenderFunc,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</QrContext.Provider>
|
||||
);
|
||||
|
@ -79,3 +149,24 @@ export function useQrContext() {
|
|||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function defaultRender(qr: OutputQr, ctx: CanvasRenderingContext2D) {
|
||||
const pixelSize = 10;
|
||||
ctx.canvas.width = qr.matrixWidth * pixelSize;
|
||||
ctx.canvas.height = qr.matrixHeight * pixelSize;
|
||||
|
||||
ctx.fillStyle = "rgb(255, 255, 255)";
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
ctx.fillStyle = "rgb(0, 0, 0)";
|
||||
|
||||
for (let y = 0; y < qr.matrixHeight; y++) {
|
||||
for (let x = 0; x < qr.matrixWidth; x++) {
|
||||
const module = qr.matrix[y * qr.matrixWidth + x];
|
||||
|
||||
if (module & 1) {
|
||||
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
untrack,
|
||||
useContext,
|
||||
type Accessor,
|
||||
type JSX,
|
||||
type Setter,
|
||||
} from "solid-js";
|
||||
import { createStore, type SetStoreFunction } from "solid-js/store";
|
||||
import {
|
||||
QrOptions,
|
||||
Version,
|
||||
Margin,
|
||||
get_matrix,
|
||||
SvgOptions,
|
||||
get_svg,
|
||||
type QrError,
|
||||
type SvgResult,
|
||||
} from "fuqr";
|
||||
import { useQrContext } from "./QrContext";
|
||||
|
||||
type RenderOptions = {
|
||||
bgColor: string;
|
||||
fgColor: string;
|
||||
bgImgFile: File | null;
|
||||
fgImgFile: File | null;
|
||||
pixelateBgImg: boolean;
|
||||
pixelateFgImg: boolean;
|
||||
};
|
||||
|
||||
export type BoxSelection = {
|
||||
top: number;
|
||||
bot: number;
|
||||
left: number;
|
||||
right: number;
|
||||
};
|
||||
|
||||
export const SvgContext = createContext<{
|
||||
svgOptions: RenderOptions;
|
||||
setSvgOptions: SetStoreFunction<RenderOptions>;
|
||||
selections: Accessor<BoxSelection[]>;
|
||||
setSelectionsInPlace: Setter<BoxSelection[]>;
|
||||
scaleX: Accessor<number[]>;
|
||||
setScaleXInPlace: Setter<number[]>;
|
||||
scaleY: Accessor<number[]>;
|
||||
setScaleYInPlace: Setter<number[]>;
|
||||
svgResult: Accessor<SvgResult | null>;
|
||||
setSvgResult: Setter<SvgResult | null>;
|
||||
}>();
|
||||
|
||||
export function SvgContextProvider(props: { children: JSX.Element }) {
|
||||
const [svgOptions, setSvgOptions] = createStore<RenderOptions>({
|
||||
bgColor: "#ffffff",
|
||||
fgColor: "#000000",
|
||||
bgImgFile: null,
|
||||
fgImgFile: null,
|
||||
pixelateFgImg: false,
|
||||
pixelateBgImg: false,
|
||||
});
|
||||
const { inputQr, outputQr, setOutputQr } = useQrContext();
|
||||
|
||||
const [selections, setSelectionsInPlace] = createSignal<BoxSelection[]>([], {
|
||||
equals: false,
|
||||
});
|
||||
|
||||
const [scaleX, setScaleXInPlace] = createSignal<number[]>([], {
|
||||
equals: false,
|
||||
});
|
||||
const [scaleY, setScaleYInPlace] = createSignal<number[]>([], {
|
||||
equals: false,
|
||||
});
|
||||
|
||||
const [svgResult, setSvgResult] = createSignal<SvgResult | null>(null);
|
||||
|
||||
createEffect(() => {
|
||||
try {
|
||||
// NOTE: Version and Margin cannot be reused, so must be created each time
|
||||
let qrOptions = new QrOptions()
|
||||
.min_version(new Version(inputQr.minVersion))
|
||||
.min_ecl(inputQr.minEcl)
|
||||
.mask(inputQr.mask!) // null makes more sense than undefined
|
||||
.mode(inputQr.mode!) // null makes more sense than undefined
|
||||
.margin(new Margin(10))
|
||||
.margin(
|
||||
new Margin(0)
|
||||
.setTop(inputQr.margin.top)
|
||||
.setRight(inputQr.margin.right)
|
||||
.setBottom(inputQr.margin.bottom)
|
||||
.setLeft(inputQr.margin.left)
|
||||
);
|
||||
|
||||
let m = get_matrix(inputQr.text, qrOptions);
|
||||
setOutputQr({
|
||||
text: inputQr.text,
|
||||
version: m.version["0"],
|
||||
ecl: m.ecl,
|
||||
mode: m.mode,
|
||||
mask: m.mask,
|
||||
margin: {
|
||||
top: m.margin.top,
|
||||
right: m.margin.right,
|
||||
bottom: m.margin.bottom,
|
||||
left: m.margin.left,
|
||||
},
|
||||
});
|
||||
const matrixLength = m.width() * m.height();
|
||||
if (matrixLength !== untrack(scaleX).length) {
|
||||
console.log("setScale");
|
||||
setSelectionsInPlace([]);
|
||||
setScaleXInPlace(Array(matrixLength).fill(100));
|
||||
setScaleYInPlace(Array(matrixLength).fill(100));
|
||||
}
|
||||
|
||||
qrOptions = new QrOptions()
|
||||
.min_version(new Version(m.version[0]))
|
||||
.min_ecl(m.ecl)
|
||||
.mask(m.mask)
|
||||
.mode(m.mode);
|
||||
|
||||
let svgOpts = new SvgOptions()
|
||||
.foreground(svgOptions.fgColor)
|
||||
.background(svgOptions.bgColor)
|
||||
.scale_x_matrix(new Uint8Array(scaleX()))
|
||||
.scale_y_matrix(new Uint8Array(scaleY()));
|
||||
|
||||
// infallible b/c outputQr contains successful options
|
||||
setSvgResult(get_svg(inputQr.text, qrOptions, svgOpts));
|
||||
} catch (e) {
|
||||
setOutputQr(e as QrError);
|
||||
setSvgResult(null);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<SvgContext.Provider
|
||||
value={{
|
||||
svgOptions,
|
||||
setSvgOptions,
|
||||
svgResult,
|
||||
setSvgResult,
|
||||
selections,
|
||||
setSelectionsInPlace,
|
||||
scaleX,
|
||||
setScaleXInPlace,
|
||||
scaleY,
|
||||
setScaleYInPlace,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SvgContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSvgContext() {
|
||||
const context = useContext(SvgContext);
|
||||
if (!context) {
|
||||
throw new Error("useSvgContext: used outside SvgContextProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { Switch, Match, createSignal } from "solid-js";
|
||||
import { createSignal } from "solid-js";
|
||||
import { clientOnly } from "@solidjs/start";
|
||||
import { Editor } from "~/components/editor/QrEditor";
|
||||
import { FlatButton } from "~/components/Button";
|
||||
import QrPreview from "~/components/preview/QrPreview";
|
||||
import init from "fuqr";
|
||||
import { Editor } from "~/components/Editor";
|
||||
import SvgPreview from "~/components/qr/SvgPreview";
|
||||
import { SvgContextProvider } from "~/lib/SvgContext";
|
||||
|
||||
const QrContextProvider = clientOnly(async () => {
|
||||
await init();
|
||||
|
@ -12,38 +12,17 @@ const QrContextProvider = clientOnly(async () => {
|
|||
};
|
||||
});
|
||||
|
||||
// const MODULE_NAMES = [
|
||||
// "Data",
|
||||
// "Finder",
|
||||
// "Alignment",
|
||||
// "Timing",
|
||||
// "Format",
|
||||
// "Version",
|
||||
// ] as const;
|
||||
|
||||
enum Stage {
|
||||
Create,
|
||||
Customize,
|
||||
}
|
||||
export default function Home() {
|
||||
const [stage, setStage] = createSignal(Stage.Create);
|
||||
return (
|
||||
<QrContextProvider>
|
||||
<SvgContextProvider>
|
||||
<main class="max-w-screen-lg mx-auto">
|
||||
<Switch>
|
||||
<Match when={stage() == Stage.Create}>
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
<Editor />
|
||||
<div class="flex-1 min-w-200px sticky top-0 self-start p-4">
|
||||
<SvgPreview />
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
{/* <Match when={stage() == Stage.Customize}></Match> */}
|
||||
</Switch>
|
||||
<main class="max-w-screen-2xl mx-auto p-4">
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
<Editor />
|
||||
<div class="flex-grow-1 min-w-200px sticky top-0 self-start p-4">
|
||||
<QrPreview />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</SvgContextProvider>
|
||||
</QrContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
|
||||
Atom One Dark by Daniel Gamage
|
||||
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
|
||||
|
||||
base: #282c34
|
||||
mono-1: #abb2bf
|
||||
mono-2: #818896
|
||||
mono-3: #5c6370
|
||||
hue-1: #56b6c2
|
||||
hue-2: #61aeee
|
||||
hue-3: #c678dd
|
||||
hue-4: #98c379
|
||||
hue-5: #e06c75
|
||||
hue-5-2: #be5046
|
||||
hue-6: #d19a66
|
||||
hue-6-2: #e6c07b
|
||||
|
||||
*/
|
||||
|
||||
.hljs-wrapper > * {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.hljs-wrapper > * > * {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
pre[class*='language-'] {
|
||||
color: #abb2bf;
|
||||
background: #282c34;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #5c6370;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-formula {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name,
|
||||
.hljs-selector-tag,
|
||||
.hljs-deletion,
|
||||
.hljs-subst {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.hljs-literal {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta .hljs-string {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-number {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-meta,
|
||||
.hljs-selector-id,
|
||||
.hljs-title {
|
||||
color: #61aeee;
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-title.class_,
|
||||
.hljs-class .hljs-title {
|
||||
color: #e6c07b;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
|
@ -15,6 +15,26 @@ export default defineConfig({
|
|||
subtle: "#1a1b1c",
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"content-show": {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: "scale(0.96);",
|
||||
},
|
||||
to: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
"content-hide": {
|
||||
from: {
|
||||
opacity: 1,
|
||||
},
|
||||
to: {
|
||||
opacity: 0,
|
||||
transform: "scale(0.96);",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
preflights: [
|
||||
{
|
||||
|
|
Ładowanie…
Reference in New Issue