can create and drag and drop pages

This commit is contained in:
2024-01-12 15:06:15 -07:00
parent a4179e6d52
commit 9f19704724
33 changed files with 651 additions and 319 deletions

View File

@@ -272,7 +272,7 @@ public class FileStorageTests
Pages = [ Pages = [
new () { new () {
Name = "test page persistence", Name = "test page persistence",
DueDateForOrdering = new DateTime(), DueAt = new DateTime(),
Text = "this is some\n## markdown\n" Text = "this is some\n## markdown\n"
} }
] ]

View File

@@ -9,7 +9,7 @@ public class PageMarkdownTests
{ {
Name = "test title", Name = "test title",
Text = "test text content", Text = "test text content",
DueDateForOrdering = new DateTime() DueAt = new DateTime()
}; };
var pageMarkdown = page.ToMarkdown(); var pageMarkdown = page.ToMarkdown();

View File

@@ -4,7 +4,7 @@
@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.Pages.Course.Module.Assignment @using Management.Web.Pages.Course.Module.ModuleItems
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@inject FileStorageManager fileStorageManager @inject FileStorageManager fileStorageManager

View File

@@ -3,7 +3,7 @@
@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.Pages.Course.Module.Assignment @using Management.Web.Pages.Course.Module.ModuleItems
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@inject FileStorageManager fileStorageManager @inject FileStorageManager fileStorageManager

View File

@@ -1,4 +1,4 @@
@using Management.Web.Course.Module.Assignment @using Management.Web.Course.Module.ModuleItems
@inject DragContainer dragContainer @inject DragContainer dragContainer
@inject NavigationManager Navigation @inject NavigationManager Navigation

View File

@@ -3,12 +3,12 @@
@inject CoursePlanner planner @inject CoursePlanner planner
@code @code
{ {
[Parameter, EditorRequired] [Parameter, EditorRequired]
public DateTime? date { get; set; } = public DateTime? date { get; set; } =
default!; default!;
private bool isWeekDay { private bool isWeekDay {
get => date?.DayOfWeek != null; get => date?.DayOfWeek != null;
} }
@@ -25,8 +25,8 @@
{ {
planner.StateHasChanged -= reload; planner.StateHasChanged -= reload;
} }
private IEnumerable<LocalAssignment> TodaysAssignments private IEnumerable<LocalAssignment> TodaysAssignments
{ {
get get
{ {
@@ -39,7 +39,7 @@
} }
} }
private IEnumerable<LocalQuiz> todaysQuizzes private IEnumerable<LocalQuiz> todaysQuizzes
{ {
get get
{ {
@@ -51,11 +51,25 @@
.Where(q => q.DueAt.Date == date?.Date); .Where(q => q.DueAt.Date == date?.Date);
} }
} }
private IEnumerable<LocalCoursePage> todaysPages
{
get
{
if(planner.LocalCourse == null || date == null)
return Enumerable.Empty<LocalCoursePage>();
else
return planner.LocalCourse.Modules
.SelectMany(m => m.Pages)
.Where(q => q.DueAt.Date == date?.Date);
}
}
private string calculatedClass private string calculatedClass
{ {
get get
{ {
var baseClasses = "col border rounded rounded-3 p-2 pb-4 m-1 "; var baseClasses = "col border rounded rounded-3 p-2 pb-4 m-1 ";
if(dragging) if(dragging)
return baseClasses + " bg-secondary text-light "; return baseClasses + " bg-secondary text-light ";
@@ -66,15 +80,15 @@
{ {
DayOfWeek? weekDay = date?.DayOfWeek; DayOfWeek? weekDay = date?.DayOfWeek;
DayOfWeek notNullDay = weekDay ?? default; DayOfWeek notNullDay = weekDay ?? default;
var isClassDay = planner.LocalCourse?.Settings.DaysOfWeek.Contains(notNullDay) ?? false; var isClassDay = planner.LocalCourse?.Settings.DaysOfWeek.Contains(notNullDay) ?? false;
var dayInSemester = var dayInSemester =
isClassDay isClassDay
&& date <= planner.LocalCourse?.Settings.EndDate && date <= planner.LocalCourse?.Settings.EndDate
&& date >= planner.LocalCourse?.Settings.StartDate; && date >= planner.LocalCourse?.Settings.StartDate;
var totalClasses = dayInSemester var totalClasses = dayInSemester
? "bg-light-subtle text-light " + baseClasses ? "bg-light-subtle text-light " + baseClasses
: " " + baseClasses; : " " + baseClasses;
return totalClasses; return totalClasses;
@@ -105,9 +119,9 @@
dragContainer.DropCallback?.Invoke(d, null); dragContainer.DropCallback?.Invoke(d, null);
} }
} }
} }
<div <div
class="@calculatedClass" class="@calculatedClass"
@ondrop="@(() => OnDrop())" @ondrop="@(() => OnDrop())"
@ondragenter="OnDragEnter" @ondragenter="OnDragEnter"
@@ -118,13 +132,17 @@
<ul class="m-0 ps-3"> <ul class="m-0 ps-3">
@foreach (var assignment in TodaysAssignments) @foreach (var assignment in TodaysAssignments)
{ {
@* Console.WriteLine($"assignment: {assignment.Name}"); *@ <AssignmentInDay Assignment="assignment" @key="@assignment" />
<AssignmentInDay Assignment="assignment" @key="@assignment" />
} }
@foreach(var quiz in todaysQuizzes) @foreach(var quiz in todaysQuizzes)
{ {
<QuizInDay Quiz="quiz" @key="@quiz" /> <QuizInDay Quiz="quiz" @key="@quiz" />
} }
@foreach(var page in todaysPages)
{
<PageInDay Page="page" @key="page" />
}
</ul> </ul>
</div> </div>

View File

@@ -0,0 +1,55 @@
@using Management.Web.Course.Module.ModuleItems
@inject DragContainer dragContainer
@inject NavigationManager Navigation
@inject PageEditorContext pageContext
@inject MyLogger<PageInDay> logger
@inherits DroppablePage
@code {
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
private void HandleDragStart()
{
dragContainer.DropCallback = dropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private void OnClick()
{
if(planner.LocalCourse != null)
{
pageContext.Page = Page;
Navigation.NavigateTo("/course/" + planner.LocalCourse.Settings.Name + "/page/" + Page.Name);
logger.Log("navigating to coursePage page");
}
}
}
<li
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
@Page.Name
</li>

View File

@@ -1,201 +0,0 @@
@using Management.Web.Shared.Components
@using Management.Web.Course.Module.Assignment
@using CanvasModel.Assignments
@inject DragContainer dragContainer
@inject NavigationManager Navigation
@inject AssignmentEditorContext assignmentContext
@inherits DroppableAssignment
@code {
[Parameter]
[EditorRequired]
public LocalModule Module { get; set; } = new();
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
private bool showAll { get; set; } = false;
private void HandleDragStart()
{
dragContainer.DropCallback = DropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private CanvasAssignment? assignmentInCanvas => planner
.CanvasAssignments?
.FirstOrDefault(
a => a.Name == Assignment.Name
);
private bool existsInCanvas =>
assignmentInCanvas != null;
private void OnClick()
{
assignmentContext.Assignment = Assignment;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/assignment/" + Assignment.Name);
}
private bool NeedsToBeUpdatedInCanvas => planner.LocalCourse != null
&& planner.LocalCourse.Settings.CanvasId != null
&& planner.CanvasAssignments != null
&& planner.CanvasModules != null
&& assignmentInCanvas != null
&& Assignment.NeedsUpdates(
(CanvasAssignment)assignmentInCanvas,
Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups)
);
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
<div class="card">
<div class="card-body p-0">
<div class="card-title pt-2 px-2 m-0">
<div class="row mx-1">
<div class="col offset-2 offset-lg-1 ">
<h4 class="text-center m-0">
@Assignment.Name
</h4>
</div>
<div class="col-2 col-lg-1 text-end">
@if(existsInCanvas)
{
@if(NeedsToBeUpdatedInCanvas)
{
<SyncIcon />
}
else
{
<CheckIcon />
}
}
else
{
<SyncIcon />
}
</div>
</div>
</div>
@if(
planner.LocalCourse != null
&& existsInCanvas
&& NeedsToBeUpdatedInCanvas
&& assignmentInCanvas != null
)
{
<div class="mx-3 text-body-tertiary">
@Assignment.GetUpdateReason(
(CanvasAssignment)assignmentInCanvas,
Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups))
</div>
}
@if(!existsInCanvas)
{
<div class="mx-3 text-body-tertiary">
no assignment with same name in canvas
</div>
}
@if(!showAll)
{
<div class="card-text overflow-hidden p-2" style="max-height: 5rem;">
<div>Points: @Assignment.PointsPossible</div>
<div>Due At: @Assignment.DueAt</div>
</div>
}
else
{
<div class="card-text">
<div class="px-3 py-1 bg-dark-subtle my-1">
@((MarkupString) @Assignment.GetDescriptionHtml())
</div>
<section class="px-3">
<div>Points: @Assignment.PointsPossible</div>
<div>Due At: @Assignment.DueAt</div>
<div>Lock At: @Assignment.LockAt</div>
<div>Submission Types:</div>
<ul>
@foreach(var type in Assignment.SubmissionTypes)
{
<li>
@type
</li>
}
</ul>
</section>
</div>
}
@if(!showAll)
{
<div
class="text-center fs-3 fw-bold lh-1 text-primary"
role="button"
@onclick:preventDefault="true"
@onclick:stopPropagation="true"
@onclick="() => showAll = true"
>
<svg
width="30"
height="30"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 10a2 2 0 11-4.001-.001A2 2 0 016 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0112 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0118 10z"
fill="var(--bs-primary)"
/>
</svg>
</div>
}
else
{
<div
class="text-center fs-3 fw-bold lh-1 text-primary"
role="button"
@onclick:preventDefault="true"
@onclick:stopPropagation="true"
@onclick="() => showAll = false"
>
<svg
width="30"
height="30"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 10a2 2 0 11-4.001-.001A2 2 0 016 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0112 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0118 10z"
fill="var(--bs-primary)"
/>
</svg>
</div>
}
</div>
</div>
</div>

View File

@@ -1,6 +1,8 @@
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Quiz @using Management.Web.Shared.Components.Quiz
@using Management.Web.Pages.Course.Module @using Management.Web.Pages.Course.Module
@using Management.Web.Pages.Course.Module.ModuleItems
@using Management.Web.Pages.Course.Module.NewItemsButtons
@using LocalModels @using LocalModels
@using BlazorMonaco @using BlazorMonaco
@using BlazorMonaco.Editor @using BlazorMonaco.Editor
@@ -153,6 +155,10 @@
<h5>Assignments</h5> <h5>Assignments</h5>
<div class="row"> <div class="row">
@foreach(var p in Module.Pages)
{
<PageListItem Page=p />
}
@foreach (var a in Module.Assignments) @foreach (var a in Module.Assignments)
{ {
<AssignmentListItem Assignment="a" Module="Module" /> <AssignmentListItem Assignment="a" Module="Module" />

View File

@@ -0,0 +1,137 @@
@using Management.Web.Shared.Components
@using Management.Web.Course.Module.ModuleItems
@using CanvasModel.Assignments
@inject DragContainer dragContainer
@inject NavigationManager Navigation
@inject AssignmentEditorContext assignmentContext
@inherits DroppableAssignment
@code {
[Parameter]
[EditorRequired]
public LocalModule Module { get; set; } = new();
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
private bool showAll { get; set; } = false;
private void HandleDragStart()
{
dragContainer.DropCallback = DropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private CanvasAssignment? assignmentInCanvas => planner
.CanvasAssignments?
.FirstOrDefault(
a => a.Name == Assignment.Name
);
private bool existsInCanvas =>
assignmentInCanvas != null;
private void OnClick()
{
assignmentContext.Assignment = Assignment;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/assignment/" + Assignment.Name);
}
private bool NeedsToBeUpdatedInCanvas => planner.LocalCourse != null
&& planner.LocalCourse.Settings.CanvasId != null
&& planner.CanvasAssignments != null
&& planner.CanvasModules != null
&& assignmentInCanvas != null
&& Assignment.NeedsUpdates(
(CanvasAssignment)assignmentInCanvas,
Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups)
);
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
<ModuleItemLayout Name=@Assignment.Name IsSyncedWithCanvas=@(existsInCanvas && !NeedsToBeUpdatedInCanvas)>
@if(
planner.LocalCourse != null
&& existsInCanvas
&& NeedsToBeUpdatedInCanvas
&& assignmentInCanvas != null
)
{
<div class="mx-3 text-body-tertiary">
@Assignment.GetUpdateReason(
(CanvasAssignment)assignmentInCanvas,
Assignment.GetCanvasAssignmentGroupId(planner.LocalCourse.Settings.AssignmentGroups))
</div>
}
@if(!existsInCanvas)
{
<div class="mx-3 text-body-tertiary">
no assignment with same name in canvas
</div>
}
@if(!showAll)
{
<div class="card-text overflow-hidden p-2" style="max-height: 5rem;">
<div>Points: @Assignment.PointsPossible</div>
<div>Due At: @Assignment.DueAt</div>
</div>
}
else
{
<div class="card-text">
<div class="px-3 py-1 bg-dark-subtle my-1">
@((MarkupString) @Assignment.GetDescriptionHtml())
</div>
<section class="px-3">
<div>Points: @Assignment.PointsPossible</div>
<div>Due At: @Assignment.DueAt</div>
<div>Lock At: @Assignment.LockAt</div>
<div>Submission Types:</div>
<ul>
@foreach(var type in Assignment.SubmissionTypes)
{
<li>
@type
</li>
}
</ul>
</section>
</div>
}
<div
class="text-center fs-3 fw-bold lh-1 text-primary"
role="button"
@onclick:preventDefault="true"
@onclick:stopPropagation="true"
@onclick="() => showAll = !showAll"
>
<MeatballsIcon />
</div>
</ModuleItemLayout>
</div>

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Management.Web.Course.Module.Assignment; namespace Management.Web.Course.Module.ModuleItems;
public class DroppableAssignment : ComponentBase public class DroppableAssignment : ComponentBase
{ {
[Inject] [Inject]

View File

@@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Components;
namespace Management.Web.Course.Module.ModuleItems;
public class DroppablePage : ComponentBase
{
[Inject]
protected CoursePlanner planner { get; set; } = default!;
[Parameter, EditorRequired]
public LocalCoursePage Page { get; set; } = default!;
private void dropOnDate(DateTime dropDate)
{
if (planner.LocalCourse == null) return;
var currentModule = planner
.LocalCourse
.Modules
.First(m =>
m.Pages.Contains(Page)
) ?? throw new Exception("in drop page callback, could not find module");
var defaultDueTimeDate = new DateTime(
year: dropDate.Year,
month: dropDate.Month,
day: dropDate.Day,
hour: planner.LocalCourse.Settings.DefaultDueTime.Hour,
minute: planner.LocalCourse.Settings.DefaultDueTime.Minute,
second: 0
);
var moduleWithUpdatedPage = currentModule with
{
Pages = currentModule.Pages.Select(p =>
p.Name != Page.Name // we are only changing the due date, so the name should be the same
? p
: p with { DueAt = defaultDueTimeDate }
)
};
var updatedModules = planner.LocalCourse.Modules
.Select(m =>
m.Name == moduleWithUpdatedPage.Name
? moduleWithUpdatedPage
: m
);
var newCourse = planner.LocalCourse with
{
Modules = updatedModules
};
planner.LocalCourse = newCourse;
}
private void dropOnModule(LocalModule module)
{
if (planner.LocalCourse == null) return;
var newModules = planner.LocalCourse.Modules.Select(m =>
m.Name != module.Name
? m with
{
Pages = m.Pages.Where(p => p.Name != Page.Name).DistinctBy(p => p.Name)
}
: m with
{
Pages = m.Pages.Append(Page).DistinctBy(a => a.Name)
}
);
var newCourse = planner.LocalCourse with
{
Modules = newModules
};
planner.LocalCourse = newCourse;
}
protected void dropCallback(DateTime? dropDate, LocalModule? module)
{
if (module == null)
{
dropOnDate(dropDate ?? Page.DueAt);
}
else
{
dropOnModule(module);
}
}
}

View File

@@ -0,0 +1,32 @@
@using Management.Web.Shared.Components
@code {
[Parameter]
public RenderFragment ChildContent { get; set; } = default!;
[Parameter, EditorRequired]
public string Name { get; set; } = default!;
[Parameter, EditorRequired]
public bool IsSyncedWithCanvas { get; set; } = default!;
}
<div class="card">
<div class="card-body p-0">
<div class="card-title pt-2 px-2 m-0 d-flex justify-content-between">
<h4>@Name</h4>
@if(IsSyncedWithCanvas)
{
<CheckIcon />
}
else
{
<SyncIcon />
}
</div>
@ChildContent
</div>
</div>

View File

@@ -0,0 +1,48 @@
@using Management.Web.Shared.Components
@using Management.Web.Course.Module.ModuleItems
@inject DragContainer dragContainer
@inject NavigationManager Navigation
@inject PageEditorContext pageContext
@inherits DroppablePage
@code {
private void HandleDragStart()
{
dragContainer.DropCallback = dropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private bool existsInCanvas => false;
private void OnClick()
{
pageContext.Page = Page;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/page/" + Page.Name);
}
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
<ModuleItemLayout Name=@Page.Name IsSyncedWithCanvas=existsInCanvas>
@if(!existsInCanvas)
{
<div class="mx-3 text-body-tertiary">
no page with same name in canvas
</div>
}
<div class="card-text overflow-hidden p-2">
<div>Due At: @Page.DueAt</div>
</div>
</ModuleItemLayout>
</div>

View File

@@ -0,0 +1,54 @@
@using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Quiz
@inject DragContainer dragContainer
@inject QuizEditorContext quizContext
@inject NavigationManager Navigation
@inherits DroppableQuiz
@code {
private void HandleDragStart()
{
dragContainer.DropCallback = dropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private bool existsInCanvas =>
planner.CanvasQuizzes != null
? Quiz.QuizIsCreated(planner.CanvasQuizzes)
: false;
private void OnClick()
{
quizContext.Quiz = Quiz;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/quiz/" + Quiz.Name);
}
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
<ModuleItemLayout Name=Quiz.Name IsSyncedWithCanvas=existsInCanvas>
@if(!existsInCanvas)
{
<div class="mx-3 text-body-tertiary">
no quiz with same name in canvas
</div>
}
<div class="card-text overflow-hidden p-2">
<div>Due At: @Quiz.DueAt</div>
</div>
</ModuleItemLayout>
</div>

View File

@@ -1,5 +1,6 @@
@using Management.Web.Pages.Course.Module @using Management.Web.Pages.Course.Module
@using System.Linq @using System.Linq
@using Management.Web.Pages.Course.Module.NewItemsButtons
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject CoursePlanner planner @inject CoursePlanner planner

View File

@@ -26,7 +26,7 @@
{ {
Name = Name, Name = Name,
Text = "", Text = "",
DueDateForOrdering = DateTime.Now DueAt = DateTime.Now
}; };
var newModules =planner.LocalCourse.Modules.Select(m => var newModules =planner.LocalCourse.Modules.Select(m =>

View File

@@ -1,68 +0,0 @@
@using Management.Web.Shared.Components
@using Management.Web.Shared.Components.Quiz
@inject DragContainer dragContainer
@inject QuizEditorContext quizContext
@inject NavigationManager Navigation
@inherits DroppableQuiz
@code {
private void HandleDragStart()
{
dragContainer.DropCallback = dropCallback;
}
private void HandleDragEnd()
{
dragContainer.DropCallback = null;
}
private bool existsInCanvas =>
planner.CanvasQuizzes != null
? Quiz.QuizIsCreated(planner.CanvasQuizzes)
: false;
private void OnClick()
{
quizContext.Quiz = Quiz;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name + "/quiz/" + Quiz.Name);
}
}
<div
draggable="true"
@ondragstart="HandleDragStart"
@ondragend="HandleDragEnd"
@onclick="OnClick"
role="button"
>
<div class="card">
<div class="card-body p-0">
<div class="card-title pt-2 px-2 m-0 d-flex justify-content-between">
<h4>@Quiz.Name</h4>
@if(existsInCanvas)
{
<CheckIcon />
}
else
{
<SyncIcon />
}
</div>
@if(!existsInCanvas)
{
<div class="mx-3 text-body-tertiary">
no quiz with same name in canvas
</div>
}
<div class="card-text overflow-hidden p-2">
<div>Due At: @Quiz.DueAt</div>
</div>
</div>
</div>
</div>

View File

@@ -3,7 +3,7 @@
@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.Pages.Course.Module.Assignment @using Management.Web.Pages.Course.Module.ModuleItems
@using Management.Web.Shared.Components @using Management.Web.Shared.Components
@inject CanvasService canvas @inject CanvasService canvas

View File

@@ -6,7 +6,7 @@
@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.Pages.Course.Module.Assignment @using Management.Web.Pages.Course.Module.ModuleItems
@inject FileStorageManager fileStorageManager @inject FileStorageManager fileStorageManager
@inject CanvasService canvas @inject CanvasService canvas

View File

@@ -85,6 +85,7 @@ builder.Services.AddScoped<FileStorageManager>();
builder.Services.AddScoped<CoursePlanner>(); builder.Services.AddScoped<CoursePlanner>();
builder.Services.AddScoped<AssignmentEditorContext>(); builder.Services.AddScoped<AssignmentEditorContext>();
builder.Services.AddScoped<PageEditorContext>();
builder.Services.AddScoped<QuizEditorContext>(); builder.Services.AddScoped<QuizEditorContext>();
builder.Services.AddScoped<DragContainer>(); builder.Services.AddScoped<DragContainer>();

View File

@@ -0,0 +1,11 @@
<svg
width="30"
height="30"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 10a2 2 0 11-4.001-.001A2 2 0 016 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0112 10zm6 0a2 2 0 11-4.001-.001A2 2 0 0118 10z"
fill="var(--bs-primary)"
/>
</svg>

After

Width:  |  Height:  |  Size: 268 B

View File

@@ -0,0 +1,148 @@
using CanvasModel.Modules;
using LocalModels;
using Management.Planner;
using Management.Services;
using Management.Services.Canvas;
public class PageEditorContext(
CoursePlanner planner,
CanvasService canvas,
MyLogger<PageEditorContext> logger)
{
public event Action? StateHasChanged;
private CoursePlanner planner { get; } = planner;
private CanvasService canvas { get; } = canvas;
private readonly MyLogger<PageEditorContext> logger = logger;
private LocalCoursePage? _page;
private LocalModule? _module;
public LocalCoursePage? Page
{
get => _page;
set
{
if (_page == null && value != null && planner != null && planner.LocalCourse != null)
{
_module = getCurrentLocalModule(value, planner.LocalCourse);
}
_page = value;
StateHasChanged?.Invoke();
}
}
public void SavePage(LocalCoursePage newPage)
{
if (planner.LocalCourse != null && _module != null && Page != null)
{
// use Page not newPage because it is the version that was last stored
var updatedModules = planner.LocalCourse.Modules
.Select(
m =>
m.Name == _module.Name
? m with
{
Pages = m.Pages
.Select(p => p == Page ? newPage : p)
.ToArray()
}
: m
)
.ToArray();
planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules };
Page = newPage;
}
}
public void DeletePage()
{
if (planner.LocalCourse != null && Page != null && _module != null)
{
// not dealing with canvas rn
var updatedModules = planner.LocalCourse.Modules
.Select(m => m.Name != _module.Name
? m
: m with
{
Pages = m.Pages.Where(p => p == Page).ToArray()
}
)
.ToArray();
planner.LocalCourse = planner.LocalCourse with { Modules = updatedModules };
Page = null;
}
}
public async Task AddPageToCanvas()
{
// logger.Log("started to add quiz to canvas");
// if (Quiz == null)
// {
// logger.Log("cannot add null quiz to canvas");
// return;
// }
// await planner.LoadCanvasData();
// if (planner.CanvasQuizzes == null)
// {
// logger.Log("cannot add quiz to canvas, failed to retrieve current quizzes");
// return;
// }
// if (planner.LocalCourse == null)
// {
// logger.Log("cannot add quiz to canvas, no course stored in planner");
// return;
// }
// var canvasQuizId = await planner.LocalCourse.AddQuizToCanvas(Quiz, canvas);
// var courseCanvasId = planner.LocalCourse.Settings.CanvasId;
// if (courseCanvasId == null)
// {
// logger.Log("was able to add quiz to canvas, but errored while making module item. CourseCanvasId is null");
// return;
// }
// var canvasModule = getCurrentCanvasModule(Quiz, planner.LocalCourse);
// await canvas.CreateModuleItem(
// (ulong)courseCanvasId,
// canvasModule.Id,
// Quiz.Name,
// "Quiz",
// (ulong)canvasQuizId
// );
// await planner.LocalCourse.Modules.First().SortModuleItems(
// (ulong)courseCanvasId,
// canvasModule.Id,
// canvas
// );
// logger.Log($"finished adding quiz {Quiz.Name} to canvas");
}
private static LocalModule getCurrentLocalModule(LocalCoursePage page, LocalCourse course)
{
return course.Modules.FirstOrDefault(
m => m.Pages.Contains(page)
)
?? throw new Exception("could not find current module in page editor context");
}
private CanvasModule getCurrentCanvasModule(LocalCoursePage quiz, LocalCourse course)
{
var localModule = getCurrentLocalModule(quiz, course);
var canvasModule = planner.CanvasModules?.FirstOrDefault(m => m.Name == localModule.Name)
?? throw new Exception($"error in page context, canvas module with name {localModule.Name} not found in planner");
return canvasModule;
}
}

View File

@@ -5,26 +5,20 @@ using Management.Planner;
using Management.Services; using Management.Services;
using Management.Services.Canvas; using Management.Services.Canvas;
public class QuizEditorContext public class QuizEditorContext(
CoursePlanner planner,
CanvasService canvas,
MyLogger<QuizEditorContext> logger)
{ {
public QuizEditorContext(
CoursePlanner planner,
CanvasService canvas,
MyLogger<CanvasAssignmentService> logger)
{
this.planner = planner;
this.canvas = canvas;
this.logger = logger;
}
public event Action? StateHasChanged; public event Action? StateHasChanged;
private CoursePlanner planner { get; } private CoursePlanner planner { get; } = planner;
private CanvasService canvas { get; } private CanvasService canvas { get; } = canvas;
private readonly MyLogger<QuizEditorContext> logger = logger;
private LocalQuiz? _quiz; private LocalQuiz? _quiz;
private LocalModule? _module; private LocalModule? _module;
private readonly MyLogger<CanvasAssignmentService> logger;
public LocalQuiz? Quiz public LocalQuiz? Quiz
{ {

View File

@@ -6,7 +6,7 @@ using YamlDotNet.Serialization;
namespace LocalModels; namespace LocalModels;
public record LocalAssignment public record LocalAssignment: IModuleItem
{ {
private string _name = ""; private string _name = "";
public string Name public string Name
@@ -28,6 +28,7 @@ public record LocalAssignment
public IEnumerable<RubricItem> Rubric { get; init; } = Array.Empty<RubricItem>(); public IEnumerable<RubricItem> Rubric { get; init; } = Array.Empty<RubricItem>();
public int PointsPossible => Rubric.Sum(r => r.IsExtraCredit ? 0 : r.Points); public int PointsPossible => Rubric.Sum(r => r.IsExtraCredit ? 0 : r.Points);
public string GetDescriptionHtml() public string GetDescriptionHtml()
{ {
return Markdig.Markdown.ToHtml(Description); return Markdig.Markdown.ToHtml(Description);

View File

@@ -0,0 +1,9 @@
namespace LocalModels;
public interface IModuleItem
{
public string Name { get; init; }
public DateTime DueAt { get; init; }
}

View File

@@ -1,14 +1,14 @@
namespace LocalModels; namespace LocalModels;
public record LocalCoursePage public record LocalCoursePage: IModuleItem
{ {
public required string Name { get; init; } public required string Name { get; init; }
public required string Text { get; set; } public required string Text { get; set; }
public DateTime? DueDateForOrdering { get; init; } public DateTime DueAt { get; init; }
public string ToMarkdown() public string ToMarkdown()
{ {
var printableDueDate = DueDateForOrdering.ToString()?.Replace('\u202F', ' '); var printableDueDate = DueAt.ToString()?.Replace('\u202F', ' ');
var settingsMarkdown = $"Name: {Name}\n" var settingsMarkdown = $"Name: {Name}\n"
+ $"DueDateForOrdering: {printableDueDate}\n" + $"DueDateForOrdering: {printableDueDate}\n"
+ "---\n"; + "---\n";
@@ -20,9 +20,9 @@ public record LocalCoursePage
var name = MarkdownUtils.ExtractLabelValue(rawSettings, "Name"); var name = MarkdownUtils.ExtractLabelValue(rawSettings, "Name");
var rawDate = MarkdownUtils.ExtractLabelValue(rawSettings, "DueDateForOrdering"); var rawDate = MarkdownUtils.ExtractLabelValue(rawSettings, "DueDateForOrdering");
DateTime? parsedDate = DateTime.TryParse(rawDate, out DateTime parsedDueAt) DateTime parsedDate = DateTime.TryParse(rawDate, out DateTime parsedDueAt)
? parsedDueAt ? parsedDueAt
: null; : throw new LocalPageMarkdownParseException($"could not parse due date: {rawDate}");
var text = pageMarkdown.Split("---\n")[1]; var text = pageMarkdown.Split("---\n")[1];
@@ -30,7 +30,7 @@ public record LocalCoursePage
return new LocalCoursePage return new LocalCoursePage
{ {
Name = name, Name = name,
DueDateForOrdering = parsedDate, DueAt = parsedDate,
Text = text Text = text
}; };
} }

View File

@@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
namespace LocalModels; namespace LocalModels;
public record LocalQuiz public record LocalQuiz: IModuleItem
{ {
public required string Name { get; init; } public required string Name { get; init; }
public required string Description { get; init; } public required string Description { get; init; }
@@ -27,7 +27,7 @@ public record LocalQuiz
.CanvasId; .CanvasId;
public string GetDescriptionHtml() => Markdig.Markdown.ToHtml(Description); public string GetDescriptionHtml() => Markdig.Markdown.ToHtml(Description);
public string ToYaml() public string ToYaml()
{ {
var serializer = new SerializerBuilder().DisableAliases().Build(); var serializer = new SerializerBuilder().DisableAliases().Build();
@@ -100,7 +100,7 @@ Description: {Description}
var rawLockAt = extractLabelValue(settings, "LockAt"); var rawLockAt = extractLabelValue(settings, "LockAt");
DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt) DateTime? lockAt = DateTime.TryParse(rawLockAt, out DateTime parsedLockAt)
? parsedLockAt ? parsedLockAt
: null; : null;
@@ -153,6 +153,6 @@ public class QuizMarkdownParseException : Exception
{ {
public QuizMarkdownParseException(string message): base(message) public QuizMarkdownParseException(string message): base(message)
{ {
} }
} }

View File

@@ -131,6 +131,7 @@ public class CourseMarkdownLoader
} }
private async Task<IEnumerable<LocalCoursePage>> loadPagesFromPath(string modulePath) private async Task<IEnumerable<LocalCoursePage>> loadPagesFromPath(string modulePath)
{ {
using var activity = DiagnosticsConfig.Source?.StartActivity("loading Pages from file");
var pagesPath = $"{modulePath}/pages"; var pagesPath = $"{modulePath}/pages";
if (!Directory.Exists(pagesPath)) if (!Directory.Exists(pagesPath))
{ {