added quiz support, improved assignment description and rubric support

This commit is contained in:
2023-08-18 13:27:12 -06:00
parent a962ef74f9
commit a57a687f3c
10 changed files with 154 additions and 69 deletions

View File

@@ -14,7 +14,8 @@
{ {
if (assignmentContext.Assignment != null) if (assignmentContext.Assignment != null)
{ {
Description = assignmentContext.Assignment.Description; description = assignmentContext.Assignment.Description;
descriptionForPreview = description;
TemplateId = assignmentContext.Assignment.TemplateId; TemplateId = assignmentContext.Assignment.TemplateId;
UseTemplate = TemplateId != null && TemplateId != ""; UseTemplate = TemplateId != null && TemplateId != "";
VariableValues = assignmentContext.Assignment.TemplateVariables; VariableValues = assignmentContext.Assignment.TemplateVariables;
@@ -27,25 +28,7 @@
} }
private string description { get; set; } = default!; private string description { get; set; } = default!;
public string Description private string descriptionForPreview { get; set; } = default!;
{
get => description;
set
{
description = value;
if (description != string.Empty)
{
if(assignmentContext.Assignment != null)
{
var newAssignment = assignmentContext.Assignment with
{
Description = description
};
assignmentContext.SaveAssignment(newAssignment);
}
}
}
}
public bool? UseTemplate { get; set; } = null; public bool? UseTemplate { get; set; } = null;
public string? TemplateId { get; set; } public string? TemplateId { get; set; }
@@ -58,9 +41,21 @@
.AssignmentTemplates .AssignmentTemplates
.FirstOrDefault(t => t.Id == TemplateId); .FirstOrDefault(t => t.Id == TemplateId);
private void saveDescription(ChangeEventArgs e) private void handleNewDescription(ChangeEventArgs e)
{ {
var newDescription = e.Value?.ToString();
if (newDescription != string.Empty)
{
descriptionForPreview = newDescription;
if (assignmentContext.Assignment != null)
{
var newAssignment = assignmentContext.Assignment with
{
Description = newDescription
};
assignmentContext.SaveAssignment(newAssignment);
}
}
} }
private void saveTemplateId(ChangeEventArgs e) private void saveTemplateId(ChangeEventArgs e)
@@ -68,15 +63,15 @@
if(assignmentContext.Assignment != null) if(assignmentContext.Assignment != null)
{ {
var newTemplateId = e.Value?.ToString(); var newTemplateId = e.Value?.ToString();
var newAssignment = assignmentContext.Assignment with var newAssignment = assignmentContext.Assignment with
{ {
Description = e.Value?.ToString() ?? "" TemplateId = newTemplateId
}; };
assignmentContext.SaveAssignment(newAssignment); assignmentContext.SaveAssignment(newAssignment);
} }
} }
private MarkupString preview { get => (MarkupString) Markdown.ToHtml(Description); } private MarkupString preview { get => (MarkupString)Markdown.ToHtml(descriptionForPreview); }
} }
@@ -167,11 +162,11 @@
id="description" id="description"
class="form-control" class="form-control"
rows=12 rows=12
@bind="Description" @bind="description"
@bind:event="oninput" @oninput="handleNewDescription"
/> />
</div> </div>
<div class="col" @key="Description"> <div class="col" @key="descriptionForPreview">
@(preview) @(preview)
</div> </div>
</div> </div>

View File

@@ -12,6 +12,20 @@
public Action MoveUp { get; set; } = default!; public Action MoveUp { get; set; } = default!;
[Parameter, EditorRequired] [Parameter, EditorRequired]
public Action MoveDown { get; set; } = default!; public Action MoveDown { get; set; } = default!;
private int points { get; set; }
private string label { get; set; }
private string lastRubricItemId { get; set; }
protected override void OnParametersSet()
{
if(RubricItem.Id != lastRubricItemId)
{
lastRubricItemId = RubricItem.Id;
points = RubricItem.Points;
label = RubricItem.Label;
}
}
private void editItem(string label, int points) private void editItem(string label, int points)
{ {
var newRubricItem = RubricItem with var newRubricItem = RubricItem with
@@ -39,7 +53,7 @@
id="rubricLabel" id="rubricLabel"
name="rubricLabel" name="rubricLabel"
@oninput="@(e => editItem(e.Value?.ToString() ?? "", RubricItem.Points))" @oninput="@(e => editItem(e.Value?.ToString() ?? "", RubricItem.Points))"
value="@RubricItem.Label" @bind="label"
/> />
</div> </div>
<div class="col"> <div class="col">
@@ -57,7 +71,7 @@
RubricItem.Label, RubricItem.Label,
int.Parse(e.Value?.ToString() != "" ? e.Value?.ToString() ?? "0" : "0")) int.Parse(e.Value?.ToString() != "" ? e.Value?.ToString() ?? "0" : "0"))
)" )"
value="@RubricItem.Points" @bind="points"
type="number" type="number"
/> />
</div> </div>

View File

@@ -137,6 +137,7 @@
@oninput="handleNewDescription" @oninput="handleNewDescription"
/> />
</div> </div>
<QuizSettings />
@foreach(var question in quizContext.Quiz.Questions) @foreach(var question in quizContext.Quiz.Questions)
{ {

View File

@@ -42,7 +42,9 @@
{ {
if (quizContext.Quiz != null) if (quizContext.Quiz != null)
{ {
var newPoints = int.Parse(e.Value?.ToString() ?? "0"); var pointsString = e.Value?.ToString() ?? "0";
var newPoints = int.Parse(pointsString == string.Empty ? "0" : pointsString);
var newQuestion = Question with var newQuestion = Question with
{ {
Points = newPoints Points = newPoints
@@ -123,28 +125,20 @@
/> />
</div> </div>
<div class="col-auto"> <div class="col-auto">
@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> </div>
</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="mb-3 row">
<div class="col-auto"> <div class="col-auto">
<label <label
@@ -166,15 +160,6 @@
@if(questionType == QuestionType.MULTIPLE_ANSWERS || questionType == QuestionType.MULTIPLE_CHOICE) @if(questionType == QuestionType.MULTIPLE_ANSWERS || questionType == QuestionType.MULTIPLE_CHOICE)
{ {
@foreach(var answer in answers)
{
<EditableQuizAnswer
Answer="answer"
SaveAnswer="saveAnswer"
Question="Question"
/>
}
<button <button
class="btn btn-outline-danger" class="btn btn-outline-danger"
@onclick="removeAnswer" @onclick="removeAnswer"
@@ -187,6 +172,15 @@
> >
+ Add Answer + Add Answer
</button> </button>
@foreach(var answer in answers)
{
<EditableQuizAnswer
Answer="answer"
SaveAnswer="saveAnswer"
Question="Question"
/>
}
} }
<div> <div>

View File

@@ -0,0 +1,74 @@
@using Management.Web.Shared.Components
@inject QuizEditorContext quizContext
@code {
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
}
private void reload()
{
if (quizContext.Quiz != null)
{
if (!lockAtDueDate)
lockAtDueDate = quizContext.Quiz.LockAtDueDate;
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 bool lockAtDueDate { get; set; }
private DateTime? lockAt { get; set; }
private bool shuffleAnswers { get; set; }
private bool oneQuestionAtATime { get; set; }
private int allowedAttempts { get; set; }
private void handleLockAtDueDateChange()
{
if(quizContext.Quiz != null)
{
var newValue = !quizContext.Quiz.LockAtDueDate;
var newQuiz = newValue
? quizContext.Quiz with
{
LockAtDueDate = newValue,
LockAt = quizContext.Quiz.DueAt
}
: quizContext.Quiz with
{
LockAtDueDate = newValue
};
quizContext.SaveQuiz(newQuiz);
}
}
}
<div>
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="lockAtDueDate"
checked="lockAtDueDate"
@onchange="handleLockAtDueDateChange">
<label
class="form-check-label" for="lockAtDueDate">
Lock at Due Date
</label>
</div>
</div>

View File

@@ -16,7 +16,8 @@
{ {
var newModule = new LocalModule var newModule = new LocalModule
{ {
Name=Name Name=Name,
Id=Guid.NewGuid().ToString()
}; };

View File

@@ -12,10 +12,9 @@ public record LocalQuiz
public bool ShuffleAnswers { get; init; } public bool ShuffleAnswers { get; init; }
public bool OneQuestionAtATime { get; init; } public bool OneQuestionAtATime { get; init; }
public int AllowedAttempts { get; init; } = -1; // -1 is infinite public int AllowedAttempts { get; init; } = -1; // -1 is infinite
public bool ShowCorrectAnswers { get; init; } // public bool ShowCorrectAnswers { get; init; }
public int? TimeLimit { get; init; } = null; // public int? TimeLimit { get; init; } = null;
public string? HideResults { get; init; } = null; // public string? HideResults { get; init; } = null;
// If null, students can see their results after any attempt. // If null, students can see their results after any attempt.
// If “always”, students can never see their results. // If “always”, students can never see their results.
// If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null. // If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null.

View File

@@ -40,9 +40,9 @@ public class CanvasQuizService
title = localQuiz.Name, title = localQuiz.Name,
description = localQuiz.Description, description = localQuiz.Description,
// assignment_group_id = "quiz", TODO: support specific assignment groups // assignment_group_id = "quiz", TODO: support specific assignment groups
time_limit = localQuiz.TimeLimit, // time_limit = localQuiz.TimeLimit,
shuffle_answers = localQuiz.ShuffleAnswers, shuffle_answers = localQuiz.ShuffleAnswers,
hide_results = localQuiz.HideResults, // hide_results = localQuiz.HideResults,
allowed_attempts = localQuiz.AllowedAttempts, allowed_attempts = localQuiz.AllowedAttempts,
one_question_at_a_time = true, one_question_at_a_time = true,
cant_go_back = false, cant_go_back = false,

View File

@@ -74,7 +74,14 @@ public class CanvasService
Console.WriteLine($"Creating Module: {name}"); Console.WriteLine($"Creating Module: {name}");
var url = $"courses/{courseId}/modules"; var url = $"courses/{courseId}/modules";
var request = new RestRequest(url); var request = new RestRequest(url);
request.AddParameter("module[name]", name); var body = new
{
module = new
{
name = name
}
};
request.AddBody(body);
var (newModule, _) = await webRequestor.PostAsync<CanvasModule>(request); var (newModule, _) = await webRequestor.PostAsync<CanvasModule>(request);
return newModule ?? throw new Exception($"failed to create new canvas module {name}"); return newModule ?? throw new Exception($"failed to create new canvas module {name}");

View File

@@ -14,7 +14,7 @@ public class YamlManager
public LocalCourse ParseCourse(string rawCourse) public LocalCourse ParseCourse(string rawCourse)
{ {
var deserializer = new DeserializerBuilder().Build(); var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
var course = deserializer.Deserialize<LocalCourse>(rawCourse); var course = deserializer.Deserialize<LocalCourse>(rawCourse);
return course; return course;