162 lines
5.1 KiB
Plaintext
162 lines
5.1 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="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 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 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();
|
|
}
|
|
}
|