Files
tatlock-ui/lib/routing/url_state.dart
T
Jeroen SchweitzerandClaude Opus 4.5 c494ace5d8
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m5s
feat: add URL deep-linking for DataGrids
- Add PageUrlState utility for URL ↔ state serialization
- Add column `id` field for unique column identification in URLs
- Update idSelector to return String for URL compatibility
- All DataGrid pages now support URL params: search, sort, order, id
- Browser URL updates via replaceState (no GoRouter rebuilds)
- Add FilterPanelSemantics for filter panel semantic IDs
- Add TESTING.md documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 13:59:08 +01:00

99 lines
2.8 KiB
Dart

import 'package:flutter/foundation.dart' show kIsWeb;
// Conditional import for web-only functionality
import 'url_state_stub.dart' if (dart.library.js_interop) 'url_state_web.dart'
as platform;
/// Parses and builds URL query parameters for page state.
///
/// Used to enable deep-linking for DataGrid and filter panel state.
/// Query params are read once on page load and updated via browser
/// replaceState to avoid GoRouter rebuild loops.
class PageUrlState {
const PageUrlState({
this.id,
this.filter,
this.search,
this.sortColumn,
this.sortDescending = false,
});
/// Opened document ID (view/edit mode).
final String? id;
/// Filter panel search value.
final String? filter;
/// DataGrid search value.
final String? search;
/// Column ID for sorting.
final String? sortColumn;
/// Sort direction (true = descending).
final bool sortDescending;
/// Parse from query parameters map.
factory PageUrlState.fromQueryParams(Map<String, String> params) {
return PageUrlState(
id: params['id'],
filter: params['filter'],
search: params['search'],
sortColumn: params['sort'],
sortDescending: params['order'] == 'desc',
);
}
/// Convert to query parameter map (omits empty/default values).
Map<String, String> toQueryParams() {
return {
if (id != null && id!.isNotEmpty) 'id': id!,
if (filter != null && filter!.isNotEmpty) 'filter': filter!,
if (search != null && search!.isNotEmpty) 'search': search!,
if (sortColumn != null && sortColumn!.isNotEmpty) 'sort': sortColumn!,
if (sortDescending) 'order': 'desc',
};
}
/// Create a copy with modified values.
PageUrlState copyWith({
String? id,
String? filter,
String? search,
String? sortColumn,
bool? sortDescending,
bool clearId = false,
}) {
return PageUrlState(
id: clearId ? null : (id ?? this.id),
filter: filter ?? this.filter,
search: search ?? this.search,
sortColumn: sortColumn ?? this.sortColumn,
sortDescending: sortDescending ?? this.sortDescending,
);
}
/// Whether any state is present.
bool get isEmpty =>
id == null &&
(filter == null || filter!.isEmpty) &&
(search == null || search!.isEmpty) &&
sortColumn == null;
@override
String toString() =>
'PageUrlState(id: $id, filter: $filter, search: $search, '
'sort: $sortColumn, desc: $sortDescending)';
}
/// Updates browser URL with query parameters without triggering navigation.
///
/// Uses browser's replaceState API on web, no-op on other platforms.
/// This allows the URL to stay in sync for bookmarking/sharing without
/// causing Flutter to rebuild.
void updateBrowserUrlParams(Map<String, String> params) {
if (kIsWeb) {
platform.updateBrowserUrlParams(params);
}
}