restructured components so that there are more components in the pages

This commit is contained in:
2024-01-11 17:28:14 -07:00
parent 4fb5d9a25d
commit 5666d3dc85
22 changed files with 110 additions and 85 deletions

View File

@@ -0,0 +1,82 @@
@using Markdig
@code {
[Parameter, EditorRequired]
public LocalQuizQuestion Question { get; set; } = default!;
}
<div class="row justify-content-between text-secondary">
<div class="col">
points: @Question.Points
</div>
<div class="col-auto">
@Question.QuestionType
</div>
</div>
@((MarkupString)Question.HtmlText)
@if(Question.QuestionType == QuestionType.MATCHING)
{
@foreach(var answer in Question.Answers)
{
<div class="mx-3 mb-1 bg-dark px-2 rounded rounded-2 border row">
<div
class="col text-end my-auto p-1"
>
@answer.Text
</div>
<div
class="col my-auto"
>
@answer.MatchedText
</div>
</div>
}
}
else
{
@foreach(var answer in Question.Answers)
{
string answerPreview = answer.HtmlText.StartsWith("<p>")
? answer.HtmlText.Replace("<p>", "<p class='m-0'>")
: answer.HtmlText;
<div class="mx-3 mb-1 bg-dark px-2 rounded rounded-2 d-flex flex-row border">
@if(answer.Correct)
{
<svg
style="width: 1em;"
class="me-1 my-auto"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M4 12.6111L8.92308 17.5L20 6.5"
stroke="var(--bs-success)"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
}
else
{
<div
class="me-1 my-auto"
style="width: 1em;"
>
@if(Question.QuestionType == QuestionType.MULTIPLE_ANSWERS)
{
<span>[ ]</span>
}
</div>
}
<div class="markdownQuizAnswerPreview p-1">
@((MarkupString)answerPreview)
</div>
</div>
}
}

View File

@@ -0,0 +1,79 @@
@using Management.Web.Shared.Components
@inject QuizEditorContext quizContext
@code {
private Modal? modal { get; set; }
private LocalQuiz? testQuiz;
private string? error { get; set; } = null;
private string _quizMarkdownInput { get; set; } = "";
private string quizMarkdownInput
{
get => _quizMarkdownInput;
set
{
_quizMarkdownInput = value;
try
{
var newQuiz = LocalQuiz.ParseMarkdown(_quizMarkdownInput);
error = null;
testQuiz = newQuiz;
quizContext.SaveQuiz(newQuiz);
}
catch (QuizMarkdownParseException e)
{
error = e.Message;
StateHasChanged();
}
}
}
protected override void OnInitialized()
{
reload();
quizContext.StateHasChanged += reload;
}
private void reload()
{
if (quizContext.Quiz != null)
{
if (quizMarkdownInput == "")
{
quizMarkdownInput = quizContext.Quiz.ToMarkdown();
}
this.InvokeAsync(this.StateHasChanged);
}
}
public void Dispose()
{
quizContext.StateHasChanged -= reload;
}
}
<div class="d-flex flex-column h-100">
<div class="d-flex flex-row h-100 p-2">
<div class="row flex-grow-1">
<div class="col-6">
<MonacoTextArea
Value="@quizMarkdownInput"
OnChange="@((v) => quizMarkdownInput = v)"
/>
</div>
<div class="col-6 h-100 overflow-y-auto">
@if (error != null)
{
<p class="text-danger text-truncate">Error: @error</p>
}
@if(testQuiz != null)
{
<QuizPreview Quiz="testQuiz" />
}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,249 @@
@page "/course/{CourseName}/quiz/{QuizName}"
@using CanvasModel.EnrollmentTerms
@using CanvasModel.Quizzes
@using Management.Web.Shared.Components
@using CanvasModel.Courses
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@using LocalModels
@using Management.Web.Shared.Module.Assignment
@using Management.Web.Shared.Components
@inject FileStorageManager fileStorageManager
@inject CanvasService canvas
@inject CoursePlanner planner
@inject QuizEditorContext quizContext
@inject MyLogger<QuizFormPage> logger
@inject NavigationManager Navigation
@code {
[Parameter]
public string? CourseName { get; set; } = default!;
[Parameter]
public string? QuizName { get; set; } = default!;
private bool loading { get; set; } = true;
private bool addingQuizToCanvas = false;
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
quizContext.StateHasChanged -= reload;
}
protected override async Task OnInitializedAsync()
{
if (loading)
{
loading = false;
logger.Log($"loading quiz {CourseName} {QuizName}");
if (planner.LocalCourse == null)
{
var courses = await fileStorageManager.LoadSavedCourses();
planner.LocalCourse = courses.First(c => c.Settings.Name == CourseName);
logger.Log($"set course to '{planner.LocalCourse?.Settings.Name}'");
}
if (quizContext.Quiz == null)
{
var quiz = planner
.LocalCourse?
.Modules
.SelectMany(m => m.Quizzes)
.FirstOrDefault(q => q.Name == QuizName);
quizContext.Quiz = quiz;
logger.Log($"set quiz to '{quizContext.Quiz?.Name}'");
}
StateHasChanged();
if (planner.CanvasQuizzes == null)
{
await planner.LoadCanvasData();
}
base.OnInitialized();
StateHasChanged();
}
}
private void deleteQuiz()
{
quizContext.DeleteQuiz();
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name);
}
private async Task addToCanvas()
{
addingQuizToCanvas = true;
await quizContext.AddQuizToCanvas();
await planner.LoadCanvasData();
addingQuizToCanvas = false;
}
private void done()
{
quizContext.Quiz = null;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name);
}
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 int? quizPoints => quizContext.Quiz?.Questions.Sum(q => q.Points);
private bool showHelp = false;
private readonly static string exampleMarkdownQuestion = @"QUESTION REFERENCE
---
Points: 2
this is a question?
*a) correct
b) not correct
---
points: 1
question goes here
[*] correct
[ ] not correct
[] not correct
---
the points default to 1?
*a) true
b) false
---
Markdown is supported
- like
- this
- list
[*] true
[ ] false
---
This is a one point essay question
essay
---
points: 4
this is a short answer question
short_answer
---
points: 4
the underscore is optional
short answer
---
this is a matching question
^ left answer - right dropdown
^ other thing - another option
";
}
<div class="d-flex flex-column py-3" style="height: 100vh;">
<section>
<div class="row justify-content-between">
<div class="col-auto my-auto">
<button class="btn btn-outline-secondary" @onclick="done">
← go back
</button>
</div>
<div class="col-auto my-auto">
<h2>
@quizContext.Quiz?.Name
</h2>
</div>
@if (quizContext.Quiz == null)
{
<div class="col-auto">
<Spinner />
</div>
}
<div class="col-auto me-3">
<h3>
Questions: @quizContext.Quiz?.Questions.Count() - Points: @quizPoints
</h3>
@if (quizInCanvas != null)
{
@if (quizInCanvas?.Published == true)
{
<div class="text-success">
Published!
</div>
}
else
{
<div class="text-danger">
Not Published
</div>
}
}
</div>
</div>
</section>
<section
class="flex-grow-1 w-100 d-flex justify-content-center border rounded-4 bg-dark-subtle"
style="min-height: 10%; max-width: 100%;"
>
@if(showHelp)
{
<pre class="bg-dark-subtle me-3 pe-5 ps-3 rounded rounded-3">
@exampleMarkdownQuestion
</pre>
}
<div class="w-100" style="max-width: 120em; max-height: 100%;">
@if (quizContext.Quiz != null)
{
<MarkdownQuizForm />
}
</div>
</section>
<div>
<button
class="btn btn-outline-secondary mt-3"
@onclick="@(() => showHelp = !showHelp)"
>
toggle help
</button>
</div>
<section class="p-2">
@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">
Add to Canvas
</button>
@if (quizInCanvas != null)
{
<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">
Done
</button>
</div>
</div>
}
@if (addingQuizToCanvas)
{
<Spinner />
}
</section>
</div>

View File

@@ -0,0 +1,72 @@
@using Management.Web.Shared.Components
@inject QuizEditorContext quizContext
@code {
[Parameter, EditorRequired]
public LocalQuiz Quiz { get; set; } = default!;
protected override void OnInitialized()
{
quizContext.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
quizContext.StateHasChanged -= reload;
}
}
@if(Quiz != null)
{
<div class="row justify-content-start">
<div class="col-auto" style="min-width: 35em;">
<div class="row">
<div class="col-6 text-end">Name: </div>
<div class="col-6">@Quiz.Name</div>
</div>
<div class="row">
<div class="col-6 text-end">Due At: </div>
<div class="col-6">@Quiz.DueAt</div>
</div>
<div class="row">
<div class="col-6 text-end">Lock At: </div>
<div class="col-6">@Quiz.LockAt</div>
</div>
<div class="row">
<div class="col-6 text-end">Shuffle Answers: </div>
<div class="col-6">@Quiz.ShuffleAnswers</div>
</div>
<div class="row">
<div class="col-6 text-end">Allowed Attempts: </div>
<div class="col-6">@Quiz.AllowedAttempts</div>
</div>
<div class="row">
<div class="col-6 text-end">One question at a time: </div>
<div class="col-6">@Quiz.OneQuestionAtATime</div>
</div>
<div class="row">
<div class="col-6 text-end">Assignment Group: </div>
<div class="col-6">@Quiz.LocalAssignmentGroupName</div>
</div>
</div>
</div>
<div class="p-3" style="white-space: pre-wrap;">@Quiz.Description</div>
@foreach(var question in Quiz.Questions)
{
<div class="bg-dark-subtle mt-1 p-1 ps-2 rounded rounded-2">
<MarkdownQuestionPreview
Question="question"
@key="question"
/>
</div>
}
}