Skip to content

Commit

Permalink
Enable viewing of code in history page
Browse files Browse the repository at this point in the history
Similar to questions page, there is a sliding window to view the last
snapshot of the coding session before forfeit or submit.

History table is now searchable.

Profile page now properly subscribes to user$ and does not need to
refresh.

Known bugs: during a colab session, cannot view history page. No idea
how to debug since it did not show errors or any console.log()
  • Loading branch information
LimZiJia committed Nov 12, 2024
1 parent 286ec54 commit 34ec12f
Show file tree
Hide file tree
Showing 20 changed files with 1,569 additions and 1,616 deletions.
17 changes: 13 additions & 4 deletions frontend/src/_services/collab.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,27 @@ export class CollabService extends ApiService {
/**
* Allows a user to close a room (change room_status to false) and delete the associated Yjs document.
*/
closeRoom(roomId: string) {
return this.http.patch<CloseRoomResponse>(this.apiUrl + '/' + roomId + '/close', {}, this.httpOptions);
closeRoom(roomId: string, language: string, code: string) {
return this.http.patch<CloseRoomResponse>(this.apiUrl + '/' + roomId + '/close',

Check failure on line 51 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `this.apiUrl·+·'/'·+·roomId·+·'/close',·` with `⏎············this.apiUrl·+·'/'·+·roomId·+·'/close',⏎············{`
{ snapshot: {

Check failure on line 52 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `············{` with `···············`
language: language,

Check failure on line 53 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `················language:·language,·` with `····················language:·language,`
code :code

Check failure on line 54 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `code·:code` with `····code:·code,`
}},

Check failure on line 55 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `············}` with `················},⏎············`
this.httpOptions);

Check failure on line 56 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Insert `,⏎········`
}

/**
* updates the isForfeit status of a specified user in a particular room. Each user in a room has a
* isForfeit field that tracks whether the user has left the room through forfeiting or is still active.
*/
forfeit(roomId: string) {
forfeit(roomId: string, language: string, code: string) {
return this.http.patch<RoomResponse>(
this.apiUrl + '/' + roomId + '/user/isForfeit',
{ isForfeit: true },
{ isForfeit: true,

Check failure on line 66 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `·isForfeit:·true,·⏎` with `⏎················isForfeit:·true,⏎··`
snapshot: {
language: language,

Check failure on line 68 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `language:·language,·` with `····language:·language,`
code :code

Check failure on line 69 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Replace `················code·:code·` with `····················code:·code,⏎················},`
}},

Check failure on line 70 in frontend/src/_services/collab.service.ts

View workflow job for this annotation

GitHub Actions / build-service (frontend)

Delete `}`
this.httpOptions,
);
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/_services/history.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class HistoryService extends ApiService {
difficulty: item.question.difficulty,
status: item.status,
time: item.createdAt,
language: item.snapshot.language,
code: item.snapshot.code,
})),
),
);
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/app/account/history/history.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.sliding-panel {
position: fixed;
top: 0;
right: -600px; /* Adjust the width as needed */
width: 600px;
height: 100%;
background-color: #181818 !important;
color: var(--text-color); /* Use theme variable */
box-shadow: -2px 0 5px rgba(0,0,0,0.5);
transition: right 0.3s ease;
z-index: 1000;
}

.sliding-panel.open {
right: 0;
}

.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: #181818 !important;
border-bottom: 1px solid #000000; /* Use theme variable */
}

.panel-content {
padding: 1rem;
line-height: 1.6; /* Adjust line height for better readability */
color: #ffffff; /* Ensure text color is readable */
}

.panel-content p {
margin-bottom: 1rem; /* Add margin to paragraphs for spacing */
}

tr:hover {
background-color: rgba(0, 0, 0, 0.1);
}
34 changes: 32 additions & 2 deletions frontend/src/app/account/history/history.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div class="table-container">
<p-table
#dt
sortField="time"
[sortOrder]="1"
[value]="histories"
Expand All @@ -8,12 +9,27 @@
[paginator]="true"
[rows]="10"
[rowsPerPageOptions]="[10, 25, 50]"
[globalFilterFields]="['question', 'difficulty', 'topics', 'collaborator', 'status', 'time']"
styleClass="p-datatable-gridlines-striped">
<ng-template pTemplate="caption">
<div class="flex">
<h3 class="m-0">Matching History</h3>
</div>
</ng-template>
<ng-template pTemplate="caption">
<div class="flex">
<p-iconField iconPosition="left" class="ml-auto">
<p-inputIcon>
<i class="pi pi-search"></i>
</p-inputIcon>
<input
pInputText
type="text"
(input)="dt.filterGlobal($any($event.target).value, 'contains')"
placeholder="Search keyword" />
</p-iconField>
</div>
</ng-template>
<ng-template pTemplate="header" let-columns>
<tr>
<th pSortableColumn="question" style="width: 20%">
Expand All @@ -31,7 +47,7 @@ <h3 class="m-0">Matching History</h3>
</tr>
</ng-template>
<ng-template pTemplate="body" let-history>
<tr>
<tr (click)="onRowSelect(history)">
<td>{{ history.question }}</td>
<td>{{ history.difficulty }}</td>
<td>{{ history.topics.join(', ') }}</td>
Expand All @@ -45,8 +61,22 @@ <h3 class="m-0">Matching History</h3>
<i class="pi pi-spin pi-spinner" style="color: white; font-size: large"></i>
}
</td>
<td>{{ history.time | date: 'short' }}</td>
<td>{{ history.time }}</td>
</tr>
</ng-template>
</p-table>
</div>
<div class="sliding-panel" [class.open]="isPanelVisible">
<div class="panel-header">
<h4>{{ history?.question }}</h4>
<p-button
icon="pi pi-times"
severity="secondary"
label="Close"
(onClick)="closePanel()"
class="p-button-text" />
</div>
<div class="panel-content">
<div #editor class="editor-content"></div>
</div>
</div>
66 changes: 61 additions & 5 deletions frontend/src/app/account/history/history.component.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { TableModule } from 'primeng/table';
import { CommonModule } from '@angular/common';
import { CommonModule, DatePipe } from '@angular/common';
import { MatchingHistory } from './history.model';
import { HistoryService } from '../../../_services/history.service';
import { MessageService } from 'primeng/api';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from 'primeng/button';
import { IconFieldModule } from 'primeng/iconfield';
import { InputIconModule } from 'primeng/inputicon';
import { oneDark } from '@codemirror/theme-one-dark';
import { EditorState, Extension, StateEffect } from '@codemirror/state';
import { EditorView, basicSetup } from 'codemirror';
import { keymap } from '@codemirror/view';
import { indentWithTab } from '@codemirror/commands';
import { languageMap, parserMap, LanguageOption } from '../../collaboration/editor/languages';

@Component({
standalone: true,
imports: [TableModule, CommonModule],
providers: [MessageService],
imports: [TableModule, CommonModule, InputTextModule, ButtonModule, IconFieldModule, InputIconModule, ],
providers: [MessageService, DatePipe],
templateUrl: './history.component.html',
styleUrl: './history.component.css',
})
export class HistoryComponent implements OnInit {
@ViewChild('editor') editor!: ElementRef;

histories: MatchingHistory[] = [];
loading = true;
isPanelVisible = false;
history: MatchingHistory | null = null;
editorView: EditorView | null = null;

constructor(
private historyService: HistoryService,
private messageService: MessageService,
private datePipe: DatePipe,
) {}

ngOnInit() {
this.historyService.getHistories().subscribe({
next: data => {
this.histories = data;
this.histories = data.map(history => ({
...history,
time: this.datePipe.transform(history.time, 'short') // Pipe to format date for searching
}));
this.loading = false;
console.log(this.histories);
},
error: () => {
this.histories = [];
Expand All @@ -39,4 +59,40 @@ export class HistoryComponent implements OnInit {
},
});
}

onRowSelect(history: MatchingHistory) {
this.history = history;
this.isPanelVisible = true;
if (history.code && history.language) {
this.initializeEditor(history.code, history.language);
}
}

closePanel() {
this.isPanelVisible = false;
this.history = null;
}

initializeEditor(code: string, language: string) {
if (this.editor) {
if (this.editorView) {
this.editorView.destroy();
}

const languageExtension = languageMap[language] || languageMap["java"];
const state = EditorState.create({
doc: code,
extensions: [
basicSetup,
languageExtension,
oneDark,
],
});

this.editorView = new EditorView({
state,
parent: this.editor.nativeElement,
});
}
}
}
10 changes: 9 additions & 1 deletion frontend/src/app/account/history/history.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export interface MatchingHistory {
difficulty: DifficultyLevels; // question difficulty
topics: string[]; // question topics
status: statusValues; // status of the session
time: string; // time of the session
time: string | null; // time of the session
language: string | null; // language used during the session
code: string | null; // code written during the session
}

export interface User {
Expand All @@ -30,6 +32,11 @@ export interface Question {
_id: string;
}

export interface Snapshot {
language: string | null;
code: string | null;
}

export interface sessionHistory {
_id: string;
roomId: string;
Expand All @@ -39,6 +46,7 @@ export interface sessionHistory {
status: statusValues;
createdAt: string;
updatedAt: string;
snapshot: Snapshot;
}

export interface historyResponse {
Expand Down
32 changes: 27 additions & 5 deletions frontend/src/app/account/profile/profile.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ export class ProfileComponent implements OnInit {
];

ngOnInit() {
this.user = this.authenticationService.userValue;
this.authenticationService.user$.subscribe(() => {
this.user = this.authenticationService.userValue as User;
});
this.showEditProfile = false;
this.showEditPassword = false;
this.isProcessingEdit = false;
Expand Down Expand Up @@ -122,8 +124,18 @@ export class ProfileComponent implements OnInit {
)
.subscribe({
next: () => {
this.router.navigateByUrl('/account', { skipLocationChange: true }).then(() => {
this.router.navigate(['/account/profile']);
// Reset states and forms on success
this.showEditProfile = false;
this.showEditPassword = false;
this.isProcessingEdit = false;
this.isProcessingPassword = false;
this.editProfileForm.reset();
this.editPasswordForm.reset();

this.messageService.add({
severity: 'success',
summary: 'Profile Updated',
detail: 'Your profile has been updated successfully!',
});
},
error: error => {
Expand Down Expand Up @@ -181,8 +193,18 @@ export class ProfileComponent implements OnInit {
)
.subscribe({
next: () => {
this.router.navigateByUrl('/account', { skipLocationChange: true }).then(() => {
this.router.navigate(['/account/profile']);
// Reset states and forms on success
this.showEditProfile = false;
this.showEditPassword = false;
this.isProcessingEdit = false;
this.isProcessingPassword = false;
this.editProfileForm.reset();
this.editPasswordForm.reset();

this.messageService.add({
severity: 'success',
summary: 'Password Updated',
detail: 'Your password has been updated successfully!',
});
},
error: error => {
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/app/collaboration/editor/editor.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@
[ydoc]="ydoc"
[roomId]="roomId"
[isVisible]="isSubmit"
[isInitiator]="isInitiator" />
[isInitiator]="isInitiator"
[selectedLanguage]="selectedLanguage"
[yeditorText]="yeditorText" />
<app-forfeit-dialog
(notify)="forfeitNotify()"
(dialogClose)="onForfeitDialogClose()"
[yforfeit]="yforfeit"
[roomId]="roomId"
[isVisible]="isForfeitClick" />
[isVisible]="isForfeitClick"
[selectedLanguage]="selectedLanguage"
[yeditorText]="yeditorText" />
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
import { Component, EventEmitter, Input, Output, OnInit, input } from '@angular/core';
import { DialogModule } from 'primeng/dialog';
import { ButtonModule } from 'primeng/button';
import { Router } from '@angular/router';
Expand All @@ -19,6 +19,8 @@ export class ForfeitDialogComponent implements OnInit {
@Input() roomId!: string;
@Input() isVisible = false;
@Input() yforfeit!: Y.Map<boolean>;
@Input() selectedLanguage!: string;
@Input() yeditorText!: Y.Text;

@Output() dialogClose = new EventEmitter<void>();
@Output() notify = new EventEmitter<void>();
Expand All @@ -27,6 +29,7 @@ export class ForfeitDialogComponent implements OnInit {
isForfeit = false;
userId!: string;
hideButtons = false;
code!: string;

constructor(
private authService: AuthenticationService,
Expand Down Expand Up @@ -60,9 +63,12 @@ export class ForfeitDialogComponent implements OnInit {
}

onForfeit() {
this.code = this.yeditorText.toString();
const userId = this.authService.userValue?.id;
console.log('Forfeiting language:', this.selectedLanguage);
console.log('Forfeiting code:', this.code);
if (userId) {
this.collabService.forfeit(this.roomId).subscribe({
this.collabService.forfeit(this.roomId, this.selectedLanguage, this.code).subscribe({
next: () => {
this.yforfeit.set(userId, true);
this.message = 'You have forfeited. \n\n Redirecting you to homepage...';
Expand Down
Loading

0 comments on commit 34ec12f

Please sign in to comment.