-
Notifications
You must be signed in to change notification settings - Fork 3.4k
FWE clean up #12900
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
base: main
Are you sure you want to change the base?
FWE clean up #12900
Changes from all commits
83a86aa
86d9539
1772960
cf4a122
3124286
ad0405f
e7900c7
573f522
e2383b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Styles for TutorialLesson component sections. | ||
|
|
||
| .tutorial-lesson { | ||
| // Container for the entire tutorial lesson | ||
|
|
||
| .tutorial-intro { | ||
| margin: 1rem 0; | ||
|
|
||
| .description { | ||
| font-size: 1.25rem; | ||
| font-weight: 600; | ||
| margin-bottom: 1rem; | ||
| } | ||
|
|
||
| .intro-video { | ||
| display: flex; | ||
| justify-content: center; | ||
| background-color: var(--site-diagram-wrap-bgColor); | ||
| padding: 1rem; | ||
| border-radius: 1rem; | ||
|
|
||
| margin-bottom: 1.5rem; | ||
| } | ||
| } | ||
|
|
||
| .tutorial-steps { | ||
| margin: 2rem; | ||
| } | ||
|
|
||
| .tutorial-divider { | ||
| border: none; | ||
| border-top: 1px solid var(--site-inset-borderColor); | ||
| margin: 3rem 1rem; | ||
| } | ||
|
|
||
| img { | ||
| display:flex; | ||
| justify-self: center; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| // Copyright 2025 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'package:jaspr/dom.dart'; | ||
| import 'package:jaspr/jaspr.dart'; | ||
| import 'package:jaspr_content/jaspr_content.dart'; | ||
|
|
||
| /// A component that provides the structure for a tutorial lesson page. | ||
| class TutorialLesson extends CustomComponent { | ||
| const TutorialLesson() : super.base(); | ||
|
|
||
| @override | ||
| Component? create(Node node, NodesBuilder builder) { | ||
| if (node case ElementNode(tag: 'TutorialLesson', :final children?)) { | ||
| List<Node>? introContent; | ||
| List<Node>? stepsContent; | ||
| List<Node>? outroContent; | ||
|
|
||
| for (final child in children) { | ||
| if (child case ElementNode(tag: 'TutorialIntro', :final children?)) { | ||
| introContent = children; | ||
| } else if (child case ElementNode( | ||
| tag: 'TutorialSteps', | ||
| :final children?, | ||
| )) { | ||
| stepsContent = children; | ||
| } else if (child case ElementNode( | ||
| tag: 'TutorialOutro', | ||
| :final children?, | ||
| )) { | ||
| outroContent = children; | ||
| } | ||
| } | ||
|
|
||
| return section(classes: 'tutorial-lesson', [ | ||
| // Intro section | ||
| if (introContent != null) | ||
| section(classes: 'tutorial-intro', [ | ||
| ..._buildIntroContent(introContent, builder), | ||
| ]), | ||
| // Divider between intro and steps | ||
| if (introContent != null && stepsContent != null) | ||
| const hr(classes: 'tutorial-divider'), | ||
| // Steps section | ||
| if (stepsContent != null) | ||
| section(classes: 'tutorial-steps', [ | ||
| const h2([.text('Steps')]), | ||
| // Wrap steps content in a Stepper with level="3" | ||
| builder.build([ | ||
| ElementNode('Stepper', {'level': '3'}, stepsContent), | ||
| ]), | ||
| ]), | ||
| // Outro section | ||
| if (outroContent != null) | ||
| section(classes: 'tutorial-outro', [ | ||
| builder.build(outroContent), | ||
| ]), | ||
|
Comment on lines
+55
to
+58
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is likely unneeded (and currently not being used at all). I will remove it before merge |
||
| ]); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /// Builds the intro content, automatically wrapping: | ||
| /// - Paragraph elements (`<p>`) get 'description' class added | ||
| /// - YouTubeEmbed elements in a div with class 'intro-video' | ||
| List<Component> _buildIntroContent(List<Node> content, NodesBuilder builder) { | ||
| final wrappedContent = <Component>[]; | ||
|
|
||
| for (final node in content) { | ||
| if (node case ElementNode(tag: 'p', :final children)) { | ||
| // Check if paragraph contains only a video embed | ||
| final videoChild = _findVideoChild(children); | ||
| if (videoChild != null) { | ||
| // Extract video from paragraph and wrap in intro-video div | ||
| wrappedContent.add( | ||
| div(classes: 'intro-video', [ | ||
| builder.build([videoChild]), | ||
| ]), | ||
| ); | ||
| } else { | ||
| // Regular paragraph - add description class | ||
| wrappedContent.add( | ||
| p(classes: 'description', [builder.build(children ?? [])]), | ||
| ); | ||
| } | ||
| } else if (_isVideoEmbed(node)) { | ||
| // Direct video embed (not wrapped in paragraph) | ||
| wrappedContent.add( | ||
| div(classes: 'intro-video', [ | ||
| builder.build([node]), | ||
| ]), | ||
| ); | ||
| } else { | ||
| // Pass through other elements (SummaryCard, comments, etc.) | ||
| wrappedContent.add(builder.build([node])); | ||
| } | ||
| } | ||
|
|
||
| return wrappedContent; | ||
| } | ||
|
|
||
| /// Checks if a node is a YouTube video embed | ||
| bool _isVideoEmbed(Node node) { | ||
| if (node case ElementNode(tag: final tag)) { | ||
| final lowerTag = tag.toLowerCase(); | ||
| return lowerTag == 'youtubeembed' || lowerTag == 'lite-youtube'; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /// Finds a video embed child in a list of nodes, returns it if it's the only | ||
| /// meaningful content (ignoring whitespace text nodes) | ||
| Node? _findVideoChild(List<Node>? children) { | ||
| if (children == null) return null; | ||
|
|
||
| Node? videoChild; | ||
| for (final child in children) { | ||
| if (_isVideoEmbed(child)) { | ||
| videoChild = child; | ||
| } else if (child case TextNode(:final text)) { | ||
| // Ignore whitespace-only text nodes | ||
| if (text.trim().isNotEmpty) return null; | ||
| } else { | ||
| // Non-video, non-whitespace content found | ||
| return null; | ||
| } | ||
| } | ||
| return videoChild; | ||
| } | ||
| } | ||
|
Comment on lines
+105
to
+132
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was written by gemini. It feels wrong to me, but I'm not sure what the proper Jaspr way to accomplish this is. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure this isn't idiomatic. Maybe this shouldn't be here at all. It felt better than putting a bunch of in every markdown file. Is there a better way?