added monaco editor to assignment page

This commit is contained in:
2023-11-15 11:23:36 -07:00
parent a0931f5003
commit 8d83e0ecd4
13 changed files with 201 additions and 128 deletions

33
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Management.Web/bin/Debug/net8.0/Management.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/Management.Web",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Management.Web/Management.Web.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Management.Web/Management.Web.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/Management.Web/Management.Web.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -56,7 +56,7 @@
} }
} }
<div style="height: 100vh;" class="m-0 p-1">
@if (loading) @if (loading)
{ {
<Spinner /> <Spinner />
@@ -66,3 +66,4 @@
{ {
<AssignmentForm /> <AssignmentForm />
} }
</div>

View File

@@ -18,15 +18,17 @@
<link href="css/site.css" rel="stylesheet" /> <link href="css/site.css" rel="stylesheet" />
<link href="Management.Web.styles.css" rel="stylesheet" /> <link href="Management.Web.styles.css" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link <link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Kanit&family=Mukta&family=Roboto&family=Sofia+Sans+Condensed:wght@400;500&display=swap" href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Kanit&family=Mukta&family=Roboto&family=Sofia+Sans+Condensed:wght@400;500&display=swap"
rel="stylesheet"> rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,600&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="favicon.png" /> <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="favicon.png" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head> </head>

View File

@@ -42,9 +42,8 @@
.AssignmentTemplates .AssignmentTemplates
.FirstOrDefault(t => t.Id == TemplateId); .FirstOrDefault(t => t.Id == TemplateId);
private void handleNewDescription(ChangeEventArgs e) private void handleNewDescription(string newDescription)
{ {
var newDescription = e.Value?.ToString();
if (newDescription != string.Empty) if (newDescription != string.Empty)
{ {
descriptionForPreview = newDescription; descriptionForPreview = newDescription;
@@ -65,25 +64,17 @@
@if(assignmentContext.Assignment != null && planner.LocalCourse != null) @if(assignmentContext.Assignment != null && planner.LocalCourse != null)
{ {
<div class="row"> <div class="row h-100">
<div class="col">
<label for="description" class="form-label">
Description
</label>
</div>
<div class="col">
HTML Preview
</div>
</div>
<div class="row">
<div class="col-6"> <div class="col-6">
<textarea @* <textarea
id="description" id="description"
class="form-control h-100" class="form-control h-100"
rows=12 rows=12
@bind="description" @bind="description"
@oninput="handleNewDescription" @oninput="handleNewDescription"
/> /> *@
<MonacoTextArea Value="@description" OnChange="@handleNewDescription" />
</div> </div>
<div class="col-6" @key="descriptionForPreview"> <div class="col-6" @key="descriptionForPreview">
@(preview) @(preview)

View File

@@ -159,7 +159,7 @@
planner.CanvasAssignments?.FirstOrDefault(a => a.Name == assignmentContext.Assignment?.Name); planner.CanvasAssignments?.FirstOrDefault(a => a.Name == assignmentContext.Assignment?.Name);
private string canvasAssignmentUrl => private string canvasAssignmentUrl =>
$"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/assignments/{assignmentInCanvas?.Id}"; $"https://snow.instructure.com/courses/{planner.LocalCourse?.Settings.CanvasId}/assignments/{assignmentInCanvas?.Id}";
private async Task deleteFromCanvas() private async Task deleteFromCanvas()
{ {
@@ -173,103 +173,105 @@
await canvas.Assignments.Delete( await canvas.Assignments.Delete(
(ulong)planner.LocalCourse.Settings.CanvasId, (ulong)planner.LocalCourse.Settings.CanvasId,
assignmentInCanvas.Id, assignmentInCanvas.Id,
assignmentContext.Assignment.Name); assignmentContext.Assignment.Name
);
await planner.LoadCanvasData(); await planner.LoadCanvasData();
deletingAssignmentFromCanvas = false; deletingAssignmentFromCanvas = false;
StateHasChanged(); StateHasChanged();
} }
} }
@assignmentContext.Assignment?.Name
@if (assignmentContext.Assignment != null)
{ <div class="d-flex flex-column h-100">
<div class="m-1"> <div>
<label class="form-label"> @assignmentContext.Assignment?.Name
Name </div>
</label> <div class="flex-grow-1 d-flex flex-column">
<input class="form-control" @bind="name" @oninput="handleNameChange" /> @if (assignmentContext.Assignment != null)
</div> {
<ButtonSelect <div class="m-1">
Label="Assignment Group" <label class="form-label">
Options="planner.LocalCourse?.Settings.AssignmentGroups" Name
GetName="(g) => g?.Name" </label>
OnSelect="(g) => setAssignmentGroup(g)" <input class="form-control" @bind="name" @oninput="handleNameChange" />
SelectedOption="selectedAssignmentGroup" </div>
/>
<div class="m-1"> <ButtonSelect
<AssignmentDescriptionEditor /> Label="Assignment Group"
Options="planner.LocalCourse?.Settings.AssignmentGroups"
GetName="(g) => g?.Name"
OnSelect="(g) => setAssignmentGroup(g)"
SelectedOption="selectedAssignmentGroup"
/>
<div class="m-1 flex-grow-1">
<AssignmentDescriptionEditor />
</div>
<div class="form-check m-1">
<input class="form-check-input" id="lockAtDueDate" type="checkbox" @bind="lockAtDueDate"
@oninput="handleLockAtDueDateChange" />
<label class="form-check-label" for="lockAtDueDate">
Lock At Due Date
</label>
</div>
<div class="container">
<RubricMarkdownEditor />
<hr>
<div class="mx-5 px-5">
<SubmissionTypeSelector />
</div>
</div>
}
</div> </div>
<div class="form-check m-1"> <div class="d-flex justify-content-end p-3">
<input class="form-check-input" id="lockAtDueDate" type="checkbox" @bind="lockAtDueDate"
@oninput="handleLockAtDueDateChange" />
<label class="form-check-label" for="lockAtDueDate">
Lock At Due Date
</label>
</div>
<div class="container">
<RubricMarkdownEditor />
<hr>
<div class="mx-5 px-5">
<SubmissionTypeSelector />
</div>
</div>
}
<div class="d-flex justify-content-end m-3"> <ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirmAsync="HandleDelete" />
<ConfirmationModal Label="Delete" Class="btn btn-danger" OnConfirmAsync="HandleDelete" />
<button
class="btn btn-outline-secondary mx-3"
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
@onclick="addToCanvas"
>
Add To Canvas
</button>
@if (assignmentInCanvas != null)
{
<a
class="btn btn-outline-secondary me-1"
href="@canvasAssignmentUrl"
target="_blank"
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
>
View in Canvas
</a>
<button <button
class="btn btn-outline-secondary mx-3" class="btn btn-outline-secondary mx-3"
disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)" disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
@onclick="updateInCanvas" @onclick="addToCanvas"
> >
Update In Canvas Add To Canvas
</button> </button>
<ConfirmationModal @if (assignmentInCanvas != null)
Disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)" {
Label="Delete from Canvas" <a
Class="btn btn-outline-danger mx-3" class="btn btn-outline-secondary me-1"
OnConfirmAsync="deleteFromCanvas" href="@canvasAssignmentUrl"
/> target="_blank"
} disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
<button class="btn btn-primary mx-2" @onclick="@(() => { >
assignmentContext.Assignment = null; View in Canvas
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name); </a>
})"> <button
Done class="btn btn-outline-secondary mx-3"
</button> disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
</div> @onclick="updateInCanvas"
>
Update In Canvas
</button>
<ConfirmationModal
Disabled="@(addingAssignmentToCanvas || deletingAssignmentFromCanvas)"
Label="Delete from Canvas"
Class="btn btn-outline-danger mx-3"
OnConfirmAsync="deleteFromCanvas"
/>
}
<button class="btn btn-primary mx-2" @onclick="@(() => {
assignmentContext.Assignment = null;
Navigation.NavigateTo("/course/" + planner.LocalCourse?.Settings.Name);
})">
Done
</button>
</div>
@if (addingAssignmentToCanvas || deletingAssignmentFromCanvas) @if (addingAssignmentToCanvas || deletingAssignmentFromCanvas)
{ {
<Spinner /> <div>
} <Spinner />
<br> </div>
<br> }
<br> </div>
<br>
<br>
<br>
<br>
<br>
<br>

View File

@@ -5,7 +5,7 @@
@code @code
{ {
private IEnumerable<RubricItem> displayRubric { get; set; } = new RubricItem[] {}; private IEnumerable<RubricItem> displayRubric { get; set; } = new RubricItem[] { };
private string _rubricText = ""; private string _rubricText = "";
private string rubricText private string rubricText
{ {
@@ -22,9 +22,9 @@
if (assignmentContext.Assignment != null) if (assignmentContext.Assignment != null)
{ {
var newAssignment = assignmentContext.Assignment with var newAssignment = assignmentContext.Assignment with
{ {
Rubric = parsedRubric, Rubric = parsedRubric,
}; };
assignmentContext.SaveAssignment(newAssignment); assignmentContext.SaveAssignment(newAssignment);
} }
} }
@@ -49,7 +49,7 @@
{ {
if (assignmentContext.Assignment != null) if (assignmentContext.Assignment != null)
{ {
if(rubricText == string.Empty) if (rubricText == string.Empty)
{ {
rubricText = assignmentContext.Assignment.RubricToMarkdown(); rubricText = assignmentContext.Assignment.RubricToMarkdown();
} }
@@ -68,18 +68,13 @@
<br> <br>
<div class="row"> <div class="row">
<h4 class="text-center">Rubric</h4> <h4 class="text-center">Rubric</h4>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<textarea @* <textarea id="description" class="form-control h-100" rows=12 @bind="rubricText" @bind:event="oninput" /> *@
id="description" <MonacoTextArea Value="@rubricText" OnChange="@((t) => rubricText = t)" />
class="form-control h-100"
rows=12
@bind="rubricText"
@bind:event="oninput"
/>
</div> </div>
<div class="col-6"> <div class="col-6">
@if (error != null) @if (error != null)
@@ -92,7 +87,7 @@
<div class="col-3 text-center">Points</div> <div class="col-3 text-center">Points</div>
<div class="col-3 text-center">Extra Credit</div> <div class="col-3 text-center">Extra Credit</div>
</div> </div>
@foreach(var item in displayRubric) @foreach (var item in displayRubric)
{ {
<div class="row border-bottom"> <div class="row border-bottom">
<div class="col-6 text-end">@item.Label</div> <div class="col-6 text-end">@item.Label</div>

View File

@@ -30,7 +30,7 @@
} }
} }
<div> <div key="@GetName(SelectedOption)">
@foreach(var option in Options) @foreach(var option in Options)
{ {
<button <button

View File

@@ -9,6 +9,8 @@
[Parameter, EditorRequired] [Parameter, EditorRequired]
public Action<string> OnChange { get; set; } public Action<string> OnChange { get; set; }
private string randomId = "monaco-editor-" + BitConverter.ToString(new byte[16].Select(b => (byte)new Random().Next(256)).ToArray()).Replace("-", "");
private StandaloneCodeEditor _editor = null!; private StandaloneCodeEditor _editor = null!;
@@ -25,8 +27,11 @@
LineDecorationsWidth = 0, LineDecorationsWidth = 0,
WordWrap = "on", WordWrap = "on",
AutomaticLayout = true, AutomaticLayout = true,
FontFamily = "DM Mono, monospace", FontFamily = "Roboto-mono",
FontSize = 16, FontSize = 16,
Padding = new EditorPaddingOptions() {
Top = 10
}
}; };
} }
@@ -39,7 +44,7 @@
<StandaloneCodeEditor <StandaloneCodeEditor
@ref="_editor" @ref="_editor"
Id="sample-code-editor-123" Id="@randomId"
ConstructionOptions="EditorConstructionOptions" ConstructionOptions="EditorConstructionOptions"
OnDidChangeModelContent="OnDidChangeModelContent" OnDidChangeModelContent="OnDidChangeModelContent"
/> />

View File

@@ -26,6 +26,8 @@ public record LocalQuiz
.FirstOrDefault(g => g.Name == LocalAssignmentGroupName)? .FirstOrDefault(g => g.Name == LocalAssignmentGroupName)?
.CanvasId; .CanvasId;
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();

View File

@@ -26,6 +26,7 @@ public record LocalQuizQuestion
: $"[{correctIndicator}] "; : $"[{correctIndicator}] ";
// var textWithSpecificNewline = answer.Text.Replace(Environment.NewLine, Environment.NewLine + " "); // var textWithSpecificNewline = answer.Text.Replace(Environment.NewLine, Environment.NewLine + " ");
var multilineMarkdownCompatibleText = answer.Text.StartsWith("```") var multilineMarkdownCompatibleText = answer.Text.StartsWith("```")
? Environment.NewLine + answer.Text ? Environment.NewLine + answer.Text
: answer.Text; : answer.Text;

View File

@@ -19,7 +19,7 @@ public record LocalQuizQuestionAnswer
return new LocalQuizQuestionAnswer() return new LocalQuizQuestionAnswer()
{ {
Correct = isCorrect, Correct = isCorrect,
Text=text, Text = text,
}; };
} }
} }

View File

@@ -41,7 +41,7 @@ public class CanvasQuizService(
quiz = new quiz = new
{ {
title = localQuiz.Name, title = localQuiz.Name,
description = localQuiz.Description, description = localQuiz.GetDescriptionHtml(),
// 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,