normalize data
This commit is contained in:
@@ -45,8 +45,8 @@ class _MainAppState extends State<MainApp> {
|
||||
}
|
||||
|
||||
void _applyState(TimelineState state) {
|
||||
final groups = _convertGroups(state.timeline.groups);
|
||||
final entries = _convertEntries(state.timeline.groups);
|
||||
final groups = _convertGroups(state);
|
||||
final entries = _convertEntries(state);
|
||||
final domain = _computeDomain(entries);
|
||||
|
||||
setState(() {
|
||||
@@ -63,32 +63,35 @@ class _MainAppState extends State<MainApp> {
|
||||
_emitContentHeight();
|
||||
}
|
||||
|
||||
List<TimelineGroup> _convertGroups(List<TimelineGroupData> groups) {
|
||||
return [for (final g in groups) TimelineGroup(id: g.id, title: g.title)];
|
||||
/// Build an ordered list of [TimelineGroup] using [groupOrder].
|
||||
List<TimelineGroup> _convertGroups(TimelineState state) {
|
||||
return [
|
||||
for (final id in state.groupOrder)
|
||||
if (state.groups[id] case final g?)
|
||||
TimelineGroup(id: g.id, title: g.title),
|
||||
];
|
||||
}
|
||||
|
||||
List<TimelineEntry> _convertEntries(List<TimelineGroupData> groups) {
|
||||
final entries = <TimelineEntry>[];
|
||||
for (final group in groups) {
|
||||
for (final item in group.items) {
|
||||
final start = DateTime.parse(item.start);
|
||||
final end = item.end != null
|
||||
? DateTime.parse(item.end!)
|
||||
: start.add(const Duration(days: 1));
|
||||
/// Build a flat list of [TimelineEntry] from the normalized items map.
|
||||
List<TimelineEntry> _convertEntries(TimelineState state) {
|
||||
return [
|
||||
for (final item in state.items.values)
|
||||
() {
|
||||
final start = DateTime.parse(item.start);
|
||||
final end = item.end != null
|
||||
? DateTime.parse(item.end!)
|
||||
: start.add(const Duration(days: 1));
|
||||
|
||||
entries.add(
|
||||
TimelineEntry(
|
||||
return TimelineEntry(
|
||||
id: item.id,
|
||||
groupId: group.id,
|
||||
groupId: item.groupId,
|
||||
start: start,
|
||||
end: end,
|
||||
lane: item.lane,
|
||||
hasEnd: item.end != null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
);
|
||||
}(),
|
||||
];
|
||||
}
|
||||
|
||||
({DateTime start, DateTime end}) _computeDomain(List<TimelineEntry> entries) {
|
||||
@@ -156,16 +159,9 @@ class _MainAppState extends State<MainApp> {
|
||||
emitEvent('content_height', {'height': totalHeight});
|
||||
}
|
||||
|
||||
/// O(1) label lookup from the normalized items map.
|
||||
String _labelForEntry(TimelineEntry entry) {
|
||||
final state = _state;
|
||||
if (state == null) return entry.id;
|
||||
|
||||
for (final group in state.timeline.groups) {
|
||||
for (final item in group.items) {
|
||||
if (item.id == entry.id) return item.title;
|
||||
}
|
||||
}
|
||||
return entry.id;
|
||||
return _state?.items[entry.id]?.title ?? entry.id;
|
||||
}
|
||||
|
||||
static const _groupColors = [
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
// Bridge data-transfer types deserialized from JSON pushed by the React host.
|
||||
//
|
||||
// The shape is normalized: groups and items are stored as maps keyed by
|
||||
// ID, with groupOrder preserving display ordering.
|
||||
//
|
||||
// Keep in sync with `apps/web/src/lib/flutter-bridge.ts`.
|
||||
|
||||
class TimelineState {
|
||||
final TimelineData timeline;
|
||||
final Map<String, TimelineGroupData> groups;
|
||||
final Map<String, TimelineItemData> items;
|
||||
final List<String> groupOrder;
|
||||
final String? selectedItemId;
|
||||
|
||||
TimelineState({required this.timeline, this.selectedItemId});
|
||||
TimelineState({
|
||||
required this.timeline,
|
||||
required this.groups,
|
||||
required this.items,
|
||||
required this.groupOrder,
|
||||
this.selectedItemId,
|
||||
});
|
||||
|
||||
factory TimelineState.fromJson(Map<String, dynamic> json) {
|
||||
final rawGroups = json['groups'] as Map<String, dynamic>;
|
||||
final rawItems = json['items'] as Map<String, dynamic>;
|
||||
|
||||
return TimelineState(
|
||||
timeline: TimelineData.fromJson(json['timeline'] as Map<String, dynamic>),
|
||||
groups: rawGroups.map(
|
||||
(k, v) =>
|
||||
MapEntry(k, TimelineGroupData.fromJson(v as Map<String, dynamic>)),
|
||||
),
|
||||
items: rawItems.map(
|
||||
(k, v) =>
|
||||
MapEntry(k, TimelineItemData.fromJson(v as Map<String, dynamic>)),
|
||||
),
|
||||
groupOrder: (json['groupOrder'] as List<dynamic>).cast<String>(),
|
||||
selectedItemId: json['selectedItemId'] as String?,
|
||||
);
|
||||
}
|
||||
@@ -15,17 +43,13 @@ class TimelineState {
|
||||
class TimelineData {
|
||||
final String id;
|
||||
final String title;
|
||||
final List<TimelineGroupData> groups;
|
||||
|
||||
TimelineData({required this.id, required this.title, required this.groups});
|
||||
TimelineData({required this.id, required this.title});
|
||||
|
||||
factory TimelineData.fromJson(Map<String, dynamic> json) {
|
||||
return TimelineData(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
groups: (json['groups'] as List<dynamic>)
|
||||
.map((g) => TimelineGroupData.fromJson(g as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,13 +58,11 @@ class TimelineGroupData {
|
||||
final String id;
|
||||
final String title;
|
||||
final int sortOrder;
|
||||
final List<TimelineItemData> items;
|
||||
|
||||
TimelineGroupData({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.sortOrder,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
factory TimelineGroupData.fromJson(Map<String, dynamic> json) {
|
||||
@@ -48,15 +70,13 @@ class TimelineGroupData {
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
sortOrder: json['sortOrder'] as int,
|
||||
items: (json['items'] as List<dynamic>)
|
||||
.map((i) => TimelineItemData.fromJson(i as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineItemData {
|
||||
final String id;
|
||||
final String groupId;
|
||||
final String title;
|
||||
final String? description;
|
||||
final String start;
|
||||
@@ -65,6 +85,7 @@ class TimelineItemData {
|
||||
|
||||
TimelineItemData({
|
||||
required this.id,
|
||||
required this.groupId,
|
||||
required this.title,
|
||||
this.description,
|
||||
required this.start,
|
||||
@@ -75,6 +96,7 @@ class TimelineItemData {
|
||||
factory TimelineItemData.fromJson(Map<String, dynamic> json) {
|
||||
return TimelineItemData(
|
||||
id: json['id'] as String,
|
||||
groupId: json['groupId'] as String,
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String?,
|
||||
start: json['start'] as String,
|
||||
|
||||
Reference in New Issue
Block a user