pwa and mobile client

This commit is contained in:
azykov@mail.ru
2026-09-29 15:55:25 +03:00
parent 657009bd6c
commit 1935acf2ff
96 changed files with 8370 additions and 58 deletions
+2 -12
View File
@@ -1,8 +1,7 @@
import QRCode from 'qrcode';
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import {
BWIP_BCID,
TWO_DIMENSIONAL_CODE_TYPES,
bwipRenderOptions,
cardAbbreviation,
textColorOn,
type AccessKeyView,
@@ -142,16 +141,7 @@ export function CodePreview({ code, codeType }: { code: string; codeType: CodeTy
if (!code) return { error: 'Enter a code to see the preview' };
if (!bwipjs) return { error: 'Loading…' };
try {
const svg = bwipjs.toSVG({
bcid: BWIP_BCID[codeType],
text: code,
scale: 3,
...(TWO_DIMENSIONAL_CODE_TYPES.has(codeType) ? {} : { height: 14, includetext: true, textxalign: 'center' as const }),
paddingwidth: 4,
paddingheight: 4,
backgroundcolor: 'FFFFFF',
});
return { svg };
return { svg: bwipjs.toSVG(bwipRenderOptions(codeType, code)) };
} catch (e) {
return { error: String((e as Error).message ?? e).replace(/^Error:\s*/, '') };
}
+20 -3
View File
@@ -27,15 +27,21 @@ function Editor({ initial }: { initial?: Card }) {
const [card, setCard] = useState<Card | undefined>(initial);
const [fields, setFields] = useState<CardFields>(
initial
? { name: initial.name, color: initial.color, code: initial.code, codeType: initial.codeType }
: { name: '', color: randomCardColor(), code: '', codeType: 'EAN13' },
? { name: initial.name, color: initial.color, code: initial.code, codeType: initial.codeType, comment: initial.comment ?? '' }
: { name: '', color: randomCardColor(), code: '', codeType: 'EAN13', comment: '' },
);
const action = useAction();
const [saved, setSaved] = useState(false);
useEffect(() => setSaved(false), [fields]);
const dirty = !card || card.name !== fields.name || card.color !== fields.color || card.code !== fields.code || card.codeType !== fields.codeType;
const dirty =
!card ||
card.name !== fields.name ||
card.color !== fields.color ||
card.code !== fields.code ||
card.codeType !== fields.codeType ||
(card.comment ?? '') !== (fields.comment ?? '');
const set = <K extends keyof CardFields>(key: K, value: CardFields[K]) => setFields((f) => ({ ...f, [key]: value }));
const save = async (e: React.FormEvent) => {
@@ -116,6 +122,17 @@ function Editor({ initial }: { initial?: Card }) {
<textarea id="code" className="mono" rows={2} value={fields.code} onChange={(e) => set('code', e.target.value)} required />
</div>
<CodePreview code={fields.code} codeType={fields.codeType} />
<div className="field">
<label htmlFor="comment">Comment</label>
<textarea
id="comment"
rows={3}
maxLength={2000}
placeholder="Optional, e.g. whose card it is or how to use it"
value={fields.comment ?? ''}
onChange={(e) => set('comment', e.target.value)}
/>
</div>
<div className="row">
<button type="submit" disabled={action.busy || (!dirty && !!card)}>
{card ? 'Save' : 'Create card'}