Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion site/lib/_sass/_site.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@
@use 'components/card';
@use 'components/code';
@use 'components/content';
@use 'components/cookie-notice';
@use 'components/diagnostics';
@use 'components/dropdown';
@use 'components/filter-search';
@use 'components/cookie-notice';
@use 'components/footer';
@use 'components/form';
@use 'components/glossary';
@use 'components/header';
@use 'components/misc';
@use 'components/next-prev-nav';
@use 'components/quiz';
@use 'components/search';
@use 'components/side-menu';
@use 'components/sidenav';
Expand Down
4 changes: 2 additions & 2 deletions site/lib/_sass/base/_root.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ body {
--site-base-fgColor-alt: #6a6f71;

--site-raised-bgColor: #edf0f2;
--site-raised-bgColor-translucent: #{color.change(#edf0f2, $alpha: 0.1)};
--site-raised-bgColor-translucent: #{color.change(#edf0f2, $alpha: 0.2)};

--site-primary-color: #{$base_primary_color};
--site-accent-color: #833ef2;
Expand Down Expand Up @@ -113,7 +113,7 @@ body {
--site-base-fgColor-alt: #a8acad;

--site-raised-bgColor: #1c1e27;
--site-raised-bgColor-translucent: #{color.change(#1c1e27, $alpha: 0.1)};
--site-raised-bgColor-translucent: #{color.change(#1c1e27, $alpha: 0.4)};

--site-primary-color: #{$base_primary_color};
--site-accent-color: #b07fff;
Expand Down
146 changes: 146 additions & 0 deletions site/lib/_sass/components/_quiz.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
.quiz {
display: flex;
flex-direction: column;

background-color: var(--site-raised-bgColor-translucent);
border-radius: var(--site-radius);
padding: 1rem;

.quiz-title {
margin: 0;
font-size: 1.5rem;
font-weight: 500;
color: var(--site-base-fgColor-lighter);
}

.quiz-progress {
font-size: 0.9rem;
color: var(--site-primary-color);
font-weight: 500;
}

.quiz-question {
margin-top: 1.5rem;
display: none;

&.active {
display: block;
}

ol {
padding: 0;
margin: 1rem 0 0;
list-style: upper-alpha inside;

li {
padding: 1rem;
background-color: var(--site-raised-bgColor);
border-radius: var(--site-radius);
margin-bottom: 0.2rem;
transition: background-color 500ms;

&:not(:where([aria-pressed="true"], [aria-disabled="true"])):hover {
background-color: var(--site-inset-bgColor);
cursor: pointer;
}

&[aria-pressed="true"]:has(.correct) {
background-color: oklch(from var(--site-alert-tip-color) l c h / 0.2);
}

&[aria-pressed="true"]:has(.incorrect) {
background-color: oklch(from var(--site-alert-error-color) l c h / 0.2);
}

&:not([aria-pressed="true"])[aria-disabled="true"] {
opacity: 0.6;
}

p {
margin: 0;
}

.question-wrapper {
display: grid;
grid-template-rows: min-content 0fr;
transition: grid-template-rows 500ms;
}

&[aria-pressed="true"] .question-wrapper {
grid-template-rows: min-content 1fr;
}

.question {
margin-top: -1lh;
margin-left: 1.4rem;
}

.solution {
position: relative;
padding-left: 1.4rem;
font-size: 0.9rem;
overflow: hidden;

p.correct,
p.incorrect {
padding-top: 0.5rem;
font-weight: 600;
margin-bottom: 0.5rem;

&::before {
position: absolute;
left: 0;
}
}

p.correct {
color: var(--site-alert-tip-color);

&::before {
content: "✓";
}
}

p.incorrect {
color: var(--site-alert-error-color);

&::before {
content: "✗";
}
}
}
}
}

}

.quiz-complete {
min-height: 15rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

strong {
font-size: 2rem;
}
}

.quiz-actions {
display: flex;
justify-content: space-between;
margin-top: 1rem;

.quiz-button {
&.secondary {
background-color: var(--site-inset-bgColor);
color: var(--site-base-fgColor);
}

&[disabled] {
opacity: 0.4;
pointer-events: none;
}
}
}
}
25 changes: 24 additions & 1 deletion site/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:jaspr/client.dart';

import 'package:dart_dev_site/src/archive/archive_table.dart'
deferred as _archive_table;
import 'package:dart_dev_site/src/components/common/client/collapse_button.dart'
deferred as _collapse_button;
import 'package:dart_dev_site/src/components/common/client/cookie_notice.dart'
deferred as _cookie_notice;
import 'package:dart_dev_site/src/components/common/client/copy_button.dart'
Expand All @@ -28,6 +30,9 @@ import 'package:dart_dev_site/src/components/pages/glossary_search_section.dart'
deferred as _glossary_search_section;
import 'package:dart_dev_site/src/components/pages/lint_filter_search_section.dart'
deferred as _lint_filter_search_section;
import 'package:dart_dev_site/src/components/tutorial/client/quiz.dart'
deferred as _quiz;
import 'package:dart_dev_site/src/models/quiz_model.dart' as _quiz_model;

/// Default [ClientOptions] for use with your Jaspr project.
///
Expand All @@ -51,14 +56,21 @@ ClientOptions get defaultClientOptions => ClientOptions(
(p) => _archive_table.ArchiveTable(channel: p['channel'] as String),
loader: _archive_table.loadLibrary,
),
'collapse_button': ClientLoader(
(p) => _collapse_button.CollapseButton(
classes: (p['classes'] as List<Object?>).cast<String>(),
title: p['title'] as String?,
),
loader: _collapse_button.loadLibrary,
),
'cookie_notice': ClientLoader(
(p) => _cookie_notice.CookieNotice(),
loader: _cookie_notice.loadLibrary,
),
'copy_button': ClientLoader(
(p) => _copy_button.CopyButton(
toCopy: p['toCopy'] as String,
buttonText: p['buttonText'] as String?,
toCopy: p['toCopy'] as String?,
classes: (p['classes'] as List<Object?>).cast<String>(),
title: p['title'] as String?,
),
Expand Down Expand Up @@ -101,5 +113,16 @@ ClientOptions get defaultClientOptions => ClientOptions(
(p) => _lint_filter_search_section.LintFilterSearchSection(),
loader: _lint_filter_search_section.loadLibrary,
),
'quiz': ClientLoader(
(p) => _quiz.InteractiveQuiz(
title: p['title'] as String?,
questions: (p['questions'] as List<Object?>)
.map(
(i) => _quiz_model.Question.fromMap(i as Map<Object?, Object?>),
)
.toList(),
),
loader: _quiz.loadLibrary,
),
},
);
2 changes: 2 additions & 0 deletions site/lib/main.server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'src/archive/archive_table.dart';
import 'src/components/common/card.dart';
import 'src/components/common/tabs.dart';
import 'src/components/common/youtube_embed.dart';
import 'src/components/tutorial/quiz.dart';
import 'src/extensions/registry.dart';
import 'src/layouts/doc_layout.dart';
import 'src/layouts/homepage_layout.dart';
Expand Down Expand Up @@ -87,6 +88,7 @@ List<CustomComponent> get _embeddableComponents => [
const DashTabs(),
const YoutubeEmbed(),
const FileTree(),
const Quiz(),
CustomComponent(
pattern: RegExp('ArchiveTable'),
builder: (_, attrs, _) => ArchiveTable.fromAttributes(attrs),
Expand Down
22 changes: 21 additions & 1 deletion site/lib/main.server.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import 'package:jaspr/server.dart';
import 'package:dart_dev_site/src/archive/archive_table.dart' as _archive_table;
import 'package:dart_dev_site/src/components/common/client/collapse_button.dart'
as _collapse_button;
import 'package:dart_dev_site/src/components/common/client/cookie_notice.dart'
as _cookie_notice;
import 'package:dart_dev_site/src/components/common/client/copy_button.dart'
Expand All @@ -26,6 +28,8 @@ import 'package:dart_dev_site/src/components/pages/glossary_search_section.dart'
as _glossary_search_section;
import 'package:dart_dev_site/src/components/pages/lint_filter_search_section.dart'
as _lint_filter_search_section;
import 'package:dart_dev_site/src/components/tutorial/client/quiz.dart'
as _quiz;
import 'package:jaspr_content/components/file_tree.dart' as _file_tree;

/// Default [ServerOptions] for use with your Jaspr project.
Expand All @@ -51,6 +55,11 @@ ServerOptions get defaultServerOptions => ServerOptions(
'archive_table',
params: __archive_tableArchiveTable,
),
_collapse_button.CollapseButton:
ClientTarget<_collapse_button.CollapseButton>(
'collapse_button',
params: __collapse_buttonCollapseButton,
),
_cookie_notice.CookieNotice: ClientTarget<_cookie_notice.CookieNotice>(
'cookie_notice',
),
Expand Down Expand Up @@ -88,16 +97,23 @@ ServerOptions get defaultServerOptions => ServerOptions(
ClientTarget<_lint_filter_search_section.LintFilterSearchSection>(
'lint_filter_search_section',
),
_quiz.InteractiveQuiz: ClientTarget<_quiz.InteractiveQuiz>(
'quiz',
params: __quizInteractiveQuiz,
),
},
styles: () => [..._file_tree.FileTree.styles],
);

Map<String, Object?> __archive_tableArchiveTable(
_archive_table.ArchiveTable c,
) => {'channel': c.channel};
Map<String, Object?> __collapse_buttonCollapseButton(
_collapse_button.CollapseButton c,
) => {'classes': c.classes, 'title': c.title};
Map<String, Object?> __copy_buttonCopyButton(_copy_button.CopyButton c) => {
'toCopy': c.toCopy,
'buttonText': c.buttonText,
'toCopy': c.toCopy,
'classes': c.classes,
'title': c.title,
};
Expand All @@ -112,3 +128,7 @@ Map<String, Object?> __dartpad_injectorDartPadInjector(
'height': c.height,
'runAutomatically': c.runAutomatically,
};
Map<String, Object?> __quizInteractiveQuiz(_quiz.InteractiveQuiz c) => {
'title': c.title,
'questions': c.questions.map((i) => i.toJson()).toList(),
};
8 changes: 4 additions & 4 deletions site/lib/src/components/tutorial/client/quiz.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class _InteractiveQuizState extends State<InteractiveQuiz> {
if (question == currentQuestion) 'active',
].toClasses,
[
strong([.text(question.question)]),
strong([RawText(question.question)]),
ol([
for (final (index, option) in question.options.indexed)
li(
Expand All @@ -113,14 +113,14 @@ class _InteractiveQuizState extends State<InteractiveQuiz> {
[
div(classes: 'question-wrapper', [
div(classes: 'question', [
p([.text(option.text)]),
p([RawText(option.text)]),
]),
div(classes: 'solution', [
if (option.correct)
const p(classes: 'correct', [.text('That\'s right!')])
else
const p(classes: 'incorrect', [.text('Not quite')]),
p([.text(option.explanation)]),
const p(classes: 'incorrect', [.text('Not quite.')]),
p([RawText(option.explanation)]),
]),
]),
],
Expand Down
16 changes: 12 additions & 4 deletions site/lib/src/models/quiz_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// for details. 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:collection/collection.dart';
import 'package:jaspr/jaspr.dart';

import '../markdown/markdown_parser.dart' show parseMarkdownToHtml;

class Question {
const Question(this.question, this.options);

Expand All @@ -13,10 +16,10 @@ class Question {
@decoder
factory Question.fromMap(Map<Object?, Object?> json) {
return Question(
json['question'] as String,
parseMarkdownToHtml(json['question'] as String, inline: true),
(json['options'] as List<Object?>)
.map((e) => AnswerOption.fromJson(e as Map<Object?, Object?>))
.toList(),
.shuffled(),
);
}

Expand All @@ -30,16 +33,21 @@ class Question {
class AnswerOption {
const AnswerOption(this.text, this.correct, this.explanation);

/// The option text formatted as raw HTML.
final String text;

/// Whether this answer is correct.
final bool correct;

/// The correct/incorrect explanation formatted as raw HTML.
final String explanation;

@decoder
factory AnswerOption.fromJson(Map<Object?, Object?> json) {
return AnswerOption(
json['text'] as String,
parseMarkdownToHtml(json['text'] as String, inline: true),
json['correct'] as bool? ?? false,
json['explanation'] as String,
parseMarkdownToHtml(json['explanation'] as String),
);
}

Expand Down
Loading
Loading