Files

188 lines
6.0 KiB
Plaintext

@implements IAsyncDisposable
@inject IJSRuntime JS
@if (IsOpen)
{
<div class="qr-overlay" @onclick="Cancel">
<div class="qr-dialog" role="dialog" aria-modal="true" aria-labelledby="qr-title" @onclick:stopPropagation="true">
<div class="qr-header">
<div>
<h3 id="qr-title">@Title</h3>
@if (!string.IsNullOrEmpty(Subtitle))
{
<p class="qr-subtitle">@Subtitle</p>
}
</div>
<button type="button" class="qr-close" @onclick="Cancel" aria-label="Schließen">✕</button>
</div>
<div class="qr-body">
<div id="@_elementId" class="qr-viewport"></div>
@if (isStarting)
{
<div class="qr-status"><span class="spinner"></span> Kamera wird gestartet…</div>
}
else if (!string.IsNullOrEmpty(cameraError))
{
<div class="qr-status error">📷 @cameraError</div>
}
else
{
<div class="qr-status">QR-Code des Reports vor die Kamera halten</div>
}
<div class="qr-manual">
<label for="@_fileInputId" class="btn btn-secondary w-100">📷 Foto aufnehmen / Bild wählen</label>
<input id="@_fileInputId" type="file" accept="image/*" capture="environment" style="display:none" @onchange="OnFileSelected" />
</div>
<div class="qr-manual">
<label for="qr-manual-input">Oder Link / Nummer einfügen</label>
<div class="qr-manual-row">
<input id="qr-manual-input" type="text" class="form-control" placeholder="https://files.cnlaunch.com/…?dataRecordSn=…"
@bind="manualText" @bind:event="oninput" @onkeydown="OnManualKeyDown" />
<button type="button" class="btn btn-primary" @onclick="SubmitManual" disabled="@string.IsNullOrWhiteSpace(manualText)">Übernehmen</button>
</div>
</div>
</div>
</div>
</div>
}
@code {
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter] public string Title { get; set; } = "QR-Code scannen";
[Parameter] public string? Subtitle { get; set; }
[Parameter] public EventCallback<string> OnScanned { get; set; }
private readonly string _elementId = $"qr-reader-{Guid.NewGuid():N}";
private readonly string _fileInputId = $"qr-file-{Guid.NewGuid():N}";
private DotNetObjectReference<QrScannerModal>? _selfRef;
private bool _scannerActive;
private bool _wasOpen;
private bool isStarting;
private string? cameraError;
private string manualText = "";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (IsOpen && !_wasOpen)
{
_wasOpen = true;
await StartScanner();
}
else if (!IsOpen && _wasOpen)
{
_wasOpen = false;
await StopScanner();
}
}
private async Task StartScanner()
{
cameraError = null;
isStarting = true;
manualText = "";
StateHasChanged();
try
{
_selfRef ??= DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("qrScanner.start", _elementId, _selfRef);
_scannerActive = true;
}
catch (JSException ex)
{
cameraError = ExtractMessage(ex.Message);
}
catch (Exception ex)
{
cameraError = ex.Message;
}
finally
{
isStarting = false;
StateHasChanged();
}
}
private static string ExtractMessage(string raw)
{
if (raw.Contains("NotAllowedError", StringComparison.OrdinalIgnoreCase) || raw.Contains("Permission", StringComparison.OrdinalIgnoreCase))
return "Kamerazugriff wurde verweigert. Bitte Berechtigung im Browser erlauben.";
if (raw.Contains("NotFoundError", StringComparison.OrdinalIgnoreCase) || raw.Contains("Keine Kamera", StringComparison.OrdinalIgnoreCase))
return "Keine Kamera gefunden.";
var idx = raw.IndexOf('\n');
return idx > 0 ? raw[..idx] : raw;
}
private async Task StopScanner()
{
if (!_scannerActive) return;
_scannerActive = false;
try { await JS.InvokeVoidAsync("qrScanner.stop"); } catch { }
}
[JSInvokable]
public async Task OnQrScanned(string text)
{
if (string.IsNullOrWhiteSpace(text)) return;
await Close();
await OnScanned.InvokeAsync(text.Trim());
}
private async Task OnFileSelected(ChangeEventArgs e)
{
cameraError = null;
_scannerActive = false;
try
{
var text = await JS.InvokeAsync<string?>("qrScanner.scanFile", _elementId, _fileInputId);
if (!string.IsNullOrWhiteSpace(text))
{
await OnQrScanned(text);
return;
}
}
catch (JSException ex)
{
cameraError = ExtractMessage(ex.Message);
}
StateHasChanged();
}
private async Task SubmitManual()
{
if (string.IsNullOrWhiteSpace(manualText)) return;
var text = manualText.Trim();
await Close();
await OnScanned.InvokeAsync(text);
}
private async Task OnManualKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter") await SubmitManual();
}
private async Task Cancel() => await Close();
private async Task Close()
{
await StopScanner();
_wasOpen = false;
if (IsOpen)
{
IsOpen = false;
await IsOpenChanged.InvokeAsync(false);
}
}
public async ValueTask DisposeAsync()
{
await StopScanner();
_selfRef?.Dispose();
}
}