Skip to content

Commit 8147e48

Browse files
Aaron-Detrebreity
andauthored
feat(Curriculum): Add My Units tab (#2158)
Co-authored-by: Jonathan Lim-Breitbart <[email protected]>
1 parent e9badfb commit 8147e48

23 files changed

+419
-289
lines changed

src/app/curriculum/curriculum.component.html

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,44 @@
11
<div class="main app-background">
22
<div class="content">
33
<header class="content__header">
4-
<h1 class="accent flex items-center gap-2 mat-headline-4">
5-
<mat-icon>menu_book</mat-icon>
6-
<span i18n>Unit Library</span>
7-
</h1>
4+
<div class="flex flex-col sm:flex-row sm:justify-between mb-4 sm:mb-0">
5+
<h1 class="accent flex items-center gap-2 mat-headline-4">
6+
<mat-icon>menu_book</mat-icon>
7+
<span i18n>Unit Library</span>
8+
</h1>
9+
@if (showMyUnits) {
10+
<a
11+
mat-flat-button
12+
color="primary"
13+
class="self-start"
14+
href="{{ configService.getAuthoringToolLink() }}"
15+
i18n
16+
>Authoring Tool</a
17+
>
18+
}
19+
</div>
820
<p class="pb-4" i18n>
921
Explore our curriculum library to find units and resources to use with your classes!
1022
</p>
1123
</header>
12-
<section class="library library-home notice-bg-bg">
24+
<section class="library notice-bg-bg rounded-md">
1325
<div class="flex flex-col md:flex-row">
1426
<div class="dark-theme content-block primary-bg p-4 md:w-1/3 lg:w-1/4">
1527
<app-library-filters [isSplitScreen]="true" />
1628
</div>
17-
<div class="library__content library-home__content md:w-2/3 lg:w-3/4">
18-
<public-library />
29+
<div class="md:w-2/3 lg:w-3/4">
30+
@if (showMyUnits) {
31+
<mat-tab-group dynamicHeight>
32+
<mat-tab [label]="getPublicTabLabel()">
33+
<public-library />
34+
</mat-tab>
35+
<mat-tab [label]="getMyUnitsTabLabel()">
36+
<app-personal-library />
37+
</mat-tab>
38+
</mat-tab-group>
39+
} @else {
40+
<public-library />
41+
}
1942
</div>
2043
</div>
2144
</section>

src/app/curriculum/curriculum.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
border-end-start-radius: 0;
55

66
@apply md:rounded-md md:rounded-se-none md:rounded-ee-none;
7-
}
7+
}
Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,65 @@
1+
import { BehaviorSubject, of } from 'rxjs';
12
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { ConfigService } from '../services/config.service';
24
import { CurriculumComponent } from './curriculum.component';
3-
import { MockComponents, MockProvider } from 'ng-mocks';
5+
import { LibraryFiltersComponent } from '../modules/library/library-filters/library-filters.component';
46
import { LibraryService } from '../services/library.service';
5-
import { of } from 'rxjs';
7+
import { MockComponents, MockProvider, MockProviders } from 'ng-mocks';
8+
import { PersonalLibraryComponent } from '../modules/library/personal-library/personal-library.component';
69
import { ProjectFilterValues } from '../domain/projectFilterValues';
7-
import { LibraryFiltersComponent } from '../modules/library/library-filters/library-filters.component';
810
import { PublicLibraryComponent } from '../modules/library/public-library/public-library.component';
11+
import { User } from '../domain/user';
12+
import { UserService } from '../services/user.service';
913

1014
describe('CurriculumComponent', () => {
1115
let component: CurriculumComponent;
1216
let fixture: ComponentFixture<CurriculumComponent>;
1317

1418
beforeEach(async () => {
1519
await TestBed.configureTestingModule({
16-
declarations: [MockComponents(LibraryFiltersComponent, PublicLibraryComponent)],
20+
declarations: [
21+
MockComponents(LibraryFiltersComponent, PersonalLibraryComponent, PublicLibraryComponent)
22+
],
1723
imports: [CurriculumComponent],
1824
providers: [
25+
MockProviders(ConfigService, UserService),
1926
MockProvider(LibraryService, {
2027
projectFilterValuesSource$: of({} as ProjectFilterValues),
21-
communityLibraryProjectsSource$: of([])
28+
communityLibraryProjectsSource$: of([]),
29+
numberOfPublicProjectsVisible$: of(3),
30+
numberOfPersonalProjectsVisible$: of(2)
2231
})
2332
]
2433
}).compileComponents();
34+
spyOn(TestBed.inject(ConfigService), 'getAuthoringToolLink').and.returnValue('');
2535

2636
fixture = TestBed.createComponent(CurriculumComponent);
2737
component = fixture.componentInstance;
2838
fixture.detectChanges();
2939
});
3040

31-
it('should create', () => {
32-
expect(component).toBeTruthy();
41+
it('should hide My Units tab and authoring tool link when not logged in as a teacher', () => {
42+
spyOn(TestBed.inject(UserService), 'isTeacher').and.returnValue(false);
43+
component.ngOnInit();
44+
fixture.detectChanges();
45+
expect(numAuthoringToolButtonElements(fixture)).toEqual(0);
46+
expect(numTabGroupElements(fixture)).toEqual(0);
47+
});
48+
49+
it('should show My Units tab and authoring tool link when logged in as teacher', () => {
50+
spyOn(TestBed.inject(UserService), 'isTeacher').and.returnValue(true);
51+
component.ngOnInit();
52+
fixture.detectChanges();
53+
expect(numAuthoringToolButtonElements(fixture)).toEqual(1);
54+
expect(numTabGroupElements(fixture)).toEqual(1);
55+
expect(component['showMyUnits']).toBeTruthy();
3356
});
3457
});
58+
59+
function numAuthoringToolButtonElements(fixture: ComponentFixture<CurriculumComponent>) {
60+
return fixture.debugElement.nativeElement.querySelectorAll('a').length;
61+
}
62+
63+
function numTabGroupElements(fixture: ComponentFixture<CurriculumComponent>) {
64+
return fixture.debugElement.nativeElement.querySelectorAll('mat-tab-group').length;
65+
}
Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
1+
import { CommonModule } from '@angular/common';
12
import { Component } from '@angular/core';
2-
import { PublicLibraryComponent } from '../modules/library/public-library/public-library.component';
3+
import { ConfigService } from '../services/config.service';
34
import { LibraryFiltersComponent } from '../modules/library/library-filters/library-filters.component';
5+
import { LibraryService } from '../services/library.service';
46
import { MatIconModule } from '@angular/material/icon';
7+
import { MatTabsModule } from '@angular/material/tabs';
8+
import { PersonalLibraryComponent } from '../modules/library/personal-library/personal-library.component';
9+
import { PublicLibraryComponent } from '../modules/library/public-library/public-library.component';
10+
import { Subscription } from 'rxjs';
11+
import { UserService } from '../services/user.service';
12+
import { MatButtonModule } from '@angular/material/button';
513

614
@Component({
7-
selector: 'curriculum',
8-
imports: [LibraryFiltersComponent, MatIconModule, PublicLibraryComponent],
9-
templateUrl: './curriculum.component.html',
10-
styleUrl: './curriculum.component.scss'
15+
imports: [
16+
CommonModule,
17+
LibraryFiltersComponent,
18+
MatButtonModule,
19+
MatIconModule,
20+
MatTabsModule,
21+
PersonalLibraryComponent,
22+
PublicLibraryComponent
23+
],
24+
styleUrl: './curriculum.component.scss',
25+
templateUrl: './curriculum.component.html'
1126
})
12-
export class CurriculumComponent {}
27+
export class CurriculumComponent {
28+
private numMyUnitsVisible: number = 0;
29+
private numPublicUnitsVisible: number = 0;
30+
protected showMyUnits: boolean;
31+
private subscriptions: Subscription = new Subscription();
32+
33+
constructor(
34+
protected configService: ConfigService,
35+
private libraryService: LibraryService,
36+
private userService: UserService
37+
) {}
38+
39+
ngOnInit(): void {
40+
this.showMyUnits = this.userService.isTeacher();
41+
this.getLibraryProjects();
42+
this.subscribeNumUnitsVisible();
43+
}
44+
45+
ngOnDestroy(): void {
46+
this.subscriptions.unsubscribe();
47+
}
48+
49+
private getLibraryProjects(): void {
50+
this.libraryService.getCommunityLibraryProjects();
51+
this.libraryService.getOfficialLibraryProjects();
52+
if (this.showMyUnits) {
53+
this.libraryService.getPersonalLibraryProjects();
54+
this.libraryService.getSharedLibraryProjects();
55+
}
56+
}
57+
58+
private subscribeNumUnitsVisible(): void {
59+
this.subscriptions.add(
60+
this.libraryService.numberOfPersonalProjectsVisible$.subscribe(
61+
(num) => (this.numMyUnitsVisible = num)
62+
)
63+
);
64+
this.subscriptions.add(
65+
this.libraryService.numberOfPublicProjectsVisible$.subscribe(
66+
(num) => (this.numPublicUnitsVisible = num)
67+
)
68+
);
69+
}
70+
71+
protected getPublicTabLabel(): string {
72+
return $localize`Public (${this.numPublicUnitsVisible})`;
73+
}
74+
75+
protected getMyUnitsTabLabel(): string {
76+
return $localize`My Units (${this.numMyUnitsVisible})`;
77+
}
78+
}

src/app/modules/library/community-library/community-library.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class MockLibraryService {
1515
disciplineValue: [],
1616
standardValue: []
1717
});
18-
numberOfCommunityProjectsVisible = new BehaviorSubject<number>(0);
18+
numberOfPublicProjectsVisible = new BehaviorSubject<number>(0);
1919
}
2020

2121
describe('CommunityLibraryComponent', () => {

src/app/modules/library/community-library/community-library.component.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
import { BehaviorSubject } from 'rxjs';
12
import { Component, Inject } from '@angular/core';
23
import { LibraryService } from '../../../services/library.service';
34
import { LibraryProject } from '../libraryProject';
45
import { LibraryComponent } from '../library/library.component';
56
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
67

78
@Component({
8-
selector: 'app-community-library',
9-
templateUrl: './community-library.component.html',
10-
styleUrls: ['./community-library.component.scss'],
11-
standalone: false
9+
selector: 'app-community-library',
10+
templateUrl: './community-library.component.html',
11+
styleUrls: ['./community-library.component.scss'],
12+
standalone: false
1213
})
1314
export class CommunityLibraryComponent extends LibraryComponent {
1415
projects: LibraryProject[] = [];
1516
filteredProjects: LibraryProject[] = [];
1617

17-
constructor(protected dialog: MatDialog, protected libraryService: LibraryService) {
18+
constructor(
19+
protected dialog: MatDialog,
20+
protected libraryService: LibraryService
21+
) {
1822
super(dialog, libraryService);
1923
}
2024

@@ -28,12 +32,8 @@ export class CommunityLibraryComponent extends LibraryComponent {
2832
);
2933
}
3034

31-
emitNumberOfProjectsVisible(numProjectsVisible: number = null) {
32-
if (numProjectsVisible) {
33-
this.libraryService.numberOfCommunityProjectsVisible.next(numProjectsVisible);
34-
} else {
35-
this.libraryService.numberOfCommunityProjectsVisible.next(this.filteredProjects.length);
36-
}
35+
protected getNumVisiblePersonalOrPublicProjects(): BehaviorSubject<number> {
36+
return this.libraryService.numberOfPublicProjectsVisible;
3737
}
3838

3939
protected getDetailsComponent(): any {
@@ -42,9 +42,9 @@ export class CommunityLibraryComponent extends LibraryComponent {
4242
}
4343

4444
@Component({
45-
selector: 'community-library-details',
46-
templateUrl: 'community-library-details.html',
47-
standalone: false
45+
selector: 'community-library-details',
46+
templateUrl: 'community-library-details.html',
47+
standalone: false
4848
})
4949
export class CommunityLibraryDetailsComponent {
5050
constructor(

src/app/modules/library/library-project/library-project.component.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@if (project.visible) {
22
<mat-card
3-
appearance="outlined"
4-
class="library-project mat-card--button"
3+
class="library-project"
54
(click)="showDetails()"
65
(keyup.enter)="showDetails()"
76
[@flash]="{ value: '*', params: { duration: animateDuration, delay: animateDelay } }"

src/app/modules/library/library.module.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import { SelectMenuComponent } from '../shared/select-menu/select-menu.component
5555
import { UnitTagsComponent } from '../../teacher/unit-tags/unit-tags.component';
5656
import { ProjectTagService } from '../../../assets/wise5/services/projectTagService';
5757
import { StandardsSelectMenuComponent } from '../shared/standards-select-menu/standards-select-menu.component';
58+
import { CurriculumComponent } from '../../curriculum/curriculum.component';
59+
import { ColorService } from '../../../assets/wise5/services/colorService';
5860

5961
const materialModules = [
6062
MatAutocompleteModule,
@@ -83,11 +85,13 @@ const materialModules = [
8385
ApplyTagsButtonComponent,
8486
ArchiveProjectsButtonComponent,
8587
CommonModule,
88+
CurriculumComponent,
8689
FlexLayoutModule,
8790
FormsModule,
8891
LibraryFiltersComponent,
8992
LibraryProjectComponent,
9093
LibraryProjectDetailsComponent,
94+
PersonalLibraryComponent,
9195
ReactiveFormsModule,
9296
RouterModule,
9397
materialModules,
@@ -108,19 +112,21 @@ const materialModules = [
108112
OfficialLibraryDetailsComponent,
109113
CommunityLibraryComponent,
110114
CommunityLibraryDetailsComponent,
111-
PersonalLibraryComponent,
112115
PersonalLibraryDetailsComponent,
113116
ShareProjectDialogComponent,
114117
CopyProjectDialogComponent
115118
],
116119
exports: [
120+
CurriculumComponent,
117121
HomePageProjectLibraryComponent,
122+
PersonalLibraryComponent,
118123
ReactiveFormsModule,
119124
TeacherProjectLibraryComponent,
120125
UnitTagsComponent,
121126
materialModules
122127
],
123128
providers: [
129+
ColorService,
124130
LibraryService,
125131
{ provide: MatPaginatorIntl, useClass: LibraryPaginatorIntl },
126132
ProjectTagService

src/app/modules/library/library/library.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ProjectFilterValues } from '../../../domain/projectFilterValues';
33
import { LibraryService } from '../../../services/library.service';
44
import { LibraryProject } from '../libraryProject';
55
import { PageEvent, MatPaginator } from '@angular/material/paginator';
6-
import { Subscription } from 'rxjs';
6+
import { BehaviorSubject, Subscription } from 'rxjs';
77
import { MatDialog } from '@angular/material/dialog';
88

99
@Directive()
@@ -79,7 +79,15 @@ export abstract class LibraryComponent implements OnInit {
7979
this.setPagination();
8080
}
8181

82-
protected abstract emitNumberOfProjectsVisible(numProjectsVisible: number): void;
82+
protected emitNumberOfProjectsVisible(numProjectsVisible: number): void {
83+
if (numProjectsVisible) {
84+
this.getNumVisiblePersonalOrPublicProjects().next(numProjectsVisible);
85+
} else {
86+
this.getNumVisiblePersonalOrPublicProjects().next(this.filteredProjects.length);
87+
}
88+
}
89+
90+
protected abstract getNumVisiblePersonalOrPublicProjects(): BehaviorSubject<number>;
8391

8492
protected countVisibleProjects(projects: LibraryProject[]): number {
8593
return projects.filter((project) => project.visible).length;

src/app/modules/library/official-library/official-library.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class MockLibraryService {
1717
standardValue: []
1818
});
1919
implementationModelOptions: LibraryGroup[] = [];
20-
numberOfOfficialProjectsVisible = new BehaviorSubject<number>(0);
20+
numberOfPublicProjectsVisible = new BehaviorSubject<number>(0);
2121
getOfficialLibraryProjects() {}
2222
}
2323

0 commit comments

Comments
 (0)