updating assignment form to be more real time

This commit is contained in:
2023-08-08 16:27:52 -06:00
parent 7a2ee58617
commit 1494fd1906
15 changed files with 306 additions and 112 deletions

View File

@@ -39,7 +39,7 @@ builder.Services.AddScoped<CanvasService, CanvasService>();
builder.Services.AddScoped<YamlManager>(); builder.Services.AddScoped<YamlManager>();
builder.Services.AddScoped<CoursePlanner>(); builder.Services.AddScoped<CoursePlanner>();
builder.Services.AddScoped<AssignmentDragContainer>(); builder.Services.AddScoped<DragContainer>();
var app = builder.Build(); var app = builder.Build();

View File

@@ -2,74 +2,118 @@
@inject CoursePlanner planner @inject CoursePlanner planner
@code @code
{ {
[Parameter] protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
[Parameter, EditorRequired]
public LocalAssignment Assignment { get; set; } = default!;
public string Description { get; set; } = default!; public string Description { get; set; } = default!;
[Parameter]
public EventCallback<string> DescriptionChanged { get; set; }
[Parameter]
public bool UseTemplate { get; set; } public bool UseTemplate { get; set; }
[Parameter]
public EventCallback<bool> UseTemplateChanged { get; set; }
[Parameter]
public string? TemplateId { get; set; } public string? TemplateId { get; set; }
[Parameter]
public EventCallback<string?> TemplateIdChanged { get; set; }
[Parameter]
public Dictionary<string, string> VariableValues { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> VariableValues { get; set; } = new Dictionary<string, string>();
[Parameter]
public EventCallback<Dictionary<string, string>> VariableValuesChanged { get; set; } protected override void OnParametersSet()
{
Description = Assignment.Description;
TemplateId = Assignment.TemplateId;
UseTemplate = Assignment.TemplateId != null && Assignment.TemplateId != "";
}
private AssignmentTemplate? selectedTemplate => private AssignmentTemplate? selectedTemplate =>
planner planner
.LocalCourse? .LocalCourse?
.AssignmentTemplates .AssignmentTemplates
.FirstOrDefault(t => t.Id == TemplateId); .FirstOrDefault(t => t.Id == Assignment.TemplateId);
public string Preview => Markdown.ToHtml(Description); public string Preview => Markdown.ToHtml(Assignment.Description);
private void SaveAssignment(LocalAssignment newAssignment)
{
if(planner.LocalCourse != null)
{
var currentModule = planner
.LocalCourse
.Modules
.First(m =>
m.Assignments
.Select(a => a.Id)
.Contains(Assignment.Id)
) ?? throw new Exception("could not find current module in assignment description form");
var updatedModules = planner.LocalCourse.Modules.Select(m =>
m.Name == currentModule.Name
? currentModule with
{
Assignments=currentModule.Assignments.Select(a =>
a.Id == newAssignment.Id
? newAssignment
: a
).ToArray()
}
: m
).ToArray();
planner.LocalCourse = planner.LocalCourse with
{
Modules=updatedModules
};
}
}
} }
<div class="form-check form-switch"> <div class="form-check form-switch">
<input <input
class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
role="switch" role="switch"
id="useTemplateForDescription" id="useTemplateForDescription"
checked="@UseTemplate" @bind="UseTemplate"
@onchange="async (e) => <label class="form-check-label" for="useTemplateForDescription">
await UseTemplateChanged.InvokeAsync((bool)(e.Value ?? false))"
>
<label
class="form-check-label"
for="useTemplateForDescription"
>
use template for description use template for description
</label> </label>
</div> </div>
@if(UseTemplate) @if (UseTemplate)
{ {
@if(planner.LocalCourse != null) @if (planner.LocalCourse != null)
{ {
<div class="row justify-content-around"> <div class="row justify-content-around">
<div class="col-auto text-center"> <div class="col-auto text-center">
<form @onsubmit:preventDefault="true"> <form @onsubmit:preventDefault="true">
<label for="templateSelect">Templates</label> <label for="templateSelect">Templates</label>
<select <select
id="templateSelect" id="templateSelect"
class="form-select" class="form-select"
value="@TemplateId" @onchange="@((e) =>
@onchange="async (e) => {
await TemplateIdChanged.InvokeAsync(e.Value?.ToString())" var newTemplateId = e.Value?.ToString();
> SaveAssignment(Assignment with
<option value=""></option> {
@foreach (var template in planner.LocalCourse.AssignmentTemplates) TemplateId = newTemplateId
});
})"
>
<option value=""></option>
@foreach (var template in planner.LocalCourse.AssignmentTemplates)
{ {
<option value="@template.Id">@template.Name</option> <option value="@template.Id">@template.Name</option>
} }
@@ -78,30 +122,30 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
VARIABLES: VARIABLES:
@if(selectedTemplate != null) @if (selectedTemplate != null)
{ {
var variables = AssignmentTemplate.GetVariables(selectedTemplate.Markdown); var variables = AssignmentTemplate.GetVariables(selectedTemplate.Markdown);
@foreach(var variable in variables) @foreach (var variable in variables)
{ {
<div class="my-1"> <div class="my-1">
<label <label class="form-label">
class="form-label"
>
@variable @variable
</label> </label>
<input <input
class="form-control" class="form-control"
value="@VariableValues.GetValueOrDefault(variable, String.Empty)" value="@VariableValues.GetValueOrDefault(variable, String.Empty)"
@oninput="async (e) => @oninput="@((e) =>
{ {
var newValue = e.Value?.ToString() ?? String.Empty; var newValue = e.Value?.ToString() ?? String.Empty;
var newDictionary = new Dictionary<string, string>(VariableValues); var newDictionary = new Dictionary<string, string>(VariableValues);
newDictionary[variable] = newValue; newDictionary[variable] = newValue;
await VariableValuesChanged.InvokeAsync(newDictionary); SaveAssignment(Assignment with
} {
" TemplateVariables = newDictionary
/> });
})"
/>
</div> </div>
} }
} }
@@ -111,34 +155,36 @@
} }
else else
{ {
<div class="row">
<div class="col">
<label
for="description"
class="form-label"
>
Description
</label>
</div>
<div class="col">
HTML Preview
</div> <div class="row">
<div class="col">
<label for="description" class="form-label">
Description
</label>
</div> </div>
<div class="col">
HTML Preview
</div>
</div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<textarea <textarea
id="description" id="description"
class="form-control" class="form-control"
value="@Description" rows=12
rows=12 @oninput="@((e) =>
@oninput="async (e) => {
await DescriptionChanged.InvokeAsync(e.Value?.ToString() ?? String.Empty)" var newDescription = e.Value?.ToString();
/> SaveAssignment(Assignment with
{
Description = newDescription ?? ""
});
})"
/>
</div> </div>
<div class="col"> <div class="col">
@((MarkupString) Preview) @((MarkupString)Preview)
</div> </div>
</div> </div>

View File

@@ -4,6 +4,18 @@
@inject CanvasService canvas @inject CanvasService canvas
@code { @code {
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
[Parameter] [Parameter]
[EditorRequired] [EditorRequired]
@@ -20,12 +32,6 @@
[Parameter] [Parameter]
public Action OnHide { get; set; } = () => { }; public Action OnHide { get; set; } = () => { };
public Modal? AssignmentModal { get; set; } = null; public Modal? AssignmentModal { get; set; } = null;
private bool useTemplate { get; set; } = false;
private string? templateId { get; set; }
public Dictionary<string, string> templateVariables { get; set; } =
new Dictionary<string, string>();
private string description { get; set; } = String.Empty;
private string name { get; set; } = String.Empty; private string name { get; set; } = String.Empty;
private bool lockAtDueDate { get; set; } private bool lockAtDueDate { get; set; }
private IEnumerable<RubricItem> rubric { get; set; } = Enumerable.Empty<RubricItem>(); private IEnumerable<RubricItem> rubric { get; set; } = Enumerable.Empty<RubricItem>();
@@ -38,13 +44,9 @@
AssignmentModal?.Show(); AssignmentModal?.Show();
} }
name = Assignment.Name; name = Assignment.Name;
description = Assignment.Description;
lockAtDueDate = Assignment.LockAtDueDate; lockAtDueDate = Assignment.LockAtDueDate;
rubric = Assignment.Rubric; rubric = Assignment.Rubric;
submissionTypes = Assignment.SubmissionTypes; submissionTypes = Assignment.SubmissionTypes;
templateId = Assignment.TemplateId;
useTemplate = Assignment.UseTemplate;
templateVariables = Assignment.TemplateVariables;
} }
private void submitHandler() private void submitHandler()
@@ -54,18 +56,13 @@
.Select(s => s.Points) .Select(s => s.Points)
.Sum(); .Sum();
var newAssignment = Assignment with var newAssignment = Assignment with
{ {
Name=name, Name=name,
Description=description,
LockAtDueDate=lockAtDueDate, LockAtDueDate=lockAtDueDate,
Rubric=rubric, Rubric=rubric,
PointsPossible=totalRubricPoints, PointsPossible=totalRubricPoints,
SubmissionTypes=submissionTypes, SubmissionTypes=submissionTypes,
UseTemplate=useTemplate,
TemplateId=templateId,
TemplateVariables=templateVariables,
}; };
if(planner.LocalCourse != null) if(planner.LocalCourse != null)
@@ -97,6 +94,7 @@
} }
AssignmentModal?.Hide(); AssignmentModal?.Hide();
} }
private void updateRubric(IEnumerable<RubricItem> newRubric) private void updateRubric(IEnumerable<RubricItem> newRubric)
{ {
rubric = newRubric; rubric = newRubric;
@@ -162,10 +160,7 @@
</div> </div>
<div class="m-1"> <div class="m-1">
<AssignmentDescriptionEditor <AssignmentDescriptionEditor
@bind-Description="description" Assignment="Assignment"
@bind-UseTemplate="useTemplate"
@bind-TemplateId="templateId"
@bind-VariableValues="templateVariables"
/> />
</div> </div>
@@ -196,7 +191,7 @@
Class="btn btn-danger" Class="btn btn-danger"
OnConfirmAsync="HandleDelete" OnConfirmAsync="HandleDelete"
/> />
<button class="btn btn-primary" @onclick="submitHandler"> <button class="btn btn-primary" @onclick="@(() => AssignmentModal?.Hide())">
Save Save
</button> </button>
</Footer> </Footer>

View File

@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Components;
namespace Management.Web.Shared.Components.Quiz;
public class DroppableQuiz : ComponentBase
{
[Inject]
protected CoursePlanner planner { get; set; } = default!;
[Parameter, EditorRequired]
public LocalQuiz Quiz { get; set; } = default!;
internal void dropCallback(DateTime? dropDate, LocalModule? dropModule)
{
if (dropDate != null)
{
dropOnDate(dropDate ?? throw new Exception("drop date for quiz is null"));
}
else if (dropModule != null)
{
dropOnModule(dropModule);
}
}
private void dropOnDate(DateTime dropDate)
{
if (planner.LocalCourse == null)
return;
var currentModule =
planner.LocalCourse.Modules.First(m => m.Quizzes.Select(q => q.Id).Contains(Quiz.Id))
?? throw new Exception("in quiz callback, could not find module");
var defaultDueTimeDate = new DateTime(
year: dropDate.Year,
month: dropDate.Month,
day: dropDate.Day,
hour: planner.LocalCourse.DefaultDueTime.Hour,
minute: planner.LocalCourse.DefaultDueTime.Minute,
second: 0
);
var NewQuizList = currentModule.Quizzes
.Select(q => q.Id != Quiz.Id ? q : q with { DueAt = defaultDueTimeDate })
.ToArray();
var updatedModule = currentModule with { Quizzes = NewQuizList };
var updatedModules = planner.LocalCourse.Modules
.Select(m => m.Name == updatedModule.Name ? updatedModule : m)
.ToArray();
planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules };
}
private void dropOnModule(LocalModule dropModule)
{
if (planner.LocalCourse == null)
return;
var newModules = planner.LocalCourse.Modules
.Select(
m =>
m.Name != dropModule.Name
? m with
{
Quizzes = m.Quizzes.Where(q => q.Id != Quiz.Id).DistinctBy(q => q.Id)
}
: m with
{
Quizzes = m.Quizzes.Append(Quiz).DistinctBy(q => q.Id)
}
)
.ToArray();
var newCourse = planner.LocalCourse with { Modules = newModules };
planner.LocalCourse = newCourse;
}
}

View File

@@ -1,7 +1,7 @@
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@using Management.Web.Shared.Components.AssignmentForm @using Management.Web.Shared.Components.AssignmentForm
@inject AssignmentDragContainer dragContainer @inject DragContainer dragContainer
@inject CoursePlanner planner @inject CoursePlanner planner
@code { @code {
@@ -129,7 +129,6 @@
@ondragstart="HandleDragStart" @ondragstart="HandleDragStart"
@ondragend="HandleDragEnd" @ondragend="HandleDragEnd"
@onclick="@(() => showUpdateForm = true)" @onclick="@(() => showUpdateForm = true)"
class="draggableCart"
role="button" role="button"
> >
<div class="card"> <div class="card">

View File

@@ -1,11 +1,12 @@
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Quiz
@using Management.Web.Shared.Module.Assignment @using Management.Web.Shared.Module.Assignment
@using LocalModels @using LocalModels
@inject CoursePlanner configurationManagement @inject CoursePlanner configurationManagement
@inject CoursePlanner planner @inject CoursePlanner planner
@inject AssignmentDragContainer dragContainer @inject DragContainer dragContainer
@code { @code {
[Parameter, EditorRequired] [Parameter, EditorRequired]
@@ -110,6 +111,11 @@
{ {
<AssignmentDetails Assignment="a" Module="Module" /> <AssignmentDetails Assignment="a" Module="Module" />
} }
<br>
@foreach(var quiz in Module.Quizzes)
{
<QuizDetail Quiz="quiz" />
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -46,6 +46,7 @@
Modules=newModules Modules=newModules
}; };
} }
Name = "";
modal?.Hide(); modal?.Hide();
} }
} }

View File

@@ -0,0 +1,45 @@
@using Management.Web.Shared.Components.Quiz
@inject DragContainer dragContainer
@inherits DroppableQuiz
@code {
private bool showUpdateForm { get; set; } = false;
private void HandleDragStart()
{
dragContainer.DropCallback = dropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="@(() => showUpdateForm = true)"
role="button"
>
<div class="card">
<div class="card-body p-0">
<div class="card-title pt-2 px-2 m-0">
<h4>@Quiz.Name</h4>
</div>
<div class="card-text overflow-hidden p-2">
<div>Due At: @Quiz.DueAt</div>
</div>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
@using Management.Web.Shared.Components.AssignmentForm @using Management.Web.Shared.Components.AssignmentForm
@inject AssignmentDragContainer dragContainer @inject DragContainer dragContainer
@inject CoursePlanner planner @inject CoursePlanner planner
@code { @code {

View File

@@ -1,4 +1,4 @@
@inject AssignmentDragContainer dragContainer @inject DragContainer dragContainer
@inject CoursePlanner configurationManagement @inject CoursePlanner configurationManagement
@inject CoursePlanner planner @inject CoursePlanner planner

View File

@@ -1,4 +1,4 @@
public class AssignmentDragContainer public class DragContainer
{ {
public Action<DateTime?, LocalModule?>? DropCallback { get; set; } public Action<DateTime?, LocalModule?>? DropCallback { get; set; }
} }

View File

@@ -107,15 +107,40 @@ public static partial class CoursePlannerSyncronizationExtensions
{ {
var canvasAssignment = canvasAssignments.First(ca => ca.Id == localAssignment.CanvasId); var canvasAssignment = canvasAssignments.First(ca => ca.Id == localAssignment.CanvasId);
var localHtmlDescription = localAssignment.GetDescriptionHtml(courseAssignmentTemplates); var localHtmlDescription = localAssignment.GetDescriptionHtml(courseAssignmentTemplates)
.Replace("&gt;", "")
.Replace("&lt;", "")
.Replace(">", "")
.Replace("<", "");
var canvasHtmlDescription = canvasAssignment.Description; var canvasHtmlDescription = canvasAssignment.Description;
canvasHtmlDescription = CanvasScriptTagRegex().Replace(canvasHtmlDescription, ""); canvasHtmlDescription = CanvasScriptTagRegex().Replace(canvasHtmlDescription, "");
canvasHtmlDescription = CanvasLinkTagRegex().Replace(canvasHtmlDescription, ""); canvasHtmlDescription = CanvasLinkTagRegex().Replace(canvasHtmlDescription, "");
canvasHtmlDescription = canvasHtmlDescription.Replace("&gt;", ">"); canvasHtmlDescription = canvasHtmlDescription
canvasHtmlDescription = canvasHtmlDescription.Replace("&lt;", "<"); .Replace("&gt;", "")
.Replace("&lt;", "")
.Replace(">", "")
.Replace("<", "");
var dueDatesSame =
canvasAssignment.DueAt != null
&& new DateTime(
year: canvasAssignment.DueAt.Value.Year,
month: canvasAssignment.DueAt.Value.Month,
day: canvasAssignment.DueAt.Value.Day,
hour: canvasAssignment.DueAt.Value.Hour,
minute: canvasAssignment.DueAt.Value.Minute,
second: canvasAssignment.DueAt.Value.Second
)
== new DateTime(
year: localAssignment.DueAt.Year,
month: localAssignment.DueAt.Month,
day: localAssignment.DueAt.Day,
hour: localAssignment.DueAt.Hour,
minute: localAssignment.DueAt.Minute,
second: localAssignment.DueAt.Second
);
var dueDatesSame = canvasAssignment.DueAt == localAssignment.DueAt;
var descriptionSame = canvasHtmlDescription == localHtmlDescription; var descriptionSame = canvasHtmlDescription == localHtmlDescription;
var nameSame = canvasAssignment.Name == localAssignment.Name; var nameSame = canvasAssignment.Name == localAssignment.Name;
var lockDateSame = canvasAssignment.LockAt == localAssignment.LockAt; var lockDateSame = canvasAssignment.LockAt == localAssignment.LockAt;
@@ -128,7 +153,6 @@ public static partial class CoursePlannerSyncronizationExtensions
{ {
if (!dueDatesSame) if (!dueDatesSame)
{ {
Console.WriteLine( Console.WriteLine(
$"Due dates different for {localAssignment.Name}, local: {localAssignment.DueAt}, in canvas {canvasAssignment.DueAt}" $"Due dates different for {localAssignment.Name}, local: {localAssignment.DueAt}, in canvas {canvasAssignment.DueAt}"
); );

View File

@@ -41,7 +41,7 @@ public record LocalAssignment
public ulong? CanvasId { get; init; } = null; public ulong? CanvasId { get; init; } = null;
public string Name { get; init; } = ""; public string Name { get; init; } = "";
public string Description { get; init; } = ""; public string Description { get; init; } = "";
public bool UseTemplate { get; init; } = false; // public bool UseTemplate { get; init; } = false;
public string? TemplateId { get; init; } = string.Empty; public string? TemplateId { get; init; } = string.Empty;
public Dictionary<string, string> TemplateVariables { get; init; } = public Dictionary<string, string> TemplateVariables { get; init; } =
new Dictionary<string, string>(); new Dictionary<string, string>();
@@ -66,12 +66,12 @@ public record LocalAssignment
public string GetDescriptionHtml(IEnumerable<AssignmentTemplate>? templates) public string GetDescriptionHtml(IEnumerable<AssignmentTemplate>? templates)
{ {
if (UseTemplate && templates == null) if (TemplateId != null && TemplateId != "" && templates == null)
throw new Exception("cannot get description for assignment if templates not provided"); throw new Exception("cannot get description for assignment if templates not provided");
var rubricHtml = GetRubricHtml(); var rubricHtml = GetRubricHtml();
if (UseTemplate) if (TemplateId != null && TemplateId != "")
{ {
var template = var template =
(templates?.FirstOrDefault(t => t.Id == TemplateId)) (templates?.FirstOrDefault(t => t.Id == TemplateId))

View File

@@ -16,3 +16,4 @@ Development command: `dotnet watch --project Management.Web/`
Apparently the VSCode razor extension was compiled with a preview of dotnet 6... and only uses openssl 1.1. After installing openssl1.1 you can tell vscode to provide it with `export CLR_OPENSSL_VERSION_OVERRIDE=1.1; code ~/projects/canvasManagement`. Apparently the VSCode razor extension was compiled with a preview of dotnet 6... and only uses openssl 1.1. After installing openssl1.1 you can tell vscode to provide it with `export CLR_OPENSSL_VERSION_OVERRIDE=1.1; code ~/projects/canvasManagement`.
The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241) The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241)