qr transfer panel keyed (573 -> 569)

Its two appRegistryMetas names are left for now — see the note in the dav commit.
This commit is contained in:
2026-08-23 14:06:31 +00:00
parent 589c8cdf6e
commit 0f58331654
4 changed files with 36 additions and 19 deletions
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import jsQR from 'jsqr';
import { Camera, Download, RotateCcw } from 'lucide-react';
import { crc32, decodeFrame, reassemble, type MetaFrame } from './protocol';
import { useTranslation } from '../../locales';
// Receiver: camera → jsQR → collect frames until every index is present.
//
@@ -15,6 +16,7 @@ import { crc32, decodeFrame, reassemble, type MetaFrame } from './protocol';
type Progress = { meta: MetaFrame; have: number };
export const QrReceiver = () => {
const { t } = useTranslation();
const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const chunksRef = useRef(new Map<number, Uint8Array>());
@@ -130,7 +132,7 @@ export const QrReceiver = () => {
className="flex cursor-pointer items-center gap-2 rounded-md border border-duck-dark/20 px-3 py-2 text-sm text-duck-dark/80 hover:border-duck-teal hover:text-duck-teal"
>
<Camera className="h-4 w-4" />
Start scanning
{t('qrTransfer.startScanning')}
</button>
)}
@@ -172,18 +174,14 @@ export const QrReceiver = () => {
className="flex cursor-pointer items-center gap-1 text-xs text-duck-dark/50 hover:text-duck-teal"
>
<RotateCcw className="h-3.5 w-3.5" />
Receive another
{t('qrTransfer.receiveAnother')}
</button>
</div>
)}
{scanning && (
<button
type="button"
onClick={stop}
className="cursor-pointer text-xs text-duck-dark/50 hover:text-red-500"
>
Stop
<button type="button" onClick={stop} className="cursor-pointer text-xs text-duck-dark/50 hover:text-red-500">
{t('qrTransfer.stop')}
</button>
)}
</div>
@@ -1,14 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import QRCode from 'qrcode';
import { Upload, Play, Pause } from 'lucide-react';
import {
chunkFile,
crc32,
encodeData,
encodeMeta,
newSessionId,
DEFAULT_CHUNK_BYTES,
} from './protocol';
import { chunkFile, crc32, encodeData, encodeMeta, newSessionId, DEFAULT_CHUNK_BYTES } from './protocol';
import { useTranslation } from '../../locales';
// Sender: a file becomes a looping carousel of QR frames.
//
@@ -24,6 +18,7 @@ const META_EVERY = 12;
const SPEEDS = [4, 6, 8, 12] as const;
export const QrSender = () => {
const { t } = useTranslation();
const canvasRef = useRef<HTMLCanvasElement>(null);
const [file, setFile] = useState<{ name: string; mime: string; bytes: Uint8Array } | null>(null);
const [frames, setFrames] = useState<string[]>([]);
@@ -82,7 +77,7 @@ export const QrSender = () => {
<div className="flex h-full w-full flex-col items-center gap-3 overflow-y-auto p-3">
<label className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-md border border-dashed border-duck-dark/25 py-3 text-sm text-duck-dark/70 hover:border-duck-teal hover:text-duck-teal">
<Upload className="h-4 w-4" />
{file ? file.name : 'Choose a file to send'}
{file ? file.name : t('qrTransfer.chooseFile')}
<input
type="file"
className="hidden"
@@ -129,8 +124,8 @@ export const QrSender = () => {
</div>
<p className="text-center text-[10px] leading-relaxed text-duck-dark/40">
Keep this on screen until the receiver reports 100%. It loops forever — a missed frame is
picked up on the next pass.
Keep this on screen until the receiver reports 100%. It loops forever — a missed frame is picked up on the
next pass.
</p>
</>
)}
@@ -11,5 +11,17 @@
"unknownMessage": "The VNC server sent an unknown message type ({{type}}).",
"unknownEncoding": "The VNC server used encoding {{encoding}}, which this client did not ask for."
}
},
"qrTransfer": {
"chooseFile": "Choose a file to send",
"startScanning": "Start scanning",
"receiveAnother": "Receive another",
"stop": "Stop"
},
"dav": {
"empty": "Nothing here yet. Add one from a phone or desktop client over CalDAV/CardDAV.",
"calendar": "Calendar",
"contacts": "Contacts",
"readFailed": "Could not read {{name}}: {{reason}}"
}
}
@@ -11,5 +11,17 @@
"unknownMessage": "O servidor VNC enviou um tipo de mensagem desconhecido ({{type}}).",
"unknownEncoding": "O servidor VNC usou a codificação {{encoding}}, que este cliente não pediu."
}
},
"qrTransfer": {
"chooseFile": "Escolha um ficheiro para enviar",
"startScanning": "Começar a digitalizar",
"receiveAnother": "Receber outro",
"stop": "Parar"
},
"dav": {
"empty": "Ainda não há nada aqui. Adicione um a partir de um cliente de telemóvel ou computador por CalDAV/CardDAV.",
"calendar": "Calendário",
"contacts": "Contactos",
"readFailed": "Não foi possível ler {{name}}: {{reason}}"
}
}