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:
@@ -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}}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user