Skip to content

Commit b0d073c

Browse files
committedJun 6, 2024·
scrolling instructions; previous/next functionality; lesson progress tracking
1 parent eae59e1 commit b0d073c

File tree

10 files changed

+158
-31
lines changed

10 files changed

+158
-31
lines changed
 

‎LearnJsonEverything/Program.cs

+2
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@
1515
var host = builder.Build();
1616
_ = host.Services.GetService<HttpClient>();
1717

18+
_ = await CompilationHelpers.LoadAssemblyReferences(host.Services.GetService<HttpClient>()!);
19+
1820
await host.RunAsync();

‎LearnJsonEverything/Services/CompilationHelpers.cs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static async Task<MetadataReference[]> LoadAssemblyReferences(HttpClient
2727
.Where(x => !x.IsDynamic)
2828
.Select(x => x.FullName!.Split(',')[0])
2929
.Concat(EnsuredAssemblies)
30+
.Distinct()
3031
.OrderBy(x => x)
3132
.ToArray();
3233

‎LearnJsonEverything/Services/LessonData.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class LessonData
1212
public string Instructions { get; set; }
1313
public string ContextCode { get; set; }
1414
public JsonArray Tests { get; set; }
15+
public bool Achieved { get; set; }
1516
}

‎LearnJsonEverything/Services/LessonPlan.cs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
using System.Text.Json;
1+
using System.Collections;
2+
using System.Text.Json;
23
using System.Text.Json.Serialization;
34

45
namespace LearnJsonEverything.Services;
56

67
[JsonConverter(typeof(LessonPlanJsonConverter))]
7-
public class LessonPlan
8+
public class LessonPlan : IReadOnlyList<LessonData>
89
{
910
private readonly LessonData[] _lessonData;
1011
private readonly Dictionary<Guid, int> _indexLookup;
1112

1213
public LessonData this[int i] => _lessonData[i];
14+
public LessonData this[Guid id] => _lessonData[_indexLookup[id]];
15+
16+
public int Count => _lessonData.Length;
1317

1418
public LessonPlan(LessonData[] lessonData)
1519
{
1620
_lessonData = lessonData;
17-
_indexLookup = lessonData.Select((d, i) => (d.Id, i)).ToDictionary(x => x.Id, x => x.i);
21+
_indexLookup = lessonData.Select((d, i) =>
22+
{
23+
d.Index = i;
24+
return (d.Id, i);
25+
}).ToDictionary(x => x.Id, x => x.i);
1826
}
1927

2028
public LessonData? GetPrevious(Guid? id)
@@ -38,6 +46,10 @@ public LessonPlan(LessonData[] lessonData)
3846

3947
return _lessonData[index];
4048
}
49+
50+
public IEnumerator<LessonData> GetEnumerator() => ((IEnumerable<LessonData>)_lessonData).GetEnumerator();
51+
52+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
4153
}
4254

4355
public class LessonPlanJsonConverter : JsonConverter<LessonPlan>

‎LearnJsonEverything/Services/Runners/SchemaRunner.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@ public static string[] Run(string userCode, LessonData lesson)
6464
var tests = lesson.Tests.Deserialize(SerializerContext.Default.SchemaTestArray)!;
6565
var results = new List<string>();
6666

67+
var correct = true;
6768
foreach (var test in tests)
6869
{
6970
var result = runner.Run(new JsonObject { ["instance"] = test.Instance });
70-
results.Add($"{(test.IsValid == result?.IsValid ? Iconography.SuccessIcon : Iconography.ErrorIcon)} {test.Instance.AsJsonString()}");
71+
correct &= test.IsValid == result.IsValid;
72+
results.Add($"{(test.IsValid == result.IsValid ? Iconography.SuccessIcon : Iconography.ErrorIcon)} {test.Instance.AsJsonString()}");
7173
}
7274

73-
// run the code
75+
lesson.Achieved |= correct;
7476

7577
return [.. results];
7678
}

‎LearnJsonEverything/Shared/Teacher.razor

+52-18
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,29 @@
1212
@using EditorOptions = LearnJsonEverything.Services.EditorOptions
1313

1414
@inject DataManager DataManager;
15-
@inject NavigationManager NavigationManager;
1615
@inject IJSRuntime JsRuntime;
1716
@inject HttpClient Client;
1817

1918
<div id="layout" class="row fill-remaining row-margin-reset">
20-
<div class="col-4 d-flex">
21-
<div class="grid-panel flex-grow-1">
19+
<div class="col-2 d-flex h-100">
20+
<div class="grid-panel flex-grow-1 scroll">
21+
<ul role="listbox">
22+
@foreach (var lesson in _lessons)
23+
{
24+
<li id="@lesson.Id" role="option" @onclick="() => SelectLesson(lesson.Id)">
25+
<span class="@(lesson.Achieved ? "" : "invisible")">@Iconography.SuccessIcon</span>
26+
@lesson.Title
27+
</li>
28+
}
29+
</ul>
30+
</div>
31+
</div>
32+
<div class="col-4 d-flex h-100">
33+
<div class="grid-panel flex-grow-1 scroll">
2234
<MarkdownSpan Content="@Instructions"></MarkdownSpan>
2335
</div>
2436
</div>
25-
<div class="col-8 d-flex">
37+
<div class="col-6 d-flex">
2638
<div id="right-panel" class="row row-margin-reset flex-grow-1">
2739
<div id="workspace" class="col-12 d-flex flex-grow-1">
2840
<div class="grid-panel flex-grow-1">
@@ -32,9 +44,9 @@
3244
</div>
3345
<div id="controls" class="col-12">
3446
<div class="d-flex">
47+
<button class="btn btn-primary m-1" @onclick="PreviousLesson">&lt; Previous</button>
3548
<button class="btn btn-primary m-1" @onclick="Run">Run</button>
36-
<button class="btn btn-primary m-1">content</button>
37-
<button class="btn btn-primary m-1">content</button>
49+
<button class="btn btn-primary m-1" disabled="@_nextButtonDisabled" @onclick="NextLesson">Next &gt;</button>
3850
</div>
3951
</div>
4052
<div id="output" class="col-12 d-flex">
@@ -48,11 +60,12 @@
4860
</div>
4961

5062
@code {
51-
#pragma warning disable CS8618
63+
#pragma warning disable CS8618
5264
private StandaloneCodeEditor _codeEditor;
5365
private StandaloneCodeEditor _outputEditor;
54-
private LessonPlan _lessons;
66+
private LessonPlan _lessons = new([]);
5567
private LessonData? _currentLesson;
68+
private bool _nextButtonDisabled = true;
5669

5770
[Parameter]
5871
public string LessonSource { get; set; }
@@ -69,9 +82,12 @@
6982
{
7083
var userCode = await _codeEditor.GetValue();
7184

72-
await _outputEditor.SetValue("");
85+
await _outputEditor.SetValue(string.Empty);
86+
7387
var results = SchemaRunner.Run(userCode, _currentLesson!);
88+
7489
await _outputEditor.SetValue(string.Join(Environment.NewLine, results!));
90+
_nextButtonDisabled = !CanMoveToNextLesson();
7591
}
7692
catch (Exception e)
7793
{
@@ -80,35 +96,43 @@
8096
}
8197
}
8298

83-
private void PreviousLesson()
99+
private Task PreviousLesson()
84100
{
85101
_currentLesson = _lessons.GetPrevious(_currentLesson?.Id);
86-
LoadLesson();
102+
return LoadLesson();
87103
}
88104

89-
private void NextLesson()
105+
private Task NextLesson()
90106
{
91107
_currentLesson = _lessons.GetNext(_currentLesson?.Id);
92-
LoadLesson();
108+
return LoadLesson();
93109
}
94110

95-
private void Reset()
111+
private Task Reset()
96112
{
97-
LoadLesson();
113+
return LoadLesson();
98114
}
99115

100-
private void LoadLesson()
116+
private Task LoadLesson()
101117
{
102118
_currentLesson ??= _lessons[0];
103119
Instructions = SchemaRunner.BuildInstructions(_currentLesson);
120+
_nextButtonDisabled = !CanMoveToNextLesson();
121+
return _outputEditor.SetValue(string.Empty);
122+
}
123+
124+
private bool CanMoveToNextLesson()
125+
{
126+
if (_currentLesson is null) return false;
127+
if (_currentLesson.Index == _lessons.Count - 1) return false;
128+
return _currentLesson.Achieved;
104129
}
105130

106131
protected override async Task OnInitializedAsync()
107132
{
108-
_ = await CompilationHelpers.LoadAssemblyReferences(Client);
109133
await DownloadLessonPlan();
110134

111-
LoadLesson();
135+
await LoadLesson();
112136

113137
await base.OnInitializedAsync();
114138
}
@@ -121,4 +145,14 @@
121145
Console.WriteLine(json.AsJsonString());
122146
_lessons = json.Deserialize(SerializerContext.Default.LessonPlan)!;
123147
}
148+
149+
private async Task SelectLesson(Guid lessonId)
150+
{
151+
var index = _currentLesson?.Index ?? 0;
152+
if (index == 0 || _lessons[index - 1].Achieved)
153+
{
154+
_currentLesson = _lessons[lessonId];
155+
await LoadLesson();
156+
}
157+
}
124158
}

‎LearnJsonEverything/Shared/Teacher.razor.css

+27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
}
1212

1313
.grid-panel {
14+
flex: 1 1 auto;
1415
border: solid 1px #CCBCD9;
1516
border-radius: 5px;
1617
padding: 10px !important;
@@ -21,3 +22,29 @@
2122
margin-left: auto;
2223
margin-right: auto;
2324
}
25+
26+
[role="option"] {
27+
position: relative;
28+
display: block;
29+
align-content: center;
30+
text-align: left;
31+
margin: 2px;
32+
line-height: 1.8em;
33+
cursor: pointer;
34+
}
35+
36+
[role="listbox"]:focus [role="option"].focused,
37+
[role="option"]:hover {
38+
color: white;
39+
}
40+
41+
.btn-list-option {
42+
border: none;
43+
background: none;
44+
height: 100%;
45+
width: 100%;
46+
}
47+
48+
ol, ul {
49+
padding-left: .5rem;
50+
}

‎LearnJsonEverything/wwwroot/css/app.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ article {
4242
}
4343

4444
.scroll {
45-
overflow-y: auto;
45+
overflow: auto;
4646
}
4747

4848
h1:focus {

‎LearnJsonEverything/wwwroot/css/editor.css

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.editor-input {
2-
height: 600px;
3-
}
4-
51
.editor-lang-json {
62
float: right;
73
padding: 0px 14px;

‎LearnJsonEverything/wwwroot/data/lessons/schema.yaml

+55-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
- id: 26b6ebca-58e6-4814-86ea-4946d844c9a6
3-
title: Validate a string
4-
instructions: Create a `JsonSchema` object and validate the string instance. Return
5-
the evaluation results.
3+
title: Any string
4+
instructions: |-
5+
Configure the provided `JsonSchemaBuilder` to produce a schema that validates a string instance.
66
inputTemplate: ''
77
contextCode: |-
88
using System;
@@ -19,8 +19,12 @@
1919
public EvaluationResults Run(JsonObject context)
2020
{
2121
var instance = context["instance"];
22+
var builder = new JsonSchemaBuilder();
2223
2324
/* USER CODE */
25+
26+
var schema = builder.Build();
27+
return schema.Evaluate(instance);
2428
}
2529
}
2630
tests:
@@ -32,3 +36,51 @@
3236
isValid: false
3337
- instance: false
3438
isValid: false
39+
- id: 26b6ebca-58e6-4814-86ea-4946d844c9a7
40+
title: Number with limits
41+
instructions: |-
42+
Configure the provided `JsonSchemaBuilder` to produce a schema that validates a number instance
43+
that is less than or equal to 10 but greater than 0.
44+
inputTemplate: ''
45+
contextCode: |-
46+
using System;
47+
using System.Collections.Generic;
48+
using System.Text.Json;
49+
using System.Text.Json.Nodes;
50+
using System.Text.Json.Serialization;
51+
using Json.Schema;
52+
53+
namespace LearnJsonEverything;
54+
55+
public class Lesson : ILessonRunner<EvaluationResults>
56+
{
57+
public EvaluationResults Run(JsonObject context)
58+
{
59+
var instance = context["instance"];
60+
var builder = new JsonSchemaBuilder();
61+
62+
/* USER CODE */
63+
64+
var schema = builder.Build();
65+
return schema.Evaluate(instance);
66+
}
67+
}
68+
tests:
69+
- instance: 6.8
70+
isValid: true
71+
- instance: 10
72+
isValid: true
73+
- instance: 42e-6
74+
isValid: true
75+
- instance: -5.1
76+
isValid: false
77+
- instance: 0
78+
isValid: false
79+
- instance: a string value
80+
isValid: false
81+
- instance: []
82+
isValid: false
83+
- instance: {}
84+
isValid: false
85+
- instance: false
86+
isValid: false

0 commit comments

Comments
 (0)
Please sign in to comment.