Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,33 @@
</button>
<mat-menu #projectMenu="matMenu" xPosition="before">
<div class="header-account-menu">
<ng-container *ngIf="isChild">
@if (isChild) {
<div class="menu-info secondary-text" i18n>Parent ID: {{ project.parentId }}</div>
<mat-divider></mat-divider>
</ng-container>
<mat-divider />
}
<a mat-menu-item (click)="copyProject()">
<mat-icon>content_copy</mat-icon> <ng-container i18n>Copy</ng-container>
</a>
<a mat-menu-item (click)="editProject()" *ngIf="isCanEdit">
<mat-icon>edit</mat-icon> <ng-container i18n>Edit</ng-container>
</a>
<a mat-menu-item (click)="shareProject()" *ngIf="isCanShare">
<mat-icon>share</mat-icon> <ng-container i18n>Share</ng-container>
</a>
<a mat-menu-item *ngIf="!archived" (click)="archive(true)">
<mat-icon>archive</mat-icon>
<span i18n>Archive</span>
</a>
<a mat-menu-item *ngIf="archived" (click)="archive(false)">
<mat-icon>unarchive</mat-icon>
<span i18n>Restore</span>
</a>
@if (canEdit) {
<a mat-menu-item (click)="editProject()">
<mat-icon>edit</mat-icon> <ng-container i18n>Edit</ng-container>
</a>
}
@if (canShare) {
<a mat-menu-item (click)="shareProject()">
<mat-icon>share</mat-icon> <ng-container i18n>Share</ng-container>
</a>
}
@if (archived) {
<a mat-menu-item (click)="archive(false)">
<mat-icon>unarchive</mat-icon>
<span i18n>Restore</span>
</a>
} @else {
<a mat-menu-item (click)="archive(true)">
<mat-icon>archive</mat-icon>
<span i18n>Archive</span>
</a>
}
</div>
</mat-menu>
Original file line number Diff line number Diff line change
@@ -1,81 +1,30 @@
import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LibraryProjectMenuComponent } from './library-project-menu.component';
import { TeacherService } from '../../../teacher/teacher.service';
import { Project } from '../../../domain/project';
import { MatDialogModule } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu';
import { UserService } from '../../../services/user.service';
import { User } from '../../../domain/user';
import { Observable } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ConfigService } from '../../../services/config.service';
import { ArchiveProjectService } from '../../../services/archive-project.service';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { LibraryProjectMenuHarness } from './library-project-menu.harness';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { MockProviders } from 'ng-mocks';

export class MockUserService {
getUser(): Observable<User[]> {
const user: User = new User();
user.firstName = 'Demo';
user.lastName = 'Teacher';
user.roles = ['teacher'];
user.username = 'DemoTeacher';
user.id = 123456;
return Observable.create((observer) => {
observer.next(user);
observer.complete();
});
}
getUserId(): number {
return 123456;
}
}

export class MockTeacherService {
getProjectUsage(projectId: number): Observable<number> {
return Observable.create((observer) => {
observer.next(projectId);
observer.complete();
});
}
}

export class MockConfigService {
getContextPath(): string {
return '';
}
}

const archivedTag = { id: 1, text: 'archived', color: null };
let component: LibraryProjectMenuComponent;
let fixture: ComponentFixture<LibraryProjectMenuComponent>;
let harness: LibraryProjectMenuHarness;

describe('LibraryProjectMenuComponent', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LibraryProjectMenuComponent],
schemas: [NO_ERRORS_SCHEMA],
imports: [BrowserAnimationsModule,
MatDialogModule,
MatMenuModule,
MatSnackBarModule],
providers: [
ArchiveProjectService,
{ provide: TeacherService, useClass: MockTeacherService },
{ provide: UserService, useClass: MockUserService },
{ provide: ConfigService, useClass: MockConfigService },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting()
]
}).compileComponents();
})
);
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [BrowserAnimationsModule, LibraryProjectMenuComponent],
providers: [
MockProviders(ArchiveProjectService, ConfigService, TeacherService, UserService),
provideHttpClient(withInterceptorsFromDi())
]
}).compileComponents();
}));

beforeEach(async () => {
fixture = TestBed.createComponent(LibraryProjectMenuComponent);
Expand Down Expand Up @@ -109,7 +58,7 @@ function showsArchiveButton() {
function showsRestoreButton() {
describe('project has archived tag', () => {
beforeEach(() => {
component.project.tags = [archivedTag];
component.project.tags = [{ id: 1, text: 'archived', color: null }];
component.ngOnInit();
});
it('shows restore button', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,61 @@ import { UserService } from '../../../services/user.service';
import { ConfigService } from '../../../services/config.service';
import { EditRunWarningDialogComponent } from '../../../teacher/edit-run-warning-dialog/edit-run-warning-dialog.component';
import { ArchiveProjectService } from '../../../services/archive-project.service';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatMenuModule } from '@angular/material/menu';

@Component({
selector: 'app-library-project-menu',
styleUrl: './library-project-menu.component.scss',
templateUrl: './library-project-menu.component.html',
standalone: false
imports: [CommonModule, MatButtonModule, MatDividerModule, MatIconModule, MatMenuModule],
selector: 'app-library-project-menu',
styleUrl: './library-project-menu.component.scss',
templateUrl: './library-project-menu.component.html'
})
export class LibraryProjectMenuComponent {
@Input()
project: Project;

@Input()
isRun: boolean;

protected archived: boolean = false;
editLink: string = '';
previewLink: string = '';
isCanEdit: boolean = false;
isCanShare: boolean = false;
isChild: boolean = false;
protected archived: boolean;
protected canEdit: boolean;
protected canShare: boolean;
protected isChild: boolean;
@Input() isRun: boolean;
@Input() project: Project;

constructor(
private archiveProjectService: ArchiveProjectService,
private configService: ConfigService,
private dialog: MatDialog,
private teacherService: TeacherService,
private userService: UserService,
private configService: ConfigService
private userService: UserService
) {}

ngOnInit() {
this.isCanEdit = this.isOwner() || this.isSharedOwnerWithEditPermission();
this.isCanShare = this.isOwner() && !this.isRun;
this.editLink = `${this.configService.getContextPath()}/teacher/edit/unit/${this.project.id}`;
ngOnInit(): void {
this.canEdit = this.isOwner() || this.isSharedOwnerWithEditPermission();
this.canShare = this.isOwner() && !this.isRun;
this.isChild = this.project.isChild();
this.archived = this.project.hasTagWithText('archived');
}

isOwner() {
private isOwner(): boolean {
return this.userService.getUserId() == this.project.owner.id;
}

isSharedOwnerWithEditPermission() {
private isSharedOwnerWithEditPermission(): boolean {
const userId = this.userService.getUserId();
for (let sharedOwner of this.project.sharedOwners) {
if (userId == sharedOwner.id) {
return this.hasEditPermission(sharedOwner);
}
}
return false;
return this.project.sharedOwners.some(
(owner) => owner.id === userId && this.hasEditPermission(owner)
);
}

hasEditPermission(sharedOwner) {
private hasEditPermission(sharedOwner: any): boolean {
return sharedOwner.permissions.includes(Project.EDIT_PERMISSION);
}

copyProject() {
protected copyProject(): void {
this.teacherService.copyProject(this.project, this.dialog);
}

editProject() {
protected editProject(): void {
this.teacherService.getProjectLastRun(this.project.id).subscribe((projectRun) => {
if (projectRun != null) {
projectRun.project = this.project;
Expand All @@ -75,12 +70,12 @@ export class LibraryProjectMenuComponent {
panelClass: 'dialog-sm'
});
} else {
window.location.href = this.editLink;
window.location.href = `${this.configService.getContextPath()}/teacher/edit/unit/${this.project.id}`;
}
});
}

shareProject() {
protected shareProject(): void {
this.dialog.open(ShareProjectDialogComponent, {
data: { project: this.project },
panelClass: 'dialog-md'
Expand Down
2 changes: 1 addition & 1 deletion src/app/modules/library/library.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const materialModules = [
FlexLayoutModule,
FormsModule,
LibraryProjectDisciplinesComponent,
LibraryProjectMenuComponent,
ReactiveFormsModule,
RouterModule,
materialModules,
Expand All @@ -104,7 +105,6 @@ const materialModules = [
LibraryGroupThumbsComponent,
LibraryProjectComponent,
LibraryProjectDetailsComponent,
LibraryProjectMenuComponent,
LibraryFiltersComponent,
HomePageProjectLibraryComponent,
TeacherProjectLibraryComponent,
Expand Down
Loading
Loading