From 52f1cbf790ccc5c8ee887f7738a8391d2fec3630 Mon Sep 17 00:00:00 2001
From: Hiroki Terashima
Date: Fri, 29 Aug 2025 09:35:59 -0700
Subject: [PATCH 1/4] Add Location filter to unit library. Each location can
have 3 levels
---
src/app/domain/projectFilterValues.ts | 16 +++-
src/app/modules/library/Location.ts | 40 ++++++++
.../library-filters.component.html | 17 ++++
.../library-filters.component.ts | 25 ++++-
.../location-select-menu.component.html | 25 +++++
.../location-select-menu.component.ts | 35 +++++++
src/messages.xlf | 92 ++++++++++++-------
7 files changed, 213 insertions(+), 37 deletions(-)
create mode 100644 src/app/modules/library/Location.ts
create mode 100644 src/app/modules/shared/location-select-menu/location-select-menu.component.html
create mode 100644 src/app/modules/shared/location-select-menu/location-select-menu.component.ts
diff --git a/src/app/domain/projectFilterValues.ts b/src/app/domain/projectFilterValues.ts
index 49434eb6659..9a3a3e7017e 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)
);
}
@@ -95,6 +98,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.id))
+ );
+ }
+
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..b3d80bc76ba
--- /dev/null
+++ b/src/app/modules/library/Location.ts
@@ -0,0 +1,40 @@
+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 {
+ id: string;
+ name: string;
+ type: LocationType;
+ constructor(type: LocationType, name: string) {
+ this.type = type;
+ this.name = name;
+ this.id = `${locationTypeToLabel[type]}:${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..d5b43c73c39 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/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 @@
+
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..28230ae521a
--- /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.id === option.id)) {
+ 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..4757149f526 100644
--- a/src/messages.xlf
+++ b/src/messages.xlf
@@ -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
@@ -6685,6 +6729,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 +7809,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 +7820,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
From d0263010f21f6fd40851910312f28939210635ab Mon Sep 17 00:00:00 2001
From: Hiroki Terashima
Date: Fri, 29 Aug 2025 09:55:14 -0700
Subject: [PATCH 2/4] Show locations in unit details
---
.../library-project-details.component.html | 10 +++++++
src/messages.xlf | 27 ++++++++++++-------
2 files changed, 27 insertions(+), 10 deletions(-)
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/messages.xlf b/src/messages.xlf
index 4757149f526..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
@@ -6065,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
@@ -6122,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
@@ -6157,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
From c4f452b634c43783d5c609c7e925cfd60f2f3604 Mon Sep 17 00:00:00 2001
From: Jonathan Lim-Breitbart
Date: Tue, 2 Sep 2025 13:01:57 -0700
Subject: [PATCH 3/4] Filter locations by name field instead of id
---
src/app/domain/projectFilterValues.ts | 2 +-
src/app/modules/library/Location.ts | 2 --
.../library/library-filters/library-filters.component.html | 2 +-
.../location-select-menu/location-select-menu.component.ts | 2 +-
4 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/app/domain/projectFilterValues.ts b/src/app/domain/projectFilterValues.ts
index 9a3a3e7017e..1f0b459a3c8 100644
--- a/src/app/domain/projectFilterValues.ts
+++ b/src/app/domain/projectFilterValues.ts
@@ -105,7 +105,7 @@ export class ProjectFilterValues {
?.map((location) => Object.assign(new Location(), location))
.map((location) => location.getLocationOptions())
.flat()
- .some((locationOption) => this.locationValue.includes(locationOption.id))
+ .some((locationOption) => this.locationValue.includes(locationOption.name))
);
}
diff --git a/src/app/modules/library/Location.ts b/src/app/modules/library/Location.ts
index b3d80bc76ba..fd6c9e10980 100644
--- a/src/app/modules/library/Location.ts
+++ b/src/app/modules/library/Location.ts
@@ -7,13 +7,11 @@ export const locationTypeToLabel: { [key in LocationType]: string } = {
};
export class LocationOption {
- id: string;
name: string;
type: LocationType;
constructor(type: LocationType, name: string) {
this.type = type;
this.name = name;
- this.id = `${locationTypeToLabel[type]}:${name}`;
}
}
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 d5b43c73c39..a7f5cbb888d 100644
--- a/src/app/modules/library/library-filters/library-filters.component.html
+++ b/src/app/modules/library/library-filters/library-filters.component.html
@@ -138,7 +138,7 @@ Filters
placeholderText="Locations"
[value]="filterValues.locationValue"
(update)="filterUpdated($event, 'location')"
- [valueProp]="'id'"
+ [valueProp]="'name'"
[viewValueProp]="'name'"
[multiple]="true"
/>
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
index 28230ae521a..5fc1ade8149 100644
--- 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
@@ -24,7 +24,7 @@ export class LocationSelectMenuComponent extends SelectMenuComponent {
this.options
.flatMap((option: Location) => option.getLocationOptions())
.forEach((option: LocationOption) => {
- if (!this.locationOptions[option.type].some((opt) => opt.id === option.id)) {
+ if (!this.locationOptions[option.type].some((opt) => opt.name === option.name)) {
this.locationOptions[option.type].push(option);
}
});
From 587819e884ddb8eea886869a8dfaf0df4f6d11e8 Mon Sep 17 00:00:00 2001
From: Jonathan Lim-Breitbart
Date: Tue, 2 Sep 2025 13:03:01 -0700
Subject: [PATCH 4/4] Add location to hasFilters check and clear filters action
---
src/app/domain/projectFilterValues.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/domain/projectFilterValues.ts b/src/app/domain/projectFilterValues.ts
index 1f0b459a3c8..154e253a212 100644
--- a/src/app/domain/projectFilterValues.ts
+++ b/src/app/domain/projectFilterValues.ts
@@ -57,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
);
}
@@ -70,6 +71,7 @@ export class ProjectFilterValues {
this.searchValue = '';
this.standardValue = [];
this.unitTypeValue = [];
+ this.locationValue = [];
}
private matchesUnitType(project: LibraryProject): boolean {