Skip to content

Commit

Permalink
Merge pull request #396 from fujaba/feat/edit-assignment-diff
Browse files Browse the repository at this point in the history
View Diff after editing Assignment
  • Loading branch information
Clashsoft authored Dec 13, 2023
2 parents f8a32eb + 4986d76 commit 8c8ce2e
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 29 deletions.
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"codemirror": "^5.65.16",
"diff": "^5.1.0",
"file-saver": "^2.0.5",
"highlight.js": "^11.9.0",
"keycloak-angular": "^15.0.0",
Expand All @@ -57,6 +58,7 @@
"@angular/compiler-cli": "^17.0.4",
"@angular/language-service": "^17.0.4",
"@types/codemirror": "^5.60.15",
"@types/diff": "^5.0.9",
"@types/file-saver": "^2.0.7",
"@types/jasmine": "~4.3.6",
"@types/jasminewd2": "~2.0.13",
Expand Down
15 changes: 15 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<ngbx-modal [back]="['..']" size="lg" #modal>
<ng-container modal-title>
Differences
</ng-container>
<ng-container modal-body>
<h5>Metadata</h5>
<pre [innerHTML]="diffMetadata | safeHtml"></pre>
<h5>Tasks</h5>
<pre [innerHTML]="diffTasks | safeHtml"></pre>
</ng-container>
<ng-container modal-footer>
<button type="button" class="btn btn-secondary" (click)="modal.close()">
Close
</button>
</ng-container>
</ngbx-modal>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {Component, OnInit} from '@angular/core';
import {AssignmentService} from "../../../services/assignment.service";
import * as jsdiff from "diff";
import {ActivatedRoute} from "@angular/router";
import {AssignmentContext} from "../../../services/assignment.context";
import {switchMap} from "rxjs/operators";
import {TaskMarkdownService} from "../task-markdown.service";

@Component({
selector: 'app-diff-modal',
templateUrl: './diff-modal.component.html',
styleUrl: './diff-modal.component.scss'
})
export class DiffModalComponent implements OnInit {
diffMetadata = '';
diffTasks = '';

constructor(
private route: ActivatedRoute,
private context: AssignmentContext,
private assignmentService: AssignmentService,
private taskMarkdownService: TaskMarkdownService,
) {
}

ngOnInit() {
this.route.params.pipe(
switchMap(({aid}) => this.assignmentService.get(aid)),
).subscribe(oldAssignment => {
const {tasks: oldTasks, ...old} = oldAssignment;
const {tasks: newTasks, ...current} = this.context.assignment;
this.diffMetadata = renderDiff(jsdiff.diffJson(old, current));
const oldTasksMd = this.taskMarkdownService.renderTasks(oldTasks);
const newTasksMd = this.taskMarkdownService.renderTasks(newTasks);
this.diffTasks = renderDiff(jsdiff.diffWords(oldTasksMd, newTasksMd));
});
}
}

function escapeHtml(text: string) {
return text.replace(/[&<>"'`=\/]/g, function (s) {
return `&#${s.charCodeAt(0)};`;
});
}

function renderDiff(diff: jsdiff.Change[]) {
return diff.map(part => {
const color = part.added ? 'text-success' : part.removed ? 'text-danger' : '';
return `<span class="${color}">${escapeHtml(part.value)}</span>`;
}).join('');
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {PreviewComponent} from './preview/preview.component';
import {TasksComponent} from './tasks/tasks.component';
import {PlagiarismDetectionComponent} from "./plagiarism-detection/plagiarism-detection.component";
import {CodeSearchComponent} from "./code-search/code-search.component";
import {DiffModalComponent} from "./diff-modal/diff-modal.component";

export const editAssignmentChildRoutes: Routes = [
{path: 'info', component: InfoComponent, data: {title: 'Info'}},
Expand All @@ -32,6 +33,7 @@ const routes: Routes = [
data: {title: 'Edit Assignment'},
children: [
...editAssignmentChildRoutes,
{path: 'diff', component: DiffModalComponent, data: {title: 'Differences'}},
{path: '', redirectTo: 'info', pathMatch: 'full'},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {InfoComponent} from './info/info.component';
import {PreviewComponent} from './preview/preview.component';
import {TasksComponent} from './tasks/tasks.component';
import {TaskMarkdownService} from "./task-markdown.service";
import { CodeSearchComponent } from './code-search/code-search.component';
import { PlagiarismDetectionComponent } from './plagiarism-detection/plagiarism-detection.component';
import {CodeSearchComponent} from './code-search/code-search.component';
import {PlagiarismDetectionComponent} from './plagiarism-detection/plagiarism-detection.component';
import {DiffModalComponent} from "./diff-modal/diff-modal.component";


@NgModule({
Expand All @@ -31,6 +32,7 @@ import { PlagiarismDetectionComponent } from './plagiarism-detection/plagiarism-
TasksComponent,
CodeSearchComponent,
PlagiarismDetectionComponent,
DiffModalComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ <h1>
<a class="btn btn-secondary ms-2" routerLink="/assignments">
Cancel
</a>
@if (context.assignment['_id']) {
<a class="btn btn-info ms-2" routerLink="diff">
View Diff
</a>
}
@if (draft) {
<button class="btn btn-warning ms-2" (click)="deleteDraft()">
Delete Draft
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class EditAssignmentComponent implements OnInit {
}

saveDraft(): void {
this.assignmentService.saveDraft('_id' in this.context.assignment ? this.context.assignment._id : undefined, this.getAssignment());
this.assignmentService.saveDraft('_id' in this.context.assignment ? this.context.assignment._id : undefined, this.context.getAssignment());
this.draft = true;
}

Expand All @@ -76,13 +76,13 @@ export class EditAssignmentComponent implements OnInit {
}

onExport(): void {
const {_id, token, createdBy, ...rest} = this.getAssignment() as any;
const {_id, token, createdBy, ...rest} = this.context.getAssignment() as any;
this.assignmentService.download(rest);
}

submit(): void {
this.submitting = true;
const dto = this.getAssignment();
const dto = this.context.getAssignment();
const _id = '_id' in dto ? dto._id : undefined;
const operation = _id ? this.assignmentService.update(_id, {
...dto,
Expand All @@ -99,20 +99,6 @@ export class EditAssignmentComponent implements OnInit {
});
}

private getAssignment(): Assignment | CreateAssignmentDto {
return {
...this.context.assignment,
tasks: this.getTasks(this.context.assignment.tasks),
};
}

private getTasks(tasks: Task[]): Task[] {
return tasks.filter(t => !t.deleted).map(({deleted, collapsed, children, ...rest}) => ({
...rest,
children: this.getTasks(children),
}));
}

deleteDraft() {
if (!('_id' in this.context.assignment) || !confirm('Are you sure you want to delete this draft? This action cannot be undone.')) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
<div class="card mb-3">
<div class="card-body pb-0">
<app-assignment-info [assignment]="assignment"></app-assignment-info>
<app-task-list [allTasks]="tasks" [tasks]="tasks"></app-task-list>
<app-task-list [allTasks]="assignment.tasks" [tasks]="assignment.tasks"></app-task-list>
</div>
</div>

@if (!$any(assignment)._id) {
<p class="text-center">
Click "Submit" to finalize your assignment and generate a link.
</p>
}
@if (!loggedIn) {
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import {Component, OnInit} from '@angular/core';
import {KeycloakService} from 'keycloak-angular';
import {CreateAssignmentDto} from '../../../model/assignment';
import Task from '../../../model/task';
import {AssignmentContext} from '../../../services/assignment.context';


@Component({
selector: 'app-edit-assignment-preview',
templateUrl: './preview.component.html',
styleUrls: ['./preview.component.scss'],
})
export class PreviewComponent implements OnInit {
assignment: CreateAssignmentDto;
tasks: Task[];

loggedIn = false;

constructor(
private keycloakService: KeycloakService,
context: AssignmentContext,
) {
this.assignment = context.assignment;
this.tasks = this.assignment.tasks.filter(t => !t.deleted);
this.assignment = context.getAssignment();
}

ngOnInit(): void {
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/app/assignment/services/assignment.context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import {Injectable} from '@angular/core';
import Assignment, {CreateAssignmentDto} from '../model/assignment';
import Task from "../model/task";

@Injectable()
export class AssignmentContext {
assignment: Assignment | CreateAssignmentDto;
saveDraft: () => void;

getAssignment(): Assignment | CreateAssignmentDto {
return {
...this.assignment,
tasks: this.getTasks(this.assignment.tasks),
};
}

private getTasks(tasks: Task[]): Task[] {
return tasks.filter(t => !t.deleted).map(({deleted, collapsed, children, ...rest}) => ({
...rest,
children: this.getTasks(children),
}));
}
}

0 comments on commit 8c8ce2e

Please sign in to comment.