Backend: - PATCH /api/wahl/:wahlId (isOpen) to open/close a Wahl. - GET /api/wahl/:wahlId/teilnehmer (LT): participants with their priorities and any existing Force-Zuteilung. - Verified against local Postgres: PATCH toggles isOpen, teilnehmer list returns, CSV export works. (A stale dev server on :3000 masked this at first — real routes are fine.) Client (client/app/): - Wahl detail: open/close switch, participant list with a "Zuteilen" (Force-Zuteilung) action, CSV export via a browser download (browser.downloadText). - files_admin_screen.dart: LT file upload — browser.pickFile() + visibility picker -> multipart POST /api/files/:kcId; list existing files. Reachable from KcDetailScreen. - browser_web.dart gains pickFile()/downloadText() (native <input file> + Blob), with throwing stubs for the VM. - Dropped the file_picker package again (heavy transitive deps, and the native web input is enough); disk on this box is nearly full. flutter analyze/test/build web green; backend npm test 56. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
64 lines
2.1 KiB
Dart
64 lines
2.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:js_interop';
|
|
|
|
import 'package:web/web.dart' as web;
|
|
|
|
/// Per-tab storage for the PKCE verifier + CSRF state (cleared when the tab
|
|
/// closes), and the little bit of `window` access the OIDC redirect needs.
|
|
|
|
void setSession(String key, String value) =>
|
|
web.window.sessionStorage.setItem(key, value);
|
|
|
|
String? getSession(String key) => web.window.sessionStorage.getItem(key);
|
|
|
|
void removeSession(String key) => web.window.sessionStorage.removeItem(key);
|
|
|
|
void redirect(String url) => web.window.location.assign(url);
|
|
|
|
Map<String, String> currentQueryParameters() =>
|
|
Uri.parse(web.window.location.href).queryParameters;
|
|
|
|
/// Drop the OIDC callback path + `?code=…&state=…` from the address bar
|
|
/// without reloading (back to the app root).
|
|
void clearQuery() {
|
|
web.window.history.replaceState(null, '', '/');
|
|
}
|
|
|
|
/// Opens the OS file picker and reads the chosen file's bytes.
|
|
Future<({String name, List<int> bytes})?> pickFile() {
|
|
final completer = Completer<({String name, List<int> bytes})?>();
|
|
final input = web.HTMLInputElement()..type = 'file';
|
|
input.onchange = ((web.Event _) {
|
|
final files = input.files;
|
|
if (files == null || files.length == 0) {
|
|
completer.complete(null);
|
|
return;
|
|
}
|
|
final file = files.item(0)!;
|
|
final reader = web.FileReader();
|
|
reader.onload = ((web.Event _) {
|
|
final buffer = (reader.result as JSArrayBuffer).toDart;
|
|
completer.complete((name: file.name, bytes: buffer.asUint8List()));
|
|
}).toJS;
|
|
reader.onerror = ((web.Event _) => completer.complete(null)).toJS;
|
|
reader.readAsArrayBuffer(file);
|
|
}).toJS;
|
|
input.click();
|
|
return completer.future;
|
|
}
|
|
|
|
/// Triggers a browser download of an in-memory string (e.g. the CSV export).
|
|
void downloadText(
|
|
String filename,
|
|
String content, {
|
|
String mime = 'text/csv;charset=utf-8',
|
|
}) {
|
|
final blob = web.Blob([content.toJS].toJS, web.BlobPropertyBag(type: mime));
|
|
final url = web.URL.createObjectURL(blob);
|
|
web.HTMLAnchorElement()
|
|
..href = url
|
|
..download = filename
|
|
..click();
|
|
web.URL.revokeObjectURL(url);
|
|
}
|