diff --git a/src/app/domain/projectFilterValues.ts b/src/app/domain/projectFilterValues.ts index 49434eb6659..154e253a212 100644 --- a/src/app/domain/projectFilterValues.ts +++ b/src/app/domain/projectFilterValues.ts @@ -1,10 +1,12 @@ import { Subject } from 'rxjs'; import { LibraryProject } from '../modules/library/libraryProject'; +import { Location } from '../modules/library/Location'; export class ProjectFilterValues { disciplineValue: string[] = []; featureValue: string[] = []; gradeLevelValue: number[] = []; + locationValue: string[] = []; publicUnitType?: ('wiseTested' | 'communityBuilt')[] = []; publicUnitTypeValue?: ('wiseTested' | 'communityBuilt')[] = []; searchValue: string = ''; @@ -21,7 +23,8 @@ export class ProjectFilterValues { this.matchesDiscipline(project) && this.matchesUnitType(project) && this.matchesFeature(project) && - this.matchesGradeLevel(project) + this.matchesGradeLevel(project) && + this.matchesLocation(project) ); } @@ -54,7 +57,8 @@ export class ProjectFilterValues { this.disciplineValue.length + this.unitTypeValue.length + this.gradeLevelValue.length + - this.featureValue.length > + this.featureValue.length + + this.locationValue.length > 0 ); } @@ -67,6 +71,7 @@ export class ProjectFilterValues { this.searchValue = ''; this.standardValue = []; this.unitTypeValue = []; + this.locationValue = []; } private matchesUnitType(project: LibraryProject): boolean { @@ -95,6 +100,17 @@ export class ProjectFilterValues { ); } + private matchesLocation(project: LibraryProject): boolean { + return ( + this.locationValue.length === 0 || + project.metadata.locations + ?.map((location) => Object.assign(new Location(), location)) + .map((location) => location.getLocationOptions()) + .flat() + .some((locationOption) => this.locationValue.includes(locationOption.name)) + ); + } + private matchesFeature(project: LibraryProject): boolean { return ( this.featureValue.length === 0 || diff --git a/src/app/modules/library/Location.ts b/src/app/modules/library/Location.ts new file mode 100644 index 00000000000..fd6c9e10980 --- /dev/null +++ b/src/app/modules/library/Location.ts @@ -0,0 +1,38 @@ +export type LocationType = 'level1' | 'level2' | 'level3'; + +export const locationTypeToLabel: { [key in LocationType]: string } = { + level3: $localize`Locale`, + level2: $localize`State`, + level1: $localize`Country` +}; + +export class LocationOption { + name: string; + type: LocationType; + constructor(type: LocationType, name: string) { + this.type = type; + this.name = name; + } +} + +// Represents a geographical location associated with a project +export class Location { + id: string = ''; + level1: string = ''; // country + level2: string = ''; // state + level3: string = ''; // city, county, or other locale + + getLocationOptions(): LocationOption[] { + const options = []; + if (this.level1) { + options.push(new LocationOption('level1', this.level1)); + } + if (this.level2) { + options.push(new LocationOption('level2', `${this.level2}, ${this.level1}`)); + } + if (this.level3) { + options.push(new LocationOption('level3', `${this.level3}, ${this.level2}, ${this.level1}`)); + } + return options; + } +} diff --git a/src/app/modules/library/library-filters/library-filters.component.html b/src/app/modules/library/library-filters/library-filters.component.html index 52a9022624b..a7f5cbb888d 100644 --- a/src/app/modules/library/library-filters/library-filters.component.html +++ b/src/app/modules/library/library-filters/library-filters.component.html @@ -127,5 +127,22 @@

Filters

/> } + @if (locationOptions.length > 0) { +
+ +
+ } diff --git a/src/app/modules/library/library-filters/library-filters.component.ts b/src/app/modules/library/library-filters/library-filters.component.ts index 808b7e5cb9b..8dac1800901 100644 --- a/src/app/modules/library/library-filters/library-filters.component.ts +++ b/src/app/modules/library/library-filters/library-filters.component.ts @@ -16,6 +16,8 @@ import { Feature } from '../Feature'; import { Grade, GradeLevel } from '../GradeLevel'; import { MatDialog } from '@angular/material/dialog'; import { DialogWithCloseComponent } from '../../../../assets/wise5/directives/dialog-with-close/dialog-with-close.component'; +import { Location } from '../Location'; +import { LocationSelectMenuComponent } from '../../shared/location-select-menu/location-select-menu.component'; @Component({ imports: [ @@ -23,6 +25,7 @@ import { DialogWithCloseComponent } from '../../../../assets/wise5/directives/di MatBadgeModule, MatButtonModule, MatIconModule, + LocationSelectMenuComponent, SearchBarComponent, SelectMenuComponent, StandardsSelectMenuComponent @@ -44,6 +47,7 @@ export class LibraryFiltersComponent { private sharedProjects: LibraryProject[] = []; protected showFilters: boolean = false; protected standardOptions: Standard[] = []; + protected locationOptions: Location[] = []; protected unitTypeOptions: { id: string; name: string }[] = [ { id: 'WISE Platform', name: $localize`WISE Platform` }, { id: 'Other Platform', name: $localize`Other Platform` } @@ -97,6 +101,7 @@ export class LibraryFiltersComponent { ); this.populateGradeLevels(project); this.populateStandards(project); + this.populateLocations(project); } private populateGradeLevels(project: LibraryProject): void { @@ -123,12 +128,23 @@ export class LibraryFiltersComponent { }); } + private populateLocations(project: LibraryProject): void { + project.metadata.locations?.forEach((location: Location) => + this.locationOptions.push(Object.assign(new Location(), location)) + ); + } + private removeDuplicatesAndSortAlphabetically(): void { this.standardOptions = this.utilService.removeObjectArrayDuplicatesByProperty( this.standardOptions, 'id' ); this.utilService.sortObjectArrayByProperty(this.standardOptions, 'id'); + this.locationOptions = this.utilService.removeObjectArrayDuplicatesByProperty( + this.locationOptions, + 'id' + ); + this.utilService.sortObjectArrayByProperty(this.locationOptions, 'id'); this.disciplineOptions = this.utilService.removeObjectArrayDuplicatesByProperty( this.disciplineOptions, 'id' @@ -168,6 +184,9 @@ export class LibraryFiltersComponent { case 'unitType': this.filterValues.unitTypeValue = value; break; + case 'location': + this.filterValues.locationValue = value; + break; } this.emitFilterValues(); } @@ -182,9 +201,9 @@ export class LibraryFiltersComponent { } protected showTypeInfo(): void { - const message = $localize`"Type" indicates the platform on which a unit runs. "WISE Platform" units are created - using the WISE authoring tool. Students use WISE accounts to complete lessons and teachers can review and grade - work on the WISE platform. "Other" units are created using different platforms. Resources for these units + const message = $localize`"Type" indicates the platform on which a unit runs. "WISE Platform" units are created + using the WISE authoring tool. Students use WISE accounts to complete lessons and teachers can review and grade + work on the WISE platform. "Other" units are created using different platforms. Resources for these units are linked in the unit details.`; this.dialog.open(DialogWithCloseComponent, { data: { diff --git a/src/app/modules/library/library-project-details/library-project-details.component.html b/src/app/modules/library/library-project-details/library-project-details.component.html index dbee909a7c0..044984a80ba 100644 --- a/src/app/modules/library/library-project-details/library-project-details.component.html +++ b/src/app/modules/library/library-project-details/library-project-details.component.html @@ -148,6 +148,16 @@ }

} + @if (project.metadata.locations?.length > 0) { +

+ Locations: + @for (location of project.metadata.locations; track location.id; let last = $last) { + {{ location.level3 ? location.level3 + ', ' : '' + }}{{ location.level2 ? location.level2 + ', ' : '' }}{{ location.level1 }} + {{ last ? '' : ' • ' }} + } +

+ } @if (project.tags) { } diff --git a/src/app/modules/shared/location-select-menu/location-select-menu.component.html b/src/app/modules/shared/location-select-menu/location-select-menu.component.html new file mode 100644 index 00000000000..99bf52db579 --- /dev/null +++ b/src/app/modules/shared/location-select-menu/location-select-menu.component.html @@ -0,0 +1,25 @@ + + {{ placeholderText }} + + @if (multiple) { + + {{ selectField.value ? selectField.value[0] : '' }} + @if (selectField.value?.length > 1) { + (+{{ selectField.value.length - 1 }} more) + } + + } + @for (label of labels; track label) { + + @for (option of locationOptions[label]; track option.id) { + {{ option[viewValueProp] }} + } + + } + + diff --git a/src/app/modules/shared/location-select-menu/location-select-menu.component.ts b/src/app/modules/shared/location-select-menu/location-select-menu.component.ts new file mode 100644 index 00000000000..5fc1ade8149 --- /dev/null +++ b/src/app/modules/shared/location-select-menu/location-select-menu.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { SelectMenuComponent } from '../select-menu/select-menu.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatSelectModule } from '@angular/material/select'; +import { + Location, + LocationOption, + LocationType, + locationTypeToLabel +} from '../../library/Location'; + +@Component({ + imports: [FormsModule, MatSelectModule, ReactiveFormsModule], + selector: 'location-select-menu', + templateUrl: './location-select-menu.component.html' +}) +export class LocationSelectMenuComponent extends SelectMenuComponent { + protected labels: LocationType[]; + protected locationOptions = { level3: [], level2: [], level1: [] }; + protected locationTypeToLabel = locationTypeToLabel; + + ngOnInit(): void { + super.ngOnInit(); + this.options + .flatMap((option: Location) => option.getLocationOptions()) + .forEach((option: LocationOption) => { + if (!this.locationOptions[option.type].some((opt) => opt.name === option.name)) { + this.locationOptions[option.type].push(option); + } + }); + this.labels = Object.keys(this.locationOptions).filter( + (key: LocationType) => this.locationOptions[key].length > 0 + ) as LocationType[]; + } +} diff --git a/src/messages.xlf b/src/messages.xlf index ec439f0e92c..167134f8dff 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -283,7 +283,7 @@ src/app/modules/library/library-project-details/library-project-details.component.html - 208,212 + 218,222 src/app/modules/library/public-unit-type-selector/community-library-details.html @@ -5753,6 +5753,43 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.27,30 + + Locale + + src/app/modules/library/Location.ts + 4 + + + + State + + src/app/modules/library/Location.ts + 5 + + + src/app/register/register-teacher-form/register-teacher-form.component.html + 69,70 + + + src/app/teacher/account/edit-profile/edit-profile.component.html + 63,64 + + + + Country + + src/app/modules/library/Location.ts + 6 + + + src/app/register/register-teacher-form/register-teacher-form.component.html + 78,79 + + + src/app/teacher/account/edit-profile/edit-profile.component.html + 72,73 + + Copy Unit @@ -5868,25 +5905,32 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.104,106 + + Locations + + src/app/modules/library/library-filters/library-filters.component.html + 138,140 + + WISE Platform src/app/modules/library/library-filters/library-filters.component.ts - 48 + 52 Other Platform src/app/modules/library/library-filters/library-filters.component.ts - 49 + 53 NGSS src/app/modules/library/library-filters/library-filters.component.ts - 114 + 119 src/app/modules/library/library-project-details/library-project-details.component.ts @@ -5897,7 +5941,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Common Core src/app/modules/library/library-filters/library-filters.component.ts - 115 + 120 src/app/modules/library/library-project-details/library-project-details.component.ts @@ -5908,28 +5952,28 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Learning For Justice src/app/modules/library/library-filters/library-filters.component.ts - 116 + 121 src/app/modules/library/library-project-details/library-project-details.component.ts 52 - - "Type" indicates the platform on which a unit runs. "WISE Platform" units are created - using the WISE authoring tool. Students use WISE accounts to complete lessons and teachers can review and grade - work on the WISE platform. "Other" units are created using different platforms. Resources for these units + + "Type" indicates the platform on which a unit runs. "WISE Platform" units are created + using the WISE authoring tool. Students use WISE accounts to complete lessons and teachers can review and grade + work on the WISE platform. "Other" units are created using different platforms. Resources for these units are linked in the unit details. src/app/modules/library/library-filters/library-filters.component.ts - 185,188 + 204,207 Unit Type src/app/modules/library/library-filters/library-filters.component.ts - 192 + 211 src/assets/wise5/authoringTool/edit-unit-type/edit-unit-type.component.html @@ -6021,53 +6065,60 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.121,123 + + Locations: + + src/app/modules/library/library-project-details/library-project-details.component.html + 153,154 + + This unit is a copy of (used under CC BY-SA). src/app/modules/library/library-project-details/library-project-details.component.html - 164,166 + 174,176 This unit is a copy of by (used under CC BY-SA). src/app/modules/library/library-project-details/library-project-details.component.html - 170,173 + 180,183 This unit is licensed under CC BY-SA. src/app/modules/library/library-project-details/library-project-details.component.html - 181,183 + 191,193 This unit is licensed under CC BY-SA by . src/app/modules/library/library-project-details/library-project-details.component.html - 186,188 + 196,198 View License src/app/modules/library/library-project-details/library-project-details.component.html - 193,197 + 203,207 More src/app/modules/library/library-project-details/library-project-details.component.html - 201,207 + 211,217 Use with Class src/app/modules/library/library-project-details/library-project-details.component.html - 217,222 + 227,232 src/app/teacher/create-run-dialog/create-run-dialog.component.html @@ -6078,7 +6129,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Preview src/app/modules/library/library-project-details/library-project-details.component.html - 225,227 + 235,237 src/app/teacher/run-menu/run-menu.component.html @@ -6113,7 +6164,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.Unit Resources src/app/modules/library/library-project-details/library-project-details.component.html - 227,232 + 237,242 @@ -6685,6 +6736,10 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. more + + src/app/modules/shared/location-select-menu/location-select-menu.component.html + 13,17 + src/app/modules/shared/select-menu/select-menu.component.html 14,18 @@ -7761,17 +7816,6 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.57,62 - - State - - src/app/register/register-teacher-form/register-teacher-form.component.html - 69,70 - - - src/app/teacher/account/edit-profile/edit-profile.component.html - 63,64 - - State required @@ -7783,17 +7827,6 @@ Click "Cancel" to keep the invalid JSON open so you can fix it.66,71 - - Country - - src/app/register/register-teacher-form/register-teacher-form.component.html - 78,79 - - - src/app/teacher/account/edit-profile/edit-profile.component.html - 72,73 - - Country required