added template editor

This commit is contained in:
2023-07-26 15:51:44 -06:00
parent 9c547b3435
commit 87fc062dfb
14 changed files with 435 additions and 97 deletions

View File

@@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="dotenv.net" Version="3.1.2" /> <PackageReference Include="dotenv.net" Version="3.1.2" />
<PackageReference Include="Markdig" Version="0.31.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -1,10 +1,12 @@
@page "/" @page "/"
@using CanvasModel.EnrollmentTerms @using CanvasModel.EnrollmentTerms
@using Management.Web.Shared.Course @using Management.Web.Shared.Course
@using Management.Web.Shared.Module.Assignment.Templates
@using Management.Web.Shared.Semester @using Management.Web.Shared.Semester
@using CanvasModel.Courses @using CanvasModel.Courses
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@using LocalModels @using LocalModels
@using Management.Web.Shared.Module.Assignment
@inject CanvasService canvas @inject CanvasService canvas
@inject CoursePlanner planner @inject CoursePlanner planner
@@ -63,13 +65,15 @@ protected override void OnInitialized()
@if(planner.LocalCourse != null) @if(planner.LocalCourse != null)
{ {
<div class=""> <div class="mb-3">
<button <button
@onclick="@(() => planner.LocalCourse = null)" @onclick="@(() => planner.LocalCourse = null)"
class="btn btn-primary" class="btn btn-primary"
> >
Select New Course Select New Course
</button> </button>
<CourseSettings />
<AssignmentTemplateManagement />
</div> </div>
<CourseDetails /> <CourseDetails />
} }

View File

@@ -34,7 +34,7 @@
} }
<div class="modal @modalClass"> <div class="modal @modalClass">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title text-center w-100">@Title</h4> <h4 class="modal-title text-center w-100">@Title</h4>

View File

@@ -7,7 +7,6 @@
@code @code
{ {
private bool showEditCourseSettings = false;
protected override void OnInitialized() protected override void OnInitialized()
{ {
planner.StateHasChanged += reload; planner.StateHasChanged += reload;
@@ -21,22 +20,7 @@
planner.StateHasChanged -= reload; planner.StateHasChanged -= reload;
} }
} }
<br>
@if(!showEditCourseSettings)
{
<button class="btn btn-outline-secondary" @onclick="@(() => showEditCourseSettings = true)">Edit Course Settings</button>
}
else
{
<CourseSettings />
<button
class="btn btn-outline-secondary"
@onclick="@(() => showEditCourseSettings = false)"
>
Done Editing Course Settings
</button>
}
<div class="row"> <div class="row">

View File

@@ -4,6 +4,7 @@
@code @code
{ {
private Modal modal { get; set; } = default!;
protected override void OnInitialized() protected override void OnInitialized()
{ {
planner.StateHasChanged += reload; planner.StateHasChanged += reload;
@@ -25,7 +26,6 @@
_selectedTermId = value; _selectedTermId = value;
if(selectedTerm != null && planner.LocalCourse != null) if(selectedTerm != null && planner.LocalCourse != null)
{ {
System.Console.WriteLine(JsonSerializer.Serialize(selectedTerm));
planner.LocalCourse = planner.LocalCourse with planner.LocalCourse = planner.LocalCourse with
{ {
StartDate=selectedTerm.StartAt ?? new DateTime(), StartDate=selectedTerm.StartAt ?? new DateTime(),
@@ -52,82 +52,102 @@
} }
} }
<h5>Select Days Of Week</h5> <button
<div class="row m-3"> class="btn btn-outline-secondary"
@foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek))) @onclick="@(() => modal.Show())"
{ >
<div class="col"> Edit Course Settings
<button </button>
class="@(
planner.LocalCourse?.DaysOfWeek.Contains(day) ?? false <Modal @ref="modal">
? "btn btn-secondary" <Title>
: "btn btn-outline-secondary" <h1>Course Settings</h1>
)" </Title>
@onclick="() => <Body>
{ <h5 class="text-center">Select Days Of Week</h5>
if(planner.LocalCourse?.DaysOfWeek.Contains(day) ?? false) <div class="row m-3">
{ @foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)))
planner.LocalCourse = planner.LocalCourse with {
<div class="col">
<button
class="@(
planner.LocalCourse?.DaysOfWeek.Contains(day) ?? false
? "btn btn-secondary"
: "btn btn-outline-secondary"
)"
@onclick="() =>
{ {
DaysOfWeek = planner.LocalCourse.DaysOfWeek.Where((d) => d != day) if(planner.LocalCourse?.DaysOfWeek.Contains(day) ?? false)
};
}
else
{
if (planner.LocalCourse != null)
{
planner.LocalCourse = planner.LocalCourse with
{ {
DaysOfWeek = planner.LocalCourse.DaysOfWeek.Append(day) planner.LocalCourse = planner.LocalCourse with
}; {
} DaysOfWeek = planner.LocalCourse.DaysOfWeek.Where((d) => d != day)
} };
}" }
> else
@day {
</button> if (planner.LocalCourse != null)
{
planner.LocalCourse = planner.LocalCourse with
{
DaysOfWeek = planner.LocalCourse.DaysOfWeek.Append(day)
};
}
}
}"
>
@day
</button>
</div>
}
</div> </div>
}
</div>
@if(loading) @if(loading)
{ {
<Spinner /> <Spinner />
} }
@if (terms != null) @if (terms != null)
{ {
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-auto"> <div class="col-auto">
<form> <form @onsubmit:preventDefault="true">
<lablel for="termselect">Select Term for Start and End Date:</lablel> <label for="termselect">Select Term for Start and End Date:</label>
<select id="termselect" class="form-select" @bind="selectedTermId"> <select id="termselect" class="form-select" @bind="selectedTermId">
@foreach (var term in terms) @foreach (var term in terms)
{ {
<option value="@term.Id">@term.Name</option> <option value="@term.Id">@term.Name</option>
} }
</select> </select>
</form> </form>
</div> </div>
</div> </div>
} }
@if(planner.LocalCourse != null) @if(planner.LocalCourse != null)
{ {
<div class="row justify-content-center m-3 text-center"> <div class="row justify-content-center m-3 text-center">
<div class="col-auto"> <div class="col-auto">
<div>Default Assignment Due Time</div> <div>Default Assignment Due Time</div>
<TimePicker <TimePicker
Time="planner.LocalCourse.DefaultDueTime" Time="planner.LocalCourse.DefaultDueTime"
UpdateTime="@((newTime) => UpdateTime="@((newTime) =>
planner.LocalCourse = planner.LocalCourse =
planner.LocalCourse with planner.LocalCourse with
{ DefaultDueTime=newTime } { DefaultDueTime=newTime }
)" )"
/> />
</div> </div>
</div> </div>
} }
</Body>
<br> <Footer>
<button
class="btn btn-outline-secondary"
@onclick="@(() => modal.Hide())"
>
Done Editing Course Settings
</button>
</Footer>
</Modal>

View File

@@ -148,8 +148,8 @@
{ {
<div>Not synced with canvas</div> <div>Not synced with canvas</div>
} }
<div> <div class="text-center">
Rubric: Rubric
</div> </div>
@foreach(var rubricItem in Assignment.rubric) @foreach(var rubricItem in Assignment.rubric)
{ {
@@ -162,6 +162,15 @@
</div> </div>
</div> </div>
} }
<div>Submission Types:</div>
<ul>
@foreach(var type in Assignment.submission_types)
{
<li>
@type
</li>
}
</ul>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -27,6 +27,7 @@
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>();
private IEnumerable<SubmissionType> submissionTypes { get; set; } = Enumerable.Empty<SubmissionType>();
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
@@ -38,6 +39,7 @@
description = Assignment.description; description = Assignment.description;
lockAtDueDate = Assignment.lock_at_due_date; lockAtDueDate = Assignment.lock_at_due_date;
rubric = Assignment.rubric; rubric = Assignment.rubric;
submissionTypes = Assignment.submission_types;
} }
private void submitHandler() private void submitHandler()
@@ -53,6 +55,7 @@
lock_at_due_date=lockAtDueDate, lock_at_due_date=lockAtDueDate,
rubric=rubric, rubric=rubric,
points_possible=totalRubricPoints, points_possible=totalRubricPoints,
submission_types=submissionTypes,
}; };
if(planner.LocalCourse != null) if(planner.LocalCourse != null)
@@ -128,6 +131,10 @@
</label> </label>
</div> </div>
<RubricEditor Rubric="rubric" SetRubric="updateRubric" /> <RubricEditor Rubric="rubric" SetRubric="updateRubric" />
<SubmissionTypeSelector
Types="submissionTypes"
SetTypes="(newTypes) => submissionTypes = newTypes"
/>
</form> </form>
</Body> </Body>
<Footer> <Footer>

View File

@@ -0,0 +1,44 @@
@code
{
[Parameter, EditorRequired]
public IEnumerable<SubmissionType> Types { get; set; } = Enumerable.Empty<SubmissionType>();
[Parameter, EditorRequired]
public Action<IEnumerable<SubmissionType>> SetTypes { get; set; } = (_) => {};
private string getLabel(SubmissionType type)
{
return type.ToString().Replace("_", "") + "switch";
}
}
<h5>Submission Types</h5>
<div class="row">
@foreach (var submissionType in (SubmissionType[])Enum.GetValues(typeof(SubmissionType)))
{
<div class="col-4">
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="@getLabel(submissionType)"
checked="@Types.Contains(submissionType)"
@onchange="(e) => {
var isChecked = (bool)(e.Value ?? false);
if(isChecked)
SetTypes(Types.Append(submissionType));
else
SetTypes(Types.Where(t => t != submissionType));
}"
>
<label
class="form-check-label"
for="@getLabel(submissionType)"
>
@submissionType
</label>
</div>
</div>
}
</div>

View File

@@ -0,0 +1,122 @@
@using Management.Web.Shared.Components
@inject CoursePlanner planner
@code
{
private Modal modal { get; set; } = default!;
private string newTemplateName { get; set; } = "";
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
private string? _selectedTemplateId;
private string? selectedTemplateId
{
get { return _selectedTemplateId; }
set { _selectedTemplateId = value; }
}
private AssignmentTemplate? selectedTemplate =>
planner
.LocalCourse?
.AssignmentTemplates
.FirstOrDefault(t => t.Id == selectedTemplateId);
private void newTemplate()
{
if (planner.LocalCourse != null)
{
var newOne = new AssignmentTemplate()
{
Id=Guid.NewGuid().ToString(),
Name=newTemplateName
};
planner.LocalCourse = planner.LocalCourse with
{
AssignmentTemplates = planner.LocalCourse.AssignmentTemplates.Append(newOne)
};
newTemplateName = "";
}
}
}
<button
class="btn btn-outline-secondary"
@onclick="@(() => modal.Show())"
>
Manage Assignment Templates
</button>
@if(planner.LocalCourse != null)
{
<Modal @ref="modal">
<Title>
<h1>Assignment Templates</h1>
</Title>
<Body>
<div class="row justify-content-center">
<div class="col-auto">
<form @onsubmit:preventDefault="true">
<label for="termselect">Templates</label>
<select id="termselect" class="form-select" @bind="selectedTemplateId">
<option></option>
@foreach (var template in planner.LocalCourse.AssignmentTemplates)
{
<option value="@template.Id">@template.Name</option>
}
</select>
</form>
</div>
<div class="col-auto my-auto">
<form
@onsubmit:preventDefault="true"
@onsubmit="newTemplate"
>
<label
class="form-label"
for="newTemplateName"
>
New Template Name
</label>
<input
class="form-control"
type="text"
@bind="newTemplateName"
@bind:event="oninput"
/>
<button
class="btn btn-outline-primary"
>
New Template
</button>
</form>
</div>
</div>
@if(selectedTemplate != null)
{
<TemplateEditor Template="selectedTemplate" />
}
</Body>
<Footer>
<button
class="btn btn-outline-secondary"
@onclick="@(() => modal.Hide())"
>
Close
</button>
</Footer>
</Modal>
}

View File

@@ -0,0 +1,92 @@
@using Markdig
@inject CoursePlanner planner
@code
{
[Parameter, EditorRequired]
public AssignmentTemplate Template { get; set; } = default!;
string markdownHtml = "";
protected override void OnInitialized()
{
base.OnInitialized();
}
public string Preview => Markdown.ToHtml(Template.Markdown);
private void SetName(string newName)
{
if(planner.LocalCourse != null)
{
var newTemplates = planner.LocalCourse.AssignmentTemplates.Select(t =>
t.Id == Template.Id
? t with { Name=newName }
: t
);
planner.LocalCourse = planner.LocalCourse with
{
AssignmentTemplates=newTemplates
};
}
}
private void SetMarkdown(string newMarkdown)
{
if(planner.LocalCourse != null)
{
var newTemplates = planner.LocalCourse.AssignmentTemplates.Select(t =>
t.Id == Template.Id
? t with { Markdown=newMarkdown }
: t
);
planner.LocalCourse = planner.LocalCourse with
{
AssignmentTemplates=newTemplates
};
}
}
}
<div class="row justify-content-center m-3">
<div class="col-6">
<input
class="form-control"
type="text"
value="@Template.Name"
@oninput="@((e) => {
var newValue = (string) (e.Value ?? "");
SetName(newValue);
})"
>
</div>
</div>
<div class="row">
<div class="col-6">
<textarea
rows="30"
class="form-control"
value="@Template.Markdown"
@oninput="@((e) => {
var newValue = (string) (e.Value ?? "");
SetMarkdown(newValue);
})"
/>
</div>
<div class="col-6">
@((MarkupString) Preview)
</div>
</div>
<br>
<h5 class="text-center">Detected Template Variables</h5>
<div class="row justify-content-center">
<div class="col-auto">
<ul>
@foreach (var variable in AssignmentTemplate.GetVariables(Template.Markdown))
{
<li>@variable</li>
}
</ul>
</div>
</div>

View File

@@ -0,0 +1,18 @@
using System.Text.RegularExpressions;
namespace LocalModels;
public record AssignmentTemplate
{
public string Id { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
public string Markdown { get; set; } = String.Empty;
public static IEnumerable<string> GetVariables(string markdown)
{
string pattern = "{{(.*?)}}";
MatchCollection matches = Regex.Matches(markdown, pattern);
return matches.Select(match => match.Groups[1].Value);
}
}

View File

@@ -8,16 +8,16 @@ public record RubricItem
public enum SubmissionType public enum SubmissionType
{ {
online_text_entry,
online_upload,
online_quiz, online_quiz,
none,
on_paper, on_paper,
discussion_topic, discussion_topic,
external_tool, external_tool,
online_upload,
online_text_entry,
online_url, online_url,
media_recording, media_recording,
student_annotation, student_annotation,
none,
} }
public record LocalAssignment public record LocalAssignment

View File

@@ -9,6 +9,8 @@ public record LocalCourse
public DateTime StartDate { get; init; } public DateTime StartDate { get; init; }
public DateTime EndDate { get; init; } public DateTime EndDate { get; init; }
public SimpleTimeOnly DefaultDueTime { get; init; } = new SimpleTimeOnly(); public SimpleTimeOnly DefaultDueTime { get; init; } = new SimpleTimeOnly();
public IEnumerable<AssignmentTemplate> AssignmentTemplates { get; init; } =
Enumerable.Empty<AssignmentTemplate>();
} }
public record SimpleTimeOnly public record SimpleTimeOnly

35
tmp.json Normal file
View File

@@ -0,0 +1,35 @@
[
{
"id": 753,
"name": "Summer 2023",
"sis_term_id": null,
"sis_import_id": null,
"start_at": "2023-05-10T06:00:00Z",
"end_at": "2023-07-29T05:59:59Z",
"grading_period_group_id": null,
"workflow_state": "active",
"overrides": null
},
{
"id": 751,
"name": "Fall 2023",
"sis_term_id": null,
"sis_import_id": null,
"start_at": "2023-08-21T06:00:00Z",
"end_at": "2023-12-15T06:59:59Z",
"grading_period_group_id": null,
"workflow_state": "active",
"overrides": null
},
{
"id": 773,
"name": "Spring 2024",
"sis_term_id": null,
"sis_import_id": null,
"start_at": "2024-01-02T00:00:00Z",
"end_at": "2024-04-26T00:00:00Z",
"grading_period_group_id": null,
"workflow_state": "active",
"overrides": null
}
]