-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add an "ide" component #13708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ericwindmill
wants to merge
25
commits into
main
Choose a base branch
from
ide-component
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add an "ide" component #13708
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
59af3f3
initial checkin
ericwindmill 675e7ac
Merge branch 'main' of https://github.com/flutter/website into ide-co…
ericwindmill 78a396d
fix filename
ericwindmill 66d2e40
checkin some refactoring
ericwindmill ae00cb1
rework data format
ericwindmill 485f2ab
start refactor
ericwindmill 022ccaf
start refactor
ericwindmill abac75d
more refactor
ericwindmill 9fce03b
more refactor
ericwindmill d8b7734
more refactor
ericwindmill d13168d
more refactor
ericwindmill 1862e74
more refactor
ericwindmill 1373068
more refactor
ericwindmill f083b82
tidy scss
ericwindmill e944483
fix scss funkiness
ericwindmill 95d3c93
more refactor
ericwindmill 6eb79db
more refactor
ericwindmill 2ff76c9
more refactor
ericwindmill 68a0f0c
Merge branch 'main' of https://github.com/flutter/website into ide-co…
ericwindmill 63e6fdc
Merge branch 'main' of https://github.com/flutter/website into ide-co…
ericwindmill e7c3351
address code review
ericwindmill 433d743
Merge branch 'main' of https://github.com/flutter/website into ide-co…
ericwindmill 3d280b1
Merge branch 'main' of https://github.com/flutter/website into ide-co…
ericwindmill a1d8866
oops
ericwindmill cc7535e
Merge branch 'main' into ide-component
ericwindmill File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
533 changes: 533 additions & 0 deletions
533
packages/site_shared/lib/_sass/components/_ide-explorer.scss
Large diffs are not rendered by default.
Oops, something went wrong.
707 changes: 707 additions & 0 deletions
707
packages/site_shared/lib/components/common/ide_explorer/ide_explorer.dart
Large diffs are not rendered by default.
Oops, something went wrong.
241 changes: 241 additions & 0 deletions
241
packages/site_shared/lib/components/common/ide_explorer/markdown_component.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| import 'package:jaspr/jaspr.dart'; | ||
| import 'package:jaspr_content/jaspr_content.dart'; | ||
|
|
||
| import '../../../util.dart'; | ||
| import 'ide_explorer.dart'; | ||
|
|
||
| /// A custom markdown component that parses `<IdeExplorer>` and its | ||
| /// `<IdeProjectRoot>`, `<IdeFolder>`, and `<IdePage>` children. Defers | ||
| /// building the IDE html to the [IdeExplorer] component. | ||
| class IdeExplorerMarkdownComponent extends CustomComponent { | ||
| const IdeExplorerMarkdownComponent() : super.base(); | ||
|
|
||
| // Tag name constants | ||
| static const String _tagIdeExplorer = 'IdeExplorer'; | ||
| static const String _tagIdeProjectRoot = 'IdeProjectRoot'; | ||
| static const String _tagIdeFolder = 'IdeFolder'; | ||
| static const String _tagIdePage = 'IdePage'; | ||
|
|
||
| // Attribute name constants | ||
| static const String _attrId = 'id'; | ||
| static const String _attrLabel = 'label'; | ||
| static const String _attrIsDefaultPage = 'is-default-page'; | ||
| static const String _attrStartsClosed = 'starts-closed'; | ||
| static const String _attrBadge = 'badge'; | ||
| static const String _attrBadgeColor = 'badge-color'; | ||
| static const String _attrSubtitle = 'subtitle'; | ||
|
|
||
| // Default values | ||
| static const String _defaultRootPrefix = 'root'; | ||
| static const String _defaultNodePrefix = 'node'; | ||
|
|
||
| @override | ||
| Component? create(Node node, NodesBuilder builder) { | ||
| if (node is! ElementNode || node.tag != _tagIdeExplorer) { | ||
| return null; | ||
| } | ||
|
|
||
| final projectRootElements = node.children | ||
| ?.whereType<ElementNode>() | ||
| .where((n) => n.tag == _tagIdeProjectRoot) | ||
| .toList(); | ||
|
|
||
| if (projectRootElements == null || projectRootElements.isEmpty) { | ||
| print( | ||
| '[ERROR] <$_tagIdeExplorer> requires at ' | ||
| 'least one <$_tagIdeProjectRoot> child element.', | ||
| ); | ||
| return const Component.empty(); | ||
| } | ||
|
|
||
| final customContents = <String, Component>{}; | ||
| final roots = <IdeExplorerProjectRoot>[]; | ||
| for (final (index, rootEl) in projectRootElements.indexed) { | ||
| final rootId = _generateNodeId( | ||
| rootEl.attributes, | ||
| _defaultRootPrefix, | ||
| index, | ||
| ); | ||
| roots.add( | ||
| IdeExplorerProjectRoot( | ||
| id: rootId, | ||
| label: rootEl.attributes[_attrLabel] ?? '', | ||
| children: _parseTreeNodes( | ||
| rootEl.children, | ||
| builder, | ||
| customContents, | ||
| rootId, | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| return IdeExplorer( | ||
| roots: roots, | ||
| customContents: customContents, | ||
| ); | ||
| } | ||
|
|
||
| /// Generates a node ID from attributes or creates a default one. | ||
| String _generateNodeId( | ||
| Map<String, String> attributes, | ||
| String prefix, | ||
| int index, [ | ||
| String? parentId, | ||
| ]) { | ||
| if (attributes[_attrId] != null) { | ||
| return attributes[_attrId]!; | ||
| } | ||
| final label = attributes[_attrLabel]; | ||
| final localId = (label != null && label.isNotEmpty) | ||
| ? slugify(label) | ||
| : '$prefix-$index'; | ||
| return parentId != null ? '$parentId-$localId' : localId; | ||
| } | ||
|
ericwindmill marked this conversation as resolved.
|
||
|
|
||
| List<IdeTreeNode> _parseTreeNodes( | ||
| List<Node>? nodes, | ||
| NodesBuilder builder, | ||
| Map<String, Component> customContents, | ||
| String parentId, | ||
| ) { | ||
| if (nodes == null || nodes.isEmpty) return const []; | ||
|
|
||
| final result = <IdeTreeNode>[]; | ||
|
|
||
| for (final (index, child) in nodes.whereType<ElementNode>().indexed) { | ||
| if (child.tag != _tagIdeFolder && child.tag != _tagIdePage) { | ||
| continue; | ||
| } | ||
|
|
||
| final treeNode = _buildTreeNodeFromElement( | ||
| child, | ||
| index, | ||
| builder, | ||
| customContents, | ||
| parentId, | ||
| ); | ||
|
|
||
| result.add(treeNode); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
ericwindmill marked this conversation as resolved.
|
||
|
|
||
| /// Builds a single [IdeTreeNode] from an [ElementNode]. | ||
| IdeTreeNode _buildTreeNodeFromElement( | ||
| ElementNode element, | ||
| int index, | ||
| NodesBuilder builder, | ||
| Map<String, Component> customContents, | ||
| String parentId, | ||
| ) { | ||
| final attributes = element.attributes; | ||
| final label = attributes[_attrLabel] ?? ''; | ||
| final id = _generateNodeId(attributes, _defaultNodePrefix, index, parentId); | ||
|
|
||
| // Parse boolean attributes | ||
| final isDefaultPage = _getBoolAttribute( | ||
| attributes, | ||
| _attrIsDefaultPage, | ||
| defaultValue: false, | ||
| ); | ||
| final startsClosed = _getBoolAttribute( | ||
| attributes, | ||
| _attrStartsClosed, | ||
| defaultValue: true, | ||
| ); | ||
|
|
||
| // Parse badge attributes | ||
| final badge = attributes[_attrBadge]; | ||
| final badgeColor = attributes[_attrBadgeColor] != null | ||
| ? IdeBadgeColor.fromString(attributes[_attrBadgeColor]) | ||
| : null; | ||
|
|
||
| final subtitle = attributes[_attrSubtitle]; | ||
|
|
||
| // Recursively parse children | ||
| final nestedTreeNodes = _parseTreeNodes( | ||
| element.children, | ||
| builder, | ||
| customContents, | ||
| id, | ||
| ); | ||
|
ericwindmill marked this conversation as resolved.
|
||
|
|
||
| // Extract and store custom body content if present | ||
| _storeCustomContentIfPresent( | ||
| element.children, | ||
| id, | ||
| builder, | ||
| customContents, | ||
| ); | ||
|
|
||
| return IdeTreeNode( | ||
| id: id, | ||
| label: label, | ||
| isDefaultPage: isDefaultPage, | ||
| startsClosed: startsClosed, | ||
| badge: badge, | ||
| badgeColor: badgeColor, | ||
| subtitle: subtitle, | ||
| children: nestedTreeNodes, | ||
| ); | ||
| } | ||
|
|
||
| /// Parses a boolean attribute value, returning [defaultValue] if not present. | ||
| bool _getBoolAttribute( | ||
| Map<String, String> attributes, | ||
| String key, { | ||
| required bool defaultValue, | ||
| }) { | ||
| final value = attributes[key]; | ||
| return value != null ? value == 'true' : defaultValue; | ||
| } | ||
|
|
||
| /// Checks if a node represents body content (not a folder/page structure). | ||
| bool _isBodyContent(Node node) { | ||
| if (node is ElementNode && | ||
| (node.tag == _tagIdeFolder || node.tag == _tagIdePage)) { | ||
| return false; | ||
| } | ||
| if (node is TextNode && node.text.trim().isEmpty) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /// Extracts non-structural content nodes from children. | ||
| List<Node> _extractContentNodes(List<Node> children) { | ||
| return children | ||
| .where((n) { | ||
| if (n is ElementNode && | ||
| (n.tag == _tagIdeFolder || n.tag == _tagIdePage)) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }) | ||
| .toList(growable: false); | ||
| } | ||
|
|
||
| /// Stores custom body content for a node if it exists. | ||
| void _storeCustomContentIfPresent( | ||
| List<Node>? children, | ||
| String nodeId, | ||
| NodesBuilder builder, | ||
| Map<String, Component> customContents, | ||
| ) { | ||
| if (!_hasCustomBodyContent(children)) { | ||
| return; | ||
| } | ||
|
|
||
| final contentNodes = _extractContentNodes(children!); | ||
| if (contentNodes.isNotEmpty) { | ||
| customContents[nodeId] = builder.build(contentNodes); | ||
| } | ||
| } | ||
|
|
||
| /// Checks if a node has custom body content. | ||
| bool _hasCustomBodyContent(List<Node>? children) { | ||
| return children?.any(_isBodyContent) ?? false; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.