Skip to content

Commit

Permalink
Content tree navigation (#23593)
Browse files Browse the repository at this point in the history
Unit content navigation (#23593)

Update URL on node click (#23593)

Active unit color (#23593)

removeListener in unit (#23593)

First unit is opened on group title click (#23593)

WIP by Alexey Inkin (#23593)

selectedUnitColor (#23593)

Unit borderRadius (#23593)

RegExp todo (#23593)

added referenced collection package to remove warning (#23593)

small refinement (#23593)

expand on group tap, padding, openNode (#23593)

group expansion bug fix (#23593)

selected & unselected progress indicators (#23593)
  • Loading branch information
darkhan.nausharipov authored and nausharipov committed Nov 4, 2022
1 parent d407b60 commit 7c23033
Show file tree
Hide file tree
Showing 20 changed files with 300 additions and 101 deletions.
29 changes: 0 additions & 29 deletions learning/tour-of-beam/frontend/lib/components/filler_text.dart

This file was deleted.

14 changes: 12 additions & 2 deletions learning/tour-of-beam/frontend/lib/models/content_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,25 @@

import '../repositories/models/get_content_tree_response.dart';
import 'module.dart';
import 'node.dart';
import 'parent_node.dart';

class ContentTreeModel {
class ContentTreeModel extends ParentNodeModel {
final String sdkId;
final List<ModuleModel> modules;

@override
List<NodeModel> get nodes => modules;

const ContentTreeModel({
required this.sdkId,
required this.modules,
});
}) : super(
id: sdkId,
parent: null,
title: '',
nodes: modules,
);

ContentTreeModel.fromResponse(GetContentTreeResponse response)
: this(
Expand Down
29 changes: 21 additions & 8 deletions learning/tour-of-beam/frontend/lib/models/group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,28 @@ import 'parent_node.dart';
class GroupModel extends ParentNodeModel {
const GroupModel({
required super.id,
required super.title,
required super.nodes,
required super.parent,
required super.title,
});

GroupModel.fromResponse(GroupResponseModel group)
: super(
id: group.id,
title: group.title,
nodes:
group.nodes.map(NodeModel.fromResponse).toList(growable: false),
);
factory GroupModel.fromResponse(
GroupResponseModel groupResponse,
ParentNodeModel parent,
) {
final group = GroupModel(
id: groupResponse.id,
nodes: [],
parent: parent,
title: groupResponse.title,
);

group.nodes.addAll(
groupResponse.nodes.map<NodeModel>(
(node) => NodeModel.fromResponse(node, group),
),
);

return group;
}
}
29 changes: 19 additions & 10 deletions learning/tour-of-beam/frontend/lib/models/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ class ModuleModel extends ParentNodeModel {

const ModuleModel({
required super.id,
required super.title,
required super.nodes,
required super.parent,
required super.title,
required this.complexity,
});

ModuleModel.fromResponse(ModuleResponseModel module)
: complexity = module.complexity,
super(
id: module.id,
title: module.title,
nodes: module.nodes
.map<NodeModel>(NodeModel.fromResponse)
.toList(growable: false),
);
factory ModuleModel.fromResponse(ModuleResponseModel moduleResponse) {
final module = ModuleModel(
complexity: moduleResponse.complexity,
nodes: [],
id: moduleResponse.id,
parent: null,
title: moduleResponse.title,
);

module.nodes.addAll(
moduleResponse.nodes.map<NodeModel>(
(node) => NodeModel.fromResponse(node, module),
),
);

return module;
}
}
20 changes: 15 additions & 5 deletions learning/tour-of-beam/frontend/lib/models/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
import '../repositories/models/node.dart';
import '../repositories/models/node_type_enum.dart';
import 'group.dart';
import 'parent_node.dart';
import 'unit.dart';

abstract class NodeModel {
final String id;
final String title;
final NodeModel? parent;

const NodeModel({
required this.id,
required this.title,
required this.parent,
});

/// Constructs nodes from the response data.
Expand All @@ -36,20 +39,27 @@ abstract class NodeModel {
/// because they come from a golang backend which does not
/// support inheritance, and so they use an extra layer of composition
/// which is inconvenient in Flutter.
static List<NodeModel> fromMaps(List json) {
static List<NodeModel> fromMaps(List json, ParentNodeModel parent) {
return json
.cast<Map<String, dynamic>>()
.map<NodeResponseModel>(NodeResponseModel.fromJson)
.map(fromResponse)
.map((nodeResponse) => fromResponse(nodeResponse, parent))
.toList();
}

static NodeModel fromResponse(NodeResponseModel node) {
static NodeModel fromResponse(
NodeResponseModel node,
ParentNodeModel parent,
) {
switch (node.type) {
case NodeType.group:
return GroupModel.fromResponse(node.group!);
return GroupModel.fromResponse(node.group!, parent);
case NodeType.unit:
return UnitModel.fromResponse(node.unit!);
return UnitModel.fromResponse(node.unit!, parent);
}
}

NodeModel getFirstUnit();

NodeModel? getNodeByTreeIds(List<String> treeIds);
}
18 changes: 18 additions & 0 deletions learning/tour-of-beam/frontend/lib/models/parent_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,32 @@
* limitations under the License.
*/

import 'package:collection/collection.dart';

import 'node.dart';

abstract class ParentNodeModel extends NodeModel {
final List<NodeModel> nodes;

const ParentNodeModel({
required super.id,
required super.parent,
required super.title,
required this.nodes,
});

@override
NodeModel getFirstUnit() => nodes[0].getFirstUnit();

@override
NodeModel? getNodeByTreeIds(List<String> treeIds) {
final firstId = treeIds.firstOrNull;
final child = nodes.firstWhereOrNull((node) => node.id == firstId);

if (child == null) {
return null;
}

return child.getNodeByTreeIds(treeIds.sublist(1));
}
}
15 changes: 13 additions & 2 deletions learning/tour-of-beam/frontend/lib/models/unit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,19 @@

import '../repositories/models/unit.dart';
import 'node.dart';
import 'parent_node.dart';

class UnitModel extends NodeModel {
UnitModel.fromResponse(UnitResponseModel unit)
: super(id: unit.id, title: unit.title);
UnitModel.fromResponse(UnitResponseModel unit, ParentNodeModel parent)
: super(
id: unit.id,
parent: parent,
title: unit.title,
);

@override
NodeModel getFirstUnit() => this;

@override
NodeModel? getNodeByTreeIds(List<String> treeIds) => this;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,82 @@
*/

import 'package:flutter/widgets.dart';
import 'package:get_it/get_it.dart';
import 'package:playground_components/playground_components.dart';

import '../../../cache/content_tree.dart';
import '../../../models/group.dart';
import '../../../models/node.dart';
import '../../../models/unit.dart';

class ContentTreeController extends ChangeNotifier {
String _sdkId;
List<String> _treeIds;
NodeModel? _currentNode;
final _contentTreeCache = GetIt.instance.get<ContentTreeCache>();
final expandedIds = <String>{};

ContentTreeController({
required String initialSdkId,
List<String> initialTreeIds = const [],
}) : _sdkId = initialSdkId,
_treeIds = initialTreeIds;
_treeIds = initialTreeIds {
expandedIds.addAll(initialTreeIds);
_contentTreeCache.addListener(_onContentTreeCacheChange);
_onContentTreeCacheChange();
}

Sdk get sdk => Sdk.parseOrCreate(_sdkId);
String get sdkId => _sdkId;
List<String> get treeIds => _treeIds;
NodeModel? get currentNode => _currentNode;

void onNodeTap(NodeModel node) {
void openNode(NodeModel node) {
if (!expandedIds.contains(node.id)) {
expandedIds.add(node.id);
}

if (node == _currentNode) {
return;
}

_currentNode = node;
// TODO(alexeyinkin): Set _treeIds from node.
if (node is GroupModel) {
openNode(node.nodes.first);
} else if (node is UnitModel) {
_currentNode = node;
}

if (_currentNode != null) {
_treeIds = _getNodeAncestors(_currentNode!, [_currentNode!.id]);
}
notifyListeners();
}

List<String> _getNodeAncestors(NodeModel node, List<String> ancestors) {
if (node.parent != null) {
ancestors.add(node.parent!.id);
return _getNodeAncestors(node.parent!, ancestors);
} else {
return ancestors.reversed.toList();
}
}

void _onContentTreeCacheChange() {
final contentTree = _contentTreeCache.getContentTree(_sdkId);
if (contentTree == null) {
return;
}

openNode(
contentTree.getNodeByTreeIds(_treeIds) ?? contentTree.getFirstUnit(),
);

notifyListeners();
}

@override
void dispose() {
_contentTreeCache.removeListener(_onContentTreeCacheChange);
super.dispose();
}
}
9 changes: 7 additions & 2 deletions learning/tour-of-beam/frontend/lib/pages/tour/path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class TourPath extends PagePath {
final String sdkId;
final List<String> treeIds;

static final _regExp = RegExp(r'^/tour/([a-z]+)(/[/-a-zA-Z0-9]+)?$');
static final _regExp = RegExp(r'^/tour/([a-z]+)((/[-a-zA-Z0-9]+)*)$');

TourPath({
required this.sdkId,
Expand All @@ -47,7 +47,12 @@ class TourPath extends PagePath {
if (matches == null) return null;

final sdkId = matches[1] ?? (throw Error());
final treeIds = matches[2]?.split('/') ?? const [];
final treeIdsString = matches[2];

final treeIds = (treeIdsString == null)
? const <String>[]
// TODO(nausharipov): use RegExp to remove the slash
: treeIdsString.substring(1).split('/');

return TourPath(
sdkId: sdkId,
Expand Down
2 changes: 2 additions & 0 deletions learning/tour-of-beam/frontend/lib/pages/tour/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TourNotifier extends ChangeNotifier with PageStateMixin<void> {
playgroundController = _createPlaygroundController(initialSdkId) {
contentTreeController.addListener(_onChanged);
_unitContentCache.addListener(_onChanged);
_onChanged();
}

@override
Expand All @@ -53,6 +54,7 @@ class TourNotifier extends ChangeNotifier with PageStateMixin<void> {
);

void _onChanged() {
emitPathChanged();
final currentNode = contentTreeController.currentNode;
if (currentNode is UnitModel) {
final content = _unitContentCache.getUnitContent(
Expand Down
Loading

0 comments on commit 7c23033

Please sign in to comment.