removed question id

This commit is contained in:
2023-10-24 13:45:43 -06:00
parent 3223aef5f3
commit 2b4891c147
16 changed files with 539 additions and 816 deletions

View File

@@ -34,102 +34,7 @@ this is my description in markdown
markdown.Should().Contain("AssignmentGroup: someId");
markdown.Should().Contain("AllowedAttempts: -1");
}
[Test]
public void QuzMarkdownIncludesMultipleChoiceQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new LocalQuizQuestion()
{
Id = "someid",
Points = 2,
Text = @"`some type` of question
with many
```
lines
```
",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Answers = new LocalQuizQuestionAnswer[]
{
new LocalQuizQuestionAnswer() { Correct = true, Text = "true" },
new LocalQuizQuestionAnswer() { Correct = false, Text = "false" + Environment.NewLine +Environment.NewLine + "endline" },
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 2
`some type` of question
with many
```
lines
```
*a) true
b) false
endline
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void QuzMarkdownIncludesMultipleAnswerQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new()
{
Id = "somesdid",
Text = "oneline question",
Points = 1,
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Answers = new LocalQuizQuestionAnswer[]
{
new() { Correct = true, Text = "true" },
new() { Correct = true, Text = "false"},
new() { Correct = false, Text = "neither"},
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 1
oneline question
[*] true
[*] false
[ ] neither
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void TestCanParseMarkdownQuizWithNoQuestions()
@@ -199,42 +104,6 @@ b) false
firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline");
}
[Test]
public void CanParseQuestionWithMultipleAnswers()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
[*] click
[*] focus
[*] mousedown
[] submit
[] change
[] mouseout
[] keydown
---
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS);
firstQuestion.Text.Should().Contain("Which events are triggered when the user clicks on an input field?");
firstQuestion.Answers.First().Text.Should().Be("click");
firstQuestion.Answers.First().Correct.Should().BeTrue();
firstQuestion.Answers.ElementAt(3).Correct.Should().BeFalse();
firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit");
}
[Test]
public void CanParseMultipleQuestions()
{
@@ -267,56 +136,7 @@ b) false
secondQuestion.Points.Should().Be(2);
secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE);
}
[Test]
public void CanParseEssay()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY);
firstQuestion.Text.Should().NotContain("essay");
}
[Test]
public void CanParseShortAnswer()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER);
firstQuestion.Text.Should().NotContain("short answer");
}
[Test]
public void ShortAnswerToMarkdown_IsCorrect()
{
@@ -345,32 +165,156 @@ Which events are triggered when the user clicks on an input field?
short_answer";
questionMarkdown.Should().Contain(expectedMarkdown);
}
[Test]
public void EssayQuestionToMarkdown_IsCorrect()
public void SerializationIsDeterministic_EmptyQuiz()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments"
};
var quizMarkdown = quiz.ToMarkdown();
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_ShortAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test short answer",
QuestionType = QuestionType.SHORT_ANSWER,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
essay";
questionMarkdown.Should().Contain(expectedMarkdown);
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_Essay()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test essay",
QuestionType = QuestionType.ESSAY,
Points = 1
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleAnswer()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple answer",
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = true,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
[Test]
public void SerializationIsDeterministic_MultipleChoice()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
ShuffleAnswers = true,
OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments",
Questions = new LocalQuizQuestion[]
{
new ()
{
Text = "test multiple choice",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Points = 1,
Answers = new LocalQuizQuestionAnswer[]
{
new() {
Correct = true,
Text="yes",
},
new() {
Correct = false,
Text="no",
}
}
}
}
};
var quizMarkdown = quiz.ToMarkdown();
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz);
}
}

View File

@@ -0,0 +1,244 @@
using LocalModels;
public class QuizQuestionMarkdownTests
{
[Test]
public void QuzMarkdownIncludesMultipleChoiceQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new LocalQuizQuestion()
{
Id = "someid",
Points = 2,
Text = @"`some type` of question
with many
```
lines
```
",
QuestionType = QuestionType.MULTIPLE_CHOICE,
Answers = new LocalQuizQuestionAnswer[]
{
new LocalQuizQuestionAnswer() { Correct = true, Text = "true" },
new LocalQuizQuestionAnswer() { Correct = false, Text = "false" + Environment.NewLine +Environment.NewLine + "endline" },
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 2
`some type` of question
with many
```
lines
```
*a) true
b) false
endline
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void QuzMarkdownIncludesMultipleAnswerQuestion()
{
var quiz = new LocalQuiz()
{
Name = "Test Quiz",
Description = "desc",
LockAt = DateTime.MaxValue,
DueAt = DateTime.MaxValue,
ShuffleAnswers = true,
OneQuestionAtATime = false,
LocalAssignmentGroupName = "someId",
AllowedAttempts = -1,
Questions = new LocalQuizQuestion[]
{
new()
{
Id = "somesdid",
Text = "oneline question",
Points = 1,
QuestionType = QuestionType.MULTIPLE_ANSWERS,
Answers = new LocalQuizQuestionAnswer[]
{
new() { Correct = true, Text = "true" },
new() { Correct = true, Text = "false"},
new() { Correct = false, Text = "neither"},
}
}
}
};
var markdown = quiz.ToMarkdown();
var expectedQuestionString = @"
Points: 1
oneline question
[*] true
[*] false
[ ] neither
";
markdown.Should().Contain(expectedQuestionString);
}
[Test]
public void CanParseQuestionWithMultipleAnswers()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
[*] click
[*] focus
[*] mousedown
[] submit
[] change
[] mouseout
[] keydown
---
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_ANSWERS);
firstQuestion.Text.Should().Contain("Which events are triggered when the user clicks on an input field?");
firstQuestion.Answers.First().Text.Should().Be("click");
firstQuestion.Answers.First().Correct.Should().BeTrue();
firstQuestion.Answers.ElementAt(3).Correct.Should().BeFalse();
firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit");
}
[Test]
public void CanParseEssay()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY);
firstQuestion.Text.Should().NotContain("essay");
}
[Test]
public void CanParseShortAnswer()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(1);
firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER);
firstQuestion.Text.Should().NotContain("short answer");
}
[Test]
public void ShortAnswerToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
short answer
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
short_answer";
questionMarkdown.Should().Contain(expectedMarkdown);
}
[Test]
public void EssayQuestionToMarkdown_IsCorrect()
{
var rawMarkdownQuiz = @"
Name: Test Quiz
ShuffleAnswers: true
OneQuestionAtATime: false
DueAt: 2023-08-21T23:59:00
LockAt: 2023-08-21T23:59:00
AssignmentGroup: Assignments
AllowedAttempts: -1
Description: this is the
multi line
description
---
Which events are triggered when the user clicks on an input field?
essay
";
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
var firstQuestion = quiz.Questions.First();
var questionMarkdown = firstQuestion.ToMarkdown();
var expectedMarkdown = @"Points: 1
Which events are triggered when the user clicks on an input field?
essay";
questionMarkdown.Should().Contain(expectedMarkdown);
}
}

View File

@@ -11,7 +11,7 @@
@using Management.Web.Shared.Module.Assignment
@using Management.Web.Shared.Components
@inject YamlManager yamlManager
@inject FileStorageManager fileStorageManager
@inject CanvasService canvas
@inject CoursePlanner planner
@inject AssignmentEditorContext assignmentContext
@@ -23,28 +23,28 @@
[Parameter]
public string? AssignmentId { get; set; } = default!;
private bool loading { get; set; }= true;
private bool loading { get; set; } = true;
protected override async Task OnInitializedAsync()
{
if(loading)
if (loading)
{
loading = false;
logger.LogInformation($"loading assignment {CourseName} {AssignmentId}");
if(planner.LocalCourse == null)
if (planner.LocalCourse == null)
{
var courses = await yamlManager.LoadSavedCourses();
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
logger.LogInformation($"set course to '{planner.LocalCourse?.Settings.Name}'");
}
if(assignmentContext.Assignment == null)
if (assignmentContext.Assignment == null)
{
var assignment = planner
.LocalCourse?
.Modules
.SelectMany(m => m.Assignments)
.FirstOrDefault(a => a.Id == AssignmentId);
.LocalCourse?
.Modules
.SelectMany(m => m.Assignments)
.FirstOrDefault(a => a.Id == AssignmentId);
assignmentContext.Assignment = assignment;
logger.LogInformation($"set assignment to '{assignmentContext.Assignment?.Name}'");
@@ -56,12 +56,12 @@
}
@if(loading)
@if (loading)
{
<Spinner />
}
@if(planner.LocalCourse != null && assignmentContext.Assignment != null)
@if (planner.LocalCourse != null && assignmentContext.Assignment != null)
{
<AssignmentForm />
}

View File

@@ -9,7 +9,7 @@
@using Management.Web.Shared.Module.Assignment
@using Management.Web.Shared.Components
@inject YamlManager yamlManager
@inject FileStorageManager fileStorageManager
@inject CanvasService canvas
@inject CoursePlanner planner
@inject NavigationManager navigtion
@@ -22,9 +22,9 @@
protected override async Task OnInitializedAsync()
{
if(planner.LocalCourse == null)
if (planner.LocalCourse == null)
{
var courses = await yamlManager.LoadSavedCourses();
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
}
base.OnInitialized();
@@ -39,43 +39,34 @@
}
@if(loading)
@if (loading)
{
<Spinner />
}
@if(planner.LocalCourse != null)
@if (planner.LocalCourse != null)
{
<div class="mb-3 d-flex justify-content-between" style="height: 4em;">
<div class="my-auto">
<button
@onclick="selectNewCourse"
class="btn btn-primary"
>
Select New Course
</button>
<CourseSettings />
<AssignmentTemplateManagement />
<div class="my-auto">
<button @onclick="selectNewCourse" class="btn btn-primary">
Select New Course
</button>
<CourseSettings />
<AssignmentTemplateManagement />
<button
class="btn btn-outline-primary"
@onclick="planner.SyncWithCanvas"
>
Sync With Canvas
</button>
<a
class="btn btn-outline-secondary"
target="_blank"
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.Settings.CanvasId}")"
>
View In Canvas
</a>
<div class="my-auto ms-2 d-inline">
@planner.LocalCourse.Settings.Name
</div>
<button class="btn btn-outline-primary" @onclick="planner.SyncWithCanvas">
Sync With Canvas
</button>
<a class="btn btn-outline-secondary" target="_blank"
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.Settings.CanvasId}")">
View In Canvas
</a>
<div class="my-auto ms-2 d-inline">
@planner.LocalCourse.Settings.Name
</div>
</div>
@if(planner.LoadingCanvasData)
@if (planner.LoadingCanvasData)
{
<Spinner />
}

View File

@@ -13,7 +13,7 @@
@using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Quiz.Markdown
@inject YamlManager yamlManager
@inject FileStorageManager fileStorageManager
@inject CanvasService canvas
@inject CoursePlanner planner
@inject QuizEditorContext quizContext
@@ -26,9 +26,9 @@
[Parameter]
public string? QuizName { get; set; } = default!;
private bool loading { get; set; }= true;
private bool loading { get; set; } = true;
private bool addingQuizToCanvas = false;
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
@@ -43,31 +43,31 @@
}
protected override async Task OnInitializedAsync()
{
if(loading)
if (loading)
{
loading = false;
logger.LogInformation($"loading quiz {CourseName} {QuizName}");
if(planner.LocalCourse == null)
if (planner.LocalCourse == null)
{
var courses = await yamlManager.LoadSavedCourses();
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
logger.LogInformation($"set course to '{planner.LocalCourse?.Settings.Name}'");
}
if(quizContext.Quiz == null)
if (quizContext.Quiz == null)
{
var quiz = planner
.LocalCourse?
.Modules
.SelectMany(m => m.Quizzes)
.FirstOrDefault(q => q.Name == QuizName);
.LocalCourse?
.Modules
.SelectMany(m => m.Quizzes)
.FirstOrDefault(q => q.Name == QuizName);
quizContext.Quiz = quiz;
logger.LogInformation($"set quiz to '{quizContext.Quiz?.Name}'");
}
StateHasChanged();
if(planner.CanvasQuizzes == null)
if (planner.CanvasQuizzes == null)
{
await planner.LoadCanvasData();
}
@@ -98,7 +98,8 @@
private CanvasQuiz? quizInCanvas => planner.CanvasQuizzes?.FirstOrDefault(q => q.Title == quizContext.Quiz?.Name);
private string canvasQuizUrl => $"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/quizzes/{quizInCanvas?.Id}";
private string canvasQuizUrl =>
$"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/quizzes/{quizInCanvas?.Id}";
private int? quizPoints => quizContext.Quiz?.Questions.Sum(q => q.Points);
}
@@ -108,10 +109,7 @@
<section>
<div class="row justify-content-between">
<div class="col-auto my-auto">
<button
class="btn btn-outline-secondary"
@onclick="done"
>
<button class="btn btn-outline-secondary" @onclick="done">
← go back
</button>
</div>
@@ -121,7 +119,7 @@
</h2>
</div>
@if(quizContext.Quiz == null)
@if (quizContext.Quiz == null)
{
<div class="col-auto">
<Spinner />
@@ -131,9 +129,9 @@
<h3>
Points: @quizPoints
</h3>
@if(quizInCanvas != null)
@if (quizInCanvas != null)
{
@if(quizInCanvas?.Published == true)
@if (quizInCanvas?.Published == true)
{
<div class="text-success">
Published!
@@ -150,12 +148,11 @@
</div>
</section>
<section
class="flex-grow-1 w-100 d-flex justify-content-center overflow-y-auto overflow-x-hidden border rounded-4 bg-dark-subtle py-3 px-1"
style="min-height: 10%;max-width: 100%;"
>
<section
class="flex-grow-1 w-100 d-flex justify-content-center overflow-y-auto overflow-x-hidden border rounded-4 bg-dark-subtle py-3 px-1"
style="min-height: 10%;max-width: 100%;">
<div class="w-100" style="max-width: 120em; max-height: 100%;">
@if(quizContext.Quiz != null)
@if (quizContext.Quiz != null)
{
<MarkdownQuizForm />
}
@@ -164,45 +161,29 @@
<hr>
<section>
@if(quizContext.Quiz != null)
@if (quizContext.Quiz != null)
{
<div class="row justify-content-end">
<div class="col-auto">
<ConfirmationModal
Label="Delete"
Class="btn btn-danger"
OnConfirm="deleteQuiz"
Disabled="@addingQuizToCanvas"
/>
<button
class="btn btn-outline-secondary me-1"
@onclick="addToCanvas"
disabled="@addingQuizToCanvas"
>
<ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirm="deleteQuiz"
Disabled="@addingQuizToCanvas" />
<button class="btn btn-outline-secondary me-1" @onclick="addToCanvas" disabled="@addingQuizToCanvas">
Add to Canvas
</button>
@if(quizInCanvas != null)
@if (quizInCanvas != null)
{
<a
class="btn btn-outline-secondary me-1"
href="@canvasQuizUrl"
target="_blank"
>
<a class="btn btn-outline-secondary me-1" href="@canvasQuizUrl" target="_blank">
View in Canvas
</a>
}
<button
class="btn btn-primary"
@onclick="done"
disabled="@addingQuizToCanvas"
>
<button class="btn btn-primary" @onclick="done" disabled="@addingQuizToCanvas">
Done
</button>
</div>
</div>
}
@if(addingQuizToCanvas)
@if (addingQuizToCanvas)
{
<Spinner />
}

View File

@@ -10,6 +10,7 @@ global using LocalModels;
global using Management.Planner;
global using Management.Web.Shared.Components;
global using Management.Web.Shared;
global using Management.Web.Shared.Components.Forms;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@@ -44,7 +45,7 @@ builder.Services.AddScoped<CanvasQuizService>();
builder.Services.AddScoped<CanvasModuleService>();
builder.Services.AddScoped<CanvasService, CanvasService>();
builder.Services.AddScoped<YamlManager>();
builder.Services.AddScoped<FileStorageManager>();
builder.Services.AddScoped<CoursePlanner>();
builder.Services.AddScoped<AssignmentEditorContext>();
builder.Services.AddScoped<QuizEditorContext>();

View File

@@ -1,189 +0,0 @@
@using Management.Web.Shared.Components
@inject QuizEditorContext quizContext
@code {
private Modal? modal { get; set; }
private string name { get; set; } = "";
private string description { get; set; } = "";
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
}
private void reload()
{
if (quizContext.Quiz != null)
{
name = quizContext.Quiz.Name;
description = quizContext.Quiz.Description;
modal?.Show();
this.InvokeAsync(this.StateHasChanged);
}
}
public void Dispose()
{
quizContext.StateHasChanged -= reload;
}
private void deleteQuiz()
{
quizContext.DeleteQuiz();
modal?.Hide();
}
private void addQuestion()
{
if(quizContext.Quiz != null)
{
var newQuestion = new LocalQuizQuestion
{
Id = Guid.NewGuid().ToString(),
Points = 1,
};
var newQuiz = quizContext.Quiz with
{
Questions = quizContext.Quiz.Questions.Append(newQuestion).ToArray()
};
quizContext.SaveQuiz(newQuiz);
}
}
private void removeQuestion()
{
if(quizContext.Quiz != null)
{
var newQuestion = new LocalQuizQuestion();
var newQuiz = quizContext.Quiz with
{
Questions = quizContext.Quiz.Questions.Take(quizContext.Quiz.Questions.Count() - 1)
};
quizContext.SaveQuiz(newQuiz);
}
}
private void updateQuestion(LocalQuizQuestion updatedQuestion)
{
if(quizContext.Quiz != null)
{
var newQuestions = quizContext.Quiz.Questions.Select(q =>
q.Id == updatedQuestion.Id
? updatedQuestion
: q
);
var newQuiz = quizContext.Quiz with
{
Questions = newQuestions,
};
quizContext.SaveQuiz(newQuiz);
}
}
private void handleNewName(ChangeEventArgs e)
{
if(quizContext.Quiz != null)
{
var newQuiz = quizContext.Quiz with
{
Name = e.Value?.ToString() ?? ""
};
quizContext.SaveQuiz(newQuiz);
}
}
private void handleNewDescription(ChangeEventArgs e)
{
if(quizContext.Quiz != null)
{
var newQuiz = quizContext.Quiz with
{
Description = e.Value?.ToString() ?? ""
};
quizContext.SaveQuiz(newQuiz);
}
}
private async Task addToCanvas()
{
await quizContext.AddQuizToCanvas();
}
}
<Modal @ref="modal" OnHide="() => quizContext.Quiz = null" >
<Title>
<div class="row justify-content-between">
<div class="col-auto">
@quizContext.Quiz?.Name
</div>
<div class="col-auto me-3">
Points: @quizContext.Quiz?.Questions.Sum(q => q.Points)
</div>
</div>
</Title>
<Body>
@if(quizContext.Quiz != null)
{
<div class="m-1">
<label class="form-label">
Name
</label>
<input
class="form-control"
@bind="name"
@oninput="handleNewName"
/>
</div>
<div class="m-1">
<label class="form-label">
Description
</label>
<textarea
class="form-control"
@bind="description"
@oninput="handleNewDescription"
/>
</div>
<QuizSettings />
@foreach(var (question, i) in quizContext.Quiz.Questions.Select((q, i) => (q, i)))
{
<QuizQuestionForm
@key="@question.Id"
Index="i"
Question="question"
UpdateQuestion="updateQuestion"
/>
}
<button
class="btn btn-outline-danger"
@onclick="removeQuestion"
>
- question
</button>
<button
class="btn btn-outline-primary"
@onclick="addQuestion"
>
+ question
</button>
}
</Body>
<Footer>
<ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirm="deleteQuiz" />
<button
class="btn btn-outline-secondary"
@onclick="addToCanvas"
>
Add to Canvas
</button>
<button
class="btn btn-primary"
@onclick="() => modal?.Hide()"
>
Done
</button>
</Footer>
</Modal>

View File

@@ -1,189 +0,0 @@
@inject QuizEditorContext quizContext
@code {
[Parameter, EditorRequired]
public LocalQuizQuestion Question { get; set; } = default!;
[Parameter, EditorRequired]
public int Index { get; set; } = default!;
[Parameter, EditorRequired]
public Action<LocalQuizQuestion> UpdateQuestion { get; set; } = (_) => {};
protected override void OnParametersSet()
{
if(questionType == string.Empty)
questionType = Question.QuestionType;
if(text == string.Empty)
text = Question.Text;
if(answers.Count() == 0)
answers = Question.Answers;
if (points == 0)
points = Question.Points;
base.OnParametersSet();
}
private string text { get; set; } = string.Empty;
private string questionType { get; set; } = string.Empty;
private int points { get; set; } = 1;
private IEnumerable<LocalQuizQuestionAnswer> answers { get; set; } = Enumerable.Empty<LocalQuizQuestionAnswer>();
private void handleTypeUpdate(string type)
{
if(quizContext.Quiz != null)
{
questionType = type;
var newQuestion = Question with
{
QuestionType = questionType
};
UpdateQuestion(newQuestion);
}
}
private void handlePointsInput(ChangeEventArgs e)
{
if (quizContext.Quiz != null)
{
var pointsString = e.Value?.ToString() ?? "0";
var newPoints = int.Parse(pointsString == string.Empty ? "0" : pointsString);
var newQuestion = Question with
{
Points = newPoints
};
UpdateQuestion(newQuestion);
}
}
private void addAnswer()
{
if(quizContext.Quiz != null)
{
var newAnswer = new LocalQuizQuestionAnswer();
answers = answers.Append(newAnswer);
UpdateQuestion(Question with { Answers = answers });
}
}
private void removeAnswer()
{
if(quizContext.Quiz != null)
{
answers = answers.Take(Question.Answers.Count() - 1);
UpdateQuestion(Question with { Answers = answers });
}
}
private void saveAnswer(LocalQuizQuestionAnswer newAnswer, int index)
{
if(questionType == QuestionType.MULTIPLE_CHOICE && newAnswer.Correct)
{
answers = answers.Select((a, i) =>
index == i
? newAnswer
: a with { Correct = false }
).ToArray();
}
else
{
answers = answers.Select((a, i) =>
index == i
? newAnswer
: a
).ToArray();
}
UpdateQuestion(Question with { Answers = answers });
}
private void handleTextUpdate(ChangeEventArgs e)
{
var newText = e.Value?.ToString() ?? "";
UpdateQuestion(Question with { Text = newText });
}
}
<div class="my-1 p-2 border border-3 rounded">
<div class="row">
<div class="col">
<label
for="question_text"
class="form-label"
>
Question Text
</label>
<textarea
class="form-control"
id="question_text"
name="question_text"
@bind="text"
@oninput="handleTextUpdate"
/>
</div>
<div class="col-auto">
</div>
</div>
@foreach (var typeOption in QuestionType.AllTypes)
{
<div class="form-check">
<input class="form-check-input" type="radio" name="@(Question.Id + "question_type")"
id="@(Question.Id + typeOption)" value="@typeOption" checked="@(typeOption == questionType)"
@onchange="() => handleTypeUpdate(typeOption)">
<label class="form-check-label" for="@(Question.Id + typeOption)">
@typeOption
</label>
</div>
}
<div class="mb-3 row">
<div class="col-auto">
<label
for="exampleInputEmail1"
class="form-label"
>
Points
</label>
<input
type="number"
class="form-control"
id="exampleInputEmail1"
@bind="points"
@oninput="handlePointsInput"
>
</div>
</div>
<div>Answers:</div>
@if(questionType == QuestionType.MULTIPLE_ANSWERS || questionType == QuestionType.MULTIPLE_CHOICE)
{
<button
class="btn btn-outline-danger"
@onclick="removeAnswer"
>
- Remove Answer
</button>
<button
class="btn btn-outline-primary"
@onclick="addAnswer"
>
+ Add Answer
</button>
@foreach(var (answer, i) in answers.Select((a, i) => (a, i)))
{
<EditableQuizAnswer
Answer="answer"
AnswerIndex="i"
QuestionIndex="Index"
SaveAnswer="saveAnswer"
Question="Question"
/>
}
}
<div>
</div>
</div>

View File

@@ -1,69 +0,0 @@
@using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Forms
@inject QuizEditorContext quizContext
@inject CoursePlanner planner
@code {
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
}
private void reload()
{
if (quizContext.Quiz != null)
{
if (lockAt == null)
lockAt = quizContext.Quiz.LockAt;
if (!shuffleAnswers)
shuffleAnswers = quizContext.Quiz.ShuffleAnswers;
if (!oneQuestionAtATime)
oneQuestionAtATime = quizContext.Quiz.OneQuestionAtATime;
if (allowedAttempts == 0)
allowedAttempts = quizContext.Quiz.AllowedAttempts;
this.InvokeAsync(this.StateHasChanged);
}
}
public void Dispose()
{
quizContext.StateHasChanged -= reload;
}
private DateTime? lockAt { get; set; }
private bool shuffleAnswers { get; set; }
private bool oneQuestionAtATime { get; set; }
private int allowedAttempts { get; set; }
private void setAssignmentGroup(LocalAssignmentGroup? group)
{
if(quizContext.Quiz == null)
return;
var newQuiz = quizContext.Quiz with
{
LocalAssignmentGroupName = group?.Name
};
quizContext.SaveQuiz(newQuiz);
}
private LocalAssignmentGroup? selectedAssignmentGroup =>
planner
.LocalCourse?
.Settings
.AssignmentGroups
.FirstOrDefault(g => g.Name == quizContext.Quiz?.LocalAssignmentGroupName);
}
@if(planner.LocalCourse != null )
{
<div>
<ButtonSelect
Label="Assignment Group"
Options="planner.LocalCourse.Settings.AssignmentGroups"
GetName="(g) => g?.Name"
OnSelect="(g) => setAssignmentGroup(g)"
SelectedOption="selectedAssignmentGroup"
/>
</div>
}

View File

@@ -1,6 +1,6 @@
@using LocalModels
@inject YamlManager yamlManager
@inject FileStorageManager fileStorageManager
@inject CoursePlanner planner
@inject NavigationManager Navigation
@inject ILogger<CurrentFiles> logger
@@ -12,7 +12,7 @@
public IEnumerable<LocalCourse>? localCourses { get; set; }
protected override async Task OnParametersSetAsync()
{
localCourses = await yamlManager.LoadSavedCourses();
localCourses = await fileStorageManager.LoadSavedCourses();
}
void handleClick(MouseEventArgs e, LocalCourse course)

View File

@@ -6,7 +6,7 @@
@using LocalModels
@inject CanvasService canvas
@inject YamlManager yamlManager
@inject FileStorageManager fileStorageManager
@code {
@@ -15,18 +15,18 @@
private bool loadingTerms = false;
private bool loadingCourses = false;
public IEnumerable<LocalCourse>? localCourses { get; set; }
private IEnumerable<EnrollmentTermModel>? terms { get; set; } = null;
private IEnumerable<CourseModel>? courses { get; set;} = null;
private IEnumerable<EnrollmentTermModel>? terms { get; set; } = null;
private IEnumerable<CourseModel>? courses { get; set; } = null;
private ulong? _selectedTermId { get; set; }
private ulong? selectedTermId
{
private ulong? selectedTermId
{
get => _selectedTermId;
set
set
{
_selectedTermId = value;
this.InvokeAsync(updateCourses);
@* updateCourses(); *@
}
}
}
private EnrollmentTermModel? selectedTerm
{
@@ -34,16 +34,16 @@
}
private ulong? _selectedCourseId { get; set; }
private ulong? selectedCourseId
private ulong? selectedCourseId
{
get => _selectedCourseId;
set
set
{
_selectedCourseId = value;
}
}
private CourseModel? selectedCourse
private CourseModel? selectedCourse
{
get => courses?.First(c => c.Id == selectedCourseId);
}
@@ -56,32 +56,33 @@
}
private async Task YamlTrigger()
{
if(selectedCourse != null)
if (selectedCourse != null)
{
var course = new LocalCourse
{
Modules = new LocalModule[] {},
Settings = new LocalCourseSettings() {
Name = selectedCourse.Name,
CanvasId = selectedCourse.Id,
StartDate = selectedTerm?.StartAt ?? new DateTime(),
EndDate = selectedTerm?.EndAt ?? new DateTime(),
DaysOfWeek = days,
}
};
await yamlManager.SaveCourseAsync(course);
{
Modules = new LocalModule[] { },
Settings = new LocalCourseSettings()
{
Name = selectedCourse.Name,
CanvasId = selectedCourse.Id,
StartDate = selectedTerm?.StartAt ?? new DateTime(),
EndDate = selectedTerm?.EndAt ?? new DateTime(),
DaysOfWeek = days,
}
};
await fileStorageManager.SaveCourseAsync(course);
NewFileCreated();
}
await updateCourses();
}
private async Task updateCourses()
{
if(selectedTermId != null)
if (selectedTermId != null)
{
loadingCourses = true;
localCourses = await yamlManager.LoadSavedCourses();
localCourses = await fileStorageManager.LoadSavedCourses();
var storedCourseIds = localCourses.Select(c => c.Settings.CanvasId);
var allCourses = await canvas.GetCourses((ulong)selectedTermId);
courses = allCourses.Where(c => !storedCourseIds.Contains(c.Id));
@@ -94,7 +95,7 @@
}
}
@if(loadingTerms)
@if (loadingTerms)
{
<Spinner />
}
@@ -102,13 +103,13 @@
@if (terms != null)
{
<div class="row justify-content-center">
<div class="col-auto">
<label for="termselect">Select Term:</label>
<select id="termselect" class="form-select" @bind="selectedTermId">
@foreach (var term in terms)
{
<option value="@term.Id">@term.Name</option>
}
<div class="col-auto">
<label for="termselect">Select Term:</label>
<select id="termselect" class="form-select" @bind="selectedTermId">
@foreach (var term in terms)
{
<option value="@term.Id">@term.Name</option>
}
</select>
</div>
</div>
@@ -117,18 +118,18 @@
@if (selectedTerm is not null)
{
@if(loadingCourses)
@if (loadingCourses)
{
<Spinner />
}
@if(courses != null)
@if (courses != null)
{
<div class="row justify-content-center m-3">
<div class="col-auto">
<label for="courseselect">Select Course:</label>
<select id="courseselect" class="form-select" @bind="selectedCourseId">
@foreach (var course in courses)
<div class="col-auto">
<label for="courseselect">Select Course:</label>
<select id="courseselect" class="form-select" @bind="selectedCourseId">
@foreach (var course in courses)
{
<option value="@course.Id">@course.Name</option>
}
@@ -140,9 +141,9 @@
<h5 class="text-center mt-3">Select Days Of Week</h5>
<div class="row m-3">
@foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)))
{
<div class="col">
<button class="@(
{
<div class="col">
<button class="@(
days.Contains(day)
? "btn btn-secondary"
: "btn btn-outline-secondary"
@@ -152,19 +153,16 @@
else
days.Add(day);
}">
@day
</button>
</div>
}
@day
</button>
</div>
}
</div>
<div class="text-center">
<button
@onclick="YamlTrigger"
class="btn btn-primary"
>
Save Yaml File
</button>
</div>
<div class="text-center">
<button @onclick="YamlTrigger" class="btn btn-primary">
Save Yaml File
</button>
</div>
}

View File

@@ -1,4 +1,5 @@
@using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Forms
@inject CoursePlanner planner
@@ -28,7 +29,8 @@
LockAt = null,
DueAt = DateTime.Now,
PointsPossible = 10,
SubmissionTypes = new string[] { SubmissionType.ONLINE_TEXT_ENTRY }
SubmissionTypes = new string[] { SubmissionType.ONLINE_TEXT_ENTRY },
LocalAssignmentGroupId = selectedAssignmentGroup?.Id,
};
if(planner.LocalCourse != null)
@@ -49,7 +51,14 @@
Name = "";
modal?.Hide();
}
private void setAssignmentGroup(LocalAssignmentGroup? group)
{
selectedAssignmentGroup = group;
}
private LocalAssignmentGroup? selectedAssignmentGroup { get; set; }
}
<button
class="btn btn-outline-secondary"
@onclick="() => modal?.Show()"
@@ -64,6 +73,14 @@
<label for="Assignment Name">Name</label>
<input id="moduleName" class="form-control" @bind="Name" />
</form>
<br>
<label class="form-label">Assignment Group</label>
<ButtonSelect
Label="Assignment Group"
Options="planner.LocalCourse.Settings.AssignmentGroups"
GetName="(g) => g?.Name"
OnSelect="(g) => setAssignmentGroup(g)"
/>
</Body>
<Footer>
<button

View File

@@ -48,7 +48,6 @@
modal?.Hide();
}
private void setAssignmentGroup(LocalAssignmentGroup? group)
{
selectedAssignmentGroup = group;
@@ -69,9 +68,9 @@
<form @onsubmit:preventDefault="true" @onsubmit="submitHandler">
<label for="Assignment Name">Name</label>
<input id="moduleName" class="form-control" @bind="Name" />
<br>
<label class="form-label">Assignment Group</label>
</form>
<br>
<label class="form-label">Assignment Group</label>
<ButtonSelect
Label="Assignment Group"
Options="planner.LocalCourse.Settings.AssignmentGroups"

View File

@@ -12,13 +12,13 @@ namespace Management.Planner;
public class CoursePlanner
{
private readonly YamlManager yamlManager;
private readonly FileStorageManager fileStorageManager;
private readonly CanvasService canvas;
public bool LoadingCanvasData { get; internal set; } = false;
public CoursePlanner(YamlManager yamlManager, CanvasService canvas)
public CoursePlanner(FileStorageManager fileStorageManager, CanvasService canvas)
{
this.yamlManager = yamlManager;
this.fileStorageManager = fileStorageManager;
this.canvas = canvas;
}
@@ -61,12 +61,12 @@ public class CoursePlanner
if (LocalCourse == null)
{
Console.WriteLine("saving course as of debounce call time");
await yamlManager.SaveCourseAsync(courseAsOfDebounce);
await fileStorageManager.SaveCourseAsync(courseAsOfDebounce);
}
else
{
Console.WriteLine("Saving latest version of file");
await yamlManager.SaveCourseAsync(LocalCourse);
await fileStorageManager.SaveCourseAsync(LocalCourse);
}
}

View File

@@ -5,7 +5,6 @@ namespace LocalModels;
public record LocalQuizQuestion
{
public string Id { get; set; } = "";
public string Text { get; init; } = string.Empty;
public string HtmlText => Markdig.Markdown.ToHtml(Text);
public string QuestionType { get; init; } = string.Empty;

View File

@@ -2,7 +2,7 @@ using LocalModels;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
public class YamlManager
public class FileStorageManager
{
public string CourseToYaml(LocalCourse course)
{
@@ -66,10 +66,6 @@ public class YamlManager
foreach (var quiz in module.Quizzes)
{
var filePath = quizzesDirectory + "/" + quiz.Name + ".yml"; ;
var quizYaml = quiz.ToYaml();
await File.WriteAllTextAsync(filePath, quizYaml);
var markdownPath = quizzesDirectory + "/" + quiz.Name + ".md"; ;
var quizMarkdown = quiz.ToMarkdown();
await File.WriteAllTextAsync(markdownPath, quizMarkdown);