From 56bf4b2e8e0c5923c5195f990e6c4697a99da0ad Mon Sep 17 00:00:00 2001 From: FeTetra Date: Tue, 3 Feb 2026 22:38:44 -0500 Subject: [PATCH 1/4] Implement pages and routes for user listing --- src/app/api/client.service.ts | 18 +++- src/app/api/types/users/user-category.ts | 13 +++ src/app/app.routes.ts | 14 ++- .../items/user-category.component.ts | 43 ++++++++ src/app/components/ui/header/navtypes.ts | 8 +- ...t.html => level-categories.component.html} | 2 +- ...onent.ts => level-categories.component.ts} | 12 +-- .../categories/user-categories-component.html | 8 ++ .../categories/user-categories-component.ts | 27 +++++ .../level-listing.component.html | 2 +- .../level-listing/level-listing.component.ts | 2 +- .../user-listing/user-listing.component.html | 25 +++++ .../user-listing/user-listing.component.ts | 99 +++++++++++++++++++ src/app/services/embed.service.ts | 7 +- 14 files changed, 264 insertions(+), 16 deletions(-) create mode 100644 src/app/api/types/users/user-category.ts create mode 100644 src/app/components/items/user-category.component.ts rename src/app/pages/categories/{categories.component.html => level-categories.component.html} (76%) rename src/app/pages/categories/{categories.component.ts => level-categories.component.ts} (78%) create mode 100644 src/app/pages/categories/user-categories-component.html create mode 100644 src/app/pages/categories/user-categories-component.ts create mode 100644 src/app/pages/user-listing/user-listing.component.html create mode 100644 src/app/pages/user-listing/user-listing.component.ts diff --git a/src/app/api/client.service.ts b/src/app/api/client.service.ts index 425dfcd5..fde962da 100644 --- a/src/app/api/client.service.ts +++ b/src/app/api/client.service.ts @@ -16,6 +16,7 @@ import {Contest} from "./types/contests/contest"; import {Score} from "./types/levels/score"; import { LevelRelations } from './types/levels/level-relations'; import { Asset } from './types/asset'; +import {UserCategory} from "./types/users/user-category"; export const defaultPageSize: number = 40; @@ -24,7 +25,8 @@ export const defaultPageSize: number = 40; }) export class ClientService extends ApiImplementation { private readonly instance: LazySubject; - private readonly categories: LazySubject>; + private readonly levelCategories: LazySubject>; + private readonly userCategories: LazySubject>; private usersCache: User[] = []; @@ -33,7 +35,9 @@ export class ClientService extends ApiImplementation { this.instance = new LazySubject(() => this.http.get("/instance")); this.instance.tryLoad(); - this.categories = new LazySubject>(() => this.http.get>("/levels?includePreviews=true")) + this.levelCategories = new LazySubject>(() => this.http.get>("/levels?includePreviews=true")) + + this.userCategories = new LazySubject>(() => this.http.get>("/users")); } getInstance() { @@ -41,7 +45,7 @@ export class ClientService extends ApiImplementation { } getLevelCategories() { - return this.categories.asObservable(); + return this.levelCategories.asObservable(); } getRoomListing() { @@ -91,6 +95,14 @@ export class ClientService extends ApiImplementation { else return this.getUserByUuid(uuid!) } + getUserCategories() { + return this.userCategories.asObservable(); + } + + getUsersInCategory(category: string, skip: number = 0, count: number = defaultPageSize, params: Params | null = null) { + return this.http.get>(`/users/${category}`, {params: this.setPageQuery(params, skip, count)}); + } + getActivityPage(skip: number = 0, count: number = defaultPageSize) { return this.http.get(`/activity`, {params: this.createPageQuery(skip, count)}); } diff --git a/src/app/api/types/users/user-category.ts b/src/app/api/types/users/user-category.ts new file mode 100644 index 00000000..94f508f6 --- /dev/null +++ b/src/app/api/types/users/user-category.ts @@ -0,0 +1,13 @@ +import {User} from "./user"; +import {IconName} from "@fortawesome/free-solid-svg-icons"; + +export interface UserCategory { + apiRoute: string; + requiresUser: boolean; + name: string; + description: string; + iconHash: string; + fontAwesomeIcon: IconName; + previewUser: User | undefined; + hidden: boolean; +} \ No newline at end of file diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f66bc763..0de80563 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -9,13 +9,13 @@ export const routes: Routes = [ }, { path: 'levels', - loadComponent: () => import('./pages/categories/categories.component').then(x => x.CategoriesComponent), + loadComponent: () => import('./pages/categories/level-categories.component').then(x => x.LevelCategoriesComponent), data: {title: "Level Categories"} }, { path: 'levels/:category', loadComponent: () => import('./pages/level-listing/level-listing.component').then(x => x.LevelListingComponent), - data: {title: "Category"} + data: {title: "Level Listing"} }, { path: 'level/:id/:slug', @@ -59,6 +59,16 @@ export const routes: Routes = [ loadComponent: () => import('./pages/user/user.component').then(x => x.UserComponent), data: {title: "User Page"}, }, + { + path: 'users', + loadComponent: () => import('./pages/categories/user-categories-component').then(x => x.UserCategoriesComponent), + data: {title: "User Categories"}, + }, + { + path: 'users/:category', + loadComponent: () => import('./pages/user-listing/user-listing.component').then(x => x.UserListingComponent), + data: {title: "User Listing"}, + }, { path: 'u/:uuid', loadComponent: () => import('./pages/user/user.component').then(x => x.UserComponent), diff --git a/src/app/components/items/user-category.component.ts b/src/app/components/items/user-category.component.ts new file mode 100644 index 00000000..4e590d2b --- /dev/null +++ b/src/app/components/items/user-category.component.ts @@ -0,0 +1,43 @@ +import {Component, Input} from '@angular/core'; +import {UserCategory} from "../../api/types/users/user-category"; +import {ContainerComponent} from "../ui/container.component"; +import {RouterLink} from "@angular/router"; +import {FaIconComponent} from "@fortawesome/angular-fontawesome"; +import {faLink} from "@fortawesome/free-solid-svg-icons"; +import {ContainerTitleComponent} from "../ui/text/container-title.component"; + +import {DividerComponent} from "../ui/divider.component"; +import {UserPreviewComponent} from "./user-preview.component"; + +@Component({ + selector: 'app-user-category', + imports: [ + ContainerComponent, + RouterLink, + FaIconComponent, + ContainerTitleComponent, + DividerComponent, + UserPreviewComponent + ], + template: ` + + + + + {{category.name}} + + + +

{{category.description}}

+ + @if (category.previewUser) { + + + } +
+ ` +}) +export class UserCategoryComponent { + @Input({required: true}) category!: UserCategory; + protected readonly faLink = faLink; +} diff --git a/src/app/components/ui/header/navtypes.ts b/src/app/components/ui/header/navtypes.ts index 430308d3..b240f67c 100644 --- a/src/app/components/ui/header/navtypes.ts +++ b/src/app/components/ui/header/navtypes.ts @@ -12,7 +12,8 @@ import { faShareAlt, faThList, faTools, - faTrophy + faTrophy, + faUser } from "@fortawesome/free-solid-svg-icons"; export interface NavCategory { @@ -82,6 +83,11 @@ export const navTree: NavCategory[] = [ name: "Photos", icon: faImages, route: "/photos" + }, + { + name: "Users", + icon: faUser, + route: "/users" } ] } diff --git a/src/app/pages/categories/categories.component.html b/src/app/pages/categories/level-categories.component.html similarity index 76% rename from src/app/pages/categories/categories.component.html rename to src/app/pages/categories/level-categories.component.html index 68263c8c..b3288941 100644 --- a/src/app/pages/categories/categories.component.html +++ b/src/app/pages/categories/level-categories.component.html @@ -1,5 +1,5 @@ -

Discover and browse through new levels using categories!

+

Discover and browse through levels using categories!

@for (category of categories; track category.apiRoute) { diff --git a/src/app/pages/categories/categories.component.ts b/src/app/pages/categories/level-categories.component.ts similarity index 78% rename from src/app/pages/categories/categories.component.ts rename to src/app/pages/categories/level-categories.component.ts index f63bee28..6f50e82d 100644 --- a/src/app/pages/categories/categories.component.ts +++ b/src/app/pages/categories/level-categories.component.ts @@ -10,13 +10,13 @@ import {LevelCategoryComponent} from "../../components/items/level-category.comp @Component({ selector: 'app-categories', imports: [ - PageTitleComponent, - ResponsiveGridComponent, - LevelCategoryComponent -], - templateUrl: './categories.component.html' + PageTitleComponent, + ResponsiveGridComponent, + LevelCategoryComponent + ], + templateUrl: './level-categories.component.html' }) -export class CategoriesComponent { +export class LevelCategoriesComponent { categories: LevelCategory[] | undefined; constructor(client: ClientService) { diff --git a/src/app/pages/categories/user-categories-component.html b/src/app/pages/categories/user-categories-component.html new file mode 100644 index 00000000..d996f44b --- /dev/null +++ b/src/app/pages/categories/user-categories-component.html @@ -0,0 +1,8 @@ + +

Discover and browse through users using categories!

+ + + @for (category of categories; track category.apiRoute) { + + } + diff --git a/src/app/pages/categories/user-categories-component.ts b/src/app/pages/categories/user-categories-component.ts new file mode 100644 index 00000000..0fcba589 --- /dev/null +++ b/src/app/pages/categories/user-categories-component.ts @@ -0,0 +1,27 @@ +import { Component } from '@angular/core'; +import {PageTitleComponent} from "../../components/ui/text/page-title.component"; +import {ResponsiveGridComponent} from "../../components/ui/responsive-grid.component"; +import {UserCategory} from "../../api/types/users/user-category"; +import {ClientService} from "../../api/client.service"; + + +import {UserCategoryComponent} from "../../components/items/user-category.component"; + +@Component({ + selector: 'app-categories', + imports: [ + PageTitleComponent, + ResponsiveGridComponent, + UserCategoryComponent + ], + templateUrl: './user-categories-component.html' +}) +export class UserCategoriesComponent { + categories: UserCategory[] | undefined; + + constructor(client: ClientService) { + client.getUserCategories().subscribe(list => { + this.categories = list.data.filter(c => !c.requiresUser && !c.hidden); + }) + } +} diff --git a/src/app/pages/level-listing/level-listing.component.html b/src/app/pages/level-listing/level-listing.component.html index ef9b2c97..09f17641 100644 --- a/src/app/pages/level-listing/level-listing.component.html +++ b/src/app/pages/level-listing/level-listing.component.html @@ -16,7 +16,7 @@ @if (category == null) {

- We couldn't find that category. + We couldn't find that level category. Try checking the URL, or pick another category to get your bearings.

diff --git a/src/app/pages/level-listing/level-listing.component.ts b/src/app/pages/level-listing/level-listing.component.ts index f506987a..817e43f7 100644 --- a/src/app/pages/level-listing/level-listing.component.ts +++ b/src/app/pages/level-listing/level-listing.component.ts @@ -69,7 +69,7 @@ export class LevelListingComponent implements OnInit, Scrollable { if(this.category) { this.loadData(); - this.embed.embedCategory(this.category) + this.embed.embedLevelCategory(this.category) } else { console.warn("No category found for route " + route) } diff --git a/src/app/pages/user-listing/user-listing.component.html b/src/app/pages/user-listing/user-listing.component.html new file mode 100644 index 00000000..b258588b --- /dev/null +++ b/src/app/pages/user-listing/user-listing.component.html @@ -0,0 +1,25 @@ +@if (category) { + +

{{category.description}}

+ @if (users) { + + @for (user of this.users; track user.userId) { + + + + } + + } + +} + +@if (category == null) { + +

+ We couldn't find that user category. + Try checking the URL, or pick another category to get your bearings. +

+

+ Otherwise, it may be missing or unavailable temporarily. +

+} diff --git a/src/app/pages/user-listing/user-listing.component.ts b/src/app/pages/user-listing/user-listing.component.ts new file mode 100644 index 00000000..b74e2c1c --- /dev/null +++ b/src/app/pages/user-listing/user-listing.component.ts @@ -0,0 +1,99 @@ +import {Component, OnInit} from '@angular/core'; +import {UserCategory} from "../../api/types/users/user-category"; +import {ClientService, defaultPageSize} from "../../api/client.service"; +import {ActivatedRoute, Params} from "@angular/router"; +import {PageTitleComponent} from "../../components/ui/text/page-title.component"; + + +import {ResponsiveGridComponent} from "../../components/ui/responsive-grid.component"; +import {User} from "../../api/types/users/user"; +import {UserPreviewComponent} from "../../components/items/user-preview.component"; +import {ContainerComponent} from "../../components/ui/container.component"; +import {Scrollable} from "../../helpers/scrollable"; +import {defaultListInfo, RefreshApiListInfo} from "../../api/refresh-api-list-info"; +import {InfiniteScrollerComponent} from "../../components/ui/infinite-scroller.component"; +import {EmbedService} from "../../services/embed.service"; + +@Component({ + selector: 'app-user-listing', + imports: [ + PageTitleComponent, + ResponsiveGridComponent, + UserPreviewComponent, + ContainerComponent, + InfiniteScrollerComponent +], + templateUrl: './user-listing.component.html' +}) +export class UserListingComponent implements OnInit, Scrollable { + category: UserCategory | null | undefined = undefined; + users: User[] = []; + + private queryParams: Params = {}; + + constructor(private client: ClientService, private embed: EmbedService, private route: ActivatedRoute) { + // Start requesting category information immediately. + this.client.getUserCategories().subscribe(); + } + + ngOnInit(): void { + this.route.paramMap.subscribe(params => { + const route: string | null = params.get('category'); + + if(route == null) { + this.category = null; + return; + } + + this.route.queryParams.subscribe((params: Params) => { + this.queryParams = params; + this.setCategoryByRoute(route); + }); + }) + } + + setCategoryByRoute(route: string) { + // clear out any bad state left by previous pages + this.reset(); + + this.client.getUserCategories().subscribe(list => { + for (let category of list.data) { + if (category.apiRoute != route) continue; + + this.category = category; + } + + // if we're still here without a category, set as null (marker for not found) + if(this.category === undefined) + this.category = null; + + if(this.category) { + this.loadData(); + this.embed.embedUserCategory(this.category) + } else { + console.warn("No category found for route " + route) + } + }); + } + + isLoading: boolean = false; + listInfo: RefreshApiListInfo = defaultListInfo; + + loadData(): void { + if(!this.category) return; + + this.isLoading = true; + this.client.getUsersInCategory(this.category.apiRoute, this.listInfo.nextPageIndex, defaultPageSize, this.queryParams).subscribe(list => { + this.isLoading = false; + + this.users = this.users.concat(list.data); + this.listInfo = list.listInfo; + }); + } + + reset(): void { + this.users = []; + this.isLoading = false; + this.listInfo = defaultListInfo; + } +} diff --git a/src/app/services/embed.service.ts b/src/app/services/embed.service.ts index 25aeb3bb..441d46b2 100644 --- a/src/app/services/embed.service.ts +++ b/src/app/services/embed.service.ts @@ -8,6 +8,7 @@ import {Photo} from "../api/types/photos/photo"; import {getImageLink} from "../helpers/data-fetching"; import {TitleService} from "./title.service"; import {LevelCategory} from "../api/types/levels/level-category"; +import {UserCategory} from "../api/types/users/user-category"; @Injectable({providedIn: 'root'}) export class EmbedService { @@ -93,7 +94,11 @@ export class EmbedService { this.setNamedTag("twitter:image", getImageLink(photo.largeHash)); } - embedCategory(category: LevelCategory) { + embedLevelCategory(category: LevelCategory) { + this.embed(category.name, category.description); + } + + embedUserCategory(category: UserCategory) { this.embed(category.name, category.description); } } From 02e9223eefee0d248f01be41a2e517294eb8e859 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Fri, 13 Feb 2026 15:36:18 -0500 Subject: [PATCH 2/4] Implement pages and routes for user listing --- package-lock.json | 185 ------------------ src/app/api/client.service.ts | 4 +- .../category.ts} | 4 +- .../api/types/categories/level-category.ts | 6 + src/app/api/types/categories/user-category.ts | 6 + src/app/api/types/levels/level-category.ts | 13 -- .../items/level-category.component.ts | 2 +- .../items/user-category.component.ts | 2 +- .../categories/level-categories.component.ts | 2 +- .../categories/user-categories-component.ts | 2 +- .../level-listing/level-listing.component.ts | 2 +- .../user-listing/user-listing.component.ts | 2 +- src/app/services/embed.service.ts | 4 +- 13 files changed, 23 insertions(+), 211 deletions(-) rename src/app/api/types/{users/user-category.ts => categories/category.ts} (70%) create mode 100644 src/app/api/types/categories/level-category.ts create mode 100644 src/app/api/types/categories/user-category.ts delete mode 100644 src/app/api/types/levels/level-category.ts diff --git a/package-lock.json b/package-lock.json index 4870b794..e4f9bec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,40 +125,6 @@ } } }, - "node_modules/@angular-devkit/architect/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular-devkit/architect/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/architect/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -420,24 +386,6 @@ "postcss": "^8.1.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -474,22 +422,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -608,40 +540,6 @@ } } }, - "node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -966,40 +864,6 @@ } } }, - "node_modules/@angular/cli/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@angular/cli/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@angular/cli/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -5270,40 +5134,6 @@ } } }, - "node_modules/@schematics/angular/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@schematics/angular/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@schematics/angular/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -13613,21 +13443,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/src/app/api/client.service.ts b/src/app/api/client.service.ts index fde962da..3123a8e4 100644 --- a/src/app/api/client.service.ts +++ b/src/app/api/client.service.ts @@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {Instance} from "./types/instance"; import {LazySubject} from "../helpers/lazy-subject"; -import {LevelCategory} from "./types/levels/level-category"; +import {LevelCategory} from "./types/categories/level-category"; import {Room} from "./types/rooms/room"; import {Level} from "./types/levels/level"; import {ListWithData} from "./list-with-data"; @@ -16,7 +16,7 @@ import {Contest} from "./types/contests/contest"; import {Score} from "./types/levels/score"; import { LevelRelations } from './types/levels/level-relations'; import { Asset } from './types/asset'; -import {UserCategory} from "./types/users/user-category"; +import {UserCategory} from "./types/categories/user-category"; export const defaultPageSize: number = 40; diff --git a/src/app/api/types/users/user-category.ts b/src/app/api/types/categories/category.ts similarity index 70% rename from src/app/api/types/users/user-category.ts rename to src/app/api/types/categories/category.ts index 94f508f6..3f1e2139 100644 --- a/src/app/api/types/users/user-category.ts +++ b/src/app/api/types/categories/category.ts @@ -1,13 +1,11 @@ -import {User} from "./user"; import {IconName} from "@fortawesome/free-solid-svg-icons"; -export interface UserCategory { +export interface Category { apiRoute: string; requiresUser: boolean; name: string; description: string; iconHash: string; fontAwesomeIcon: IconName; - previewUser: User | undefined; hidden: boolean; } \ No newline at end of file diff --git a/src/app/api/types/categories/level-category.ts b/src/app/api/types/categories/level-category.ts new file mode 100644 index 00000000..6f723d75 --- /dev/null +++ b/src/app/api/types/categories/level-category.ts @@ -0,0 +1,6 @@ +import {Category} from "./category"; +import {Level} from "../levels/level"; + +export interface LevelCategory extends Category { + previewLevel: Level | undefined; +} diff --git a/src/app/api/types/categories/user-category.ts b/src/app/api/types/categories/user-category.ts new file mode 100644 index 00000000..b22cbacc --- /dev/null +++ b/src/app/api/types/categories/user-category.ts @@ -0,0 +1,6 @@ +import {Category} from "./category"; +import {User} from "../users/user"; + +export interface UserCategory extends Category { + previewUser: User | undefined; +} \ No newline at end of file diff --git a/src/app/api/types/levels/level-category.ts b/src/app/api/types/levels/level-category.ts deleted file mode 100644 index 1fd11a44..00000000 --- a/src/app/api/types/levels/level-category.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Level} from "./level"; -import {IconName} from "@fortawesome/free-solid-svg-icons"; - -export interface LevelCategory { - apiRoute: string; - requiresUser: boolean; - name: string; - description: string; - iconHash: string; - fontAwesomeIcon: IconName; - previewLevel: Level | undefined; - hidden: boolean; -} diff --git a/src/app/components/items/level-category.component.ts b/src/app/components/items/level-category.component.ts index 944e8ccb..3ec8e65b 100644 --- a/src/app/components/items/level-category.component.ts +++ b/src/app/components/items/level-category.component.ts @@ -1,5 +1,5 @@ import {Component, Input} from '@angular/core'; -import {LevelCategory} from "../../api/types/levels/level-category"; +import {LevelCategory} from "../../api/types/categories/level-category"; import {ContainerComponent} from "../ui/container.component"; import {RouterLink} from "@angular/router"; import {FaIconComponent} from "@fortawesome/angular-fontawesome"; diff --git a/src/app/components/items/user-category.component.ts b/src/app/components/items/user-category.component.ts index 4e590d2b..adb6a60a 100644 --- a/src/app/components/items/user-category.component.ts +++ b/src/app/components/items/user-category.component.ts @@ -1,5 +1,5 @@ import {Component, Input} from '@angular/core'; -import {UserCategory} from "../../api/types/users/user-category"; +import {UserCategory} from "../../api/types/categories/user-category"; import {ContainerComponent} from "../ui/container.component"; import {RouterLink} from "@angular/router"; import {FaIconComponent} from "@fortawesome/angular-fontawesome"; diff --git a/src/app/pages/categories/level-categories.component.ts b/src/app/pages/categories/level-categories.component.ts index 6f50e82d..ee44b583 100644 --- a/src/app/pages/categories/level-categories.component.ts +++ b/src/app/pages/categories/level-categories.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import {PageTitleComponent} from "../../components/ui/text/page-title.component"; import {ResponsiveGridComponent} from "../../components/ui/responsive-grid.component"; -import {LevelCategory} from "../../api/types/levels/level-category"; +import {LevelCategory} from "../../api/types/categories/level-category"; import {ClientService} from "../../api/client.service"; diff --git a/src/app/pages/categories/user-categories-component.ts b/src/app/pages/categories/user-categories-component.ts index 0fcba589..0839c8bb 100644 --- a/src/app/pages/categories/user-categories-component.ts +++ b/src/app/pages/categories/user-categories-component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import {PageTitleComponent} from "../../components/ui/text/page-title.component"; import {ResponsiveGridComponent} from "../../components/ui/responsive-grid.component"; -import {UserCategory} from "../../api/types/users/user-category"; +import {UserCategory} from "../../api/types/categories/user-category"; import {ClientService} from "../../api/client.service"; diff --git a/src/app/pages/level-listing/level-listing.component.ts b/src/app/pages/level-listing/level-listing.component.ts index 817e43f7..be38b98b 100644 --- a/src/app/pages/level-listing/level-listing.component.ts +++ b/src/app/pages/level-listing/level-listing.component.ts @@ -1,5 +1,5 @@ import {Component, OnInit} from '@angular/core'; -import {LevelCategory} from "../../api/types/levels/level-category"; +import {LevelCategory} from "../../api/types/categories/level-category"; import {ClientService, defaultPageSize} from "../../api/client.service"; import {ActivatedRoute, Params} from "@angular/router"; import {PageTitleComponent} from "../../components/ui/text/page-title.component"; diff --git a/src/app/pages/user-listing/user-listing.component.ts b/src/app/pages/user-listing/user-listing.component.ts index b74e2c1c..ee11120f 100644 --- a/src/app/pages/user-listing/user-listing.component.ts +++ b/src/app/pages/user-listing/user-listing.component.ts @@ -1,5 +1,5 @@ import {Component, OnInit} from '@angular/core'; -import {UserCategory} from "../../api/types/users/user-category"; +import {UserCategory} from "../../api/types/categories/user-category"; import {ClientService, defaultPageSize} from "../../api/client.service"; import {ActivatedRoute, Params} from "@angular/router"; import {PageTitleComponent} from "../../components/ui/text/page-title.component"; diff --git a/src/app/services/embed.service.ts b/src/app/services/embed.service.ts index 441d46b2..f6321281 100644 --- a/src/app/services/embed.service.ts +++ b/src/app/services/embed.service.ts @@ -7,8 +7,8 @@ import {ClientService} from "../api/client.service"; import {Photo} from "../api/types/photos/photo"; import {getImageLink} from "../helpers/data-fetching"; import {TitleService} from "./title.service"; -import {LevelCategory} from "../api/types/levels/level-category"; -import {UserCategory} from "../api/types/users/user-category"; +import {LevelCategory} from "../api/types/categories/level-category"; +import {UserCategory} from "../api/types/categories/user-category"; @Injectable({providedIn: 'root'}) export class EmbedService { From 93ac64dc06932c7975082893f3f228202a0dfddc Mon Sep 17 00:00:00 2001 From: FeTetra Date: Fri, 13 Feb 2026 15:58:08 -0500 Subject: [PATCH 3/4] Sync with main --- src/app/api/client.service.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/app/api/client.service.ts b/src/app/api/client.service.ts index 2d7d2bb0..795e7575 100644 --- a/src/app/api/client.service.ts +++ b/src/app/api/client.service.ts @@ -2,7 +2,8 @@ import {Injectable} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {Instance} from "./types/instance"; import {LazySubject} from "../helpers/lazy-subject"; -import {LevelCategory} from "./types/levels/level-category"; +import {LevelCategory} from "./types/categories/level-category"; +import {UserCategory} from "./types/categories/user-category"; import {Room} from "./types/rooms/room"; import {Level} from "./types/levels/level"; import {ListWithData} from "./list-with-data"; @@ -26,7 +27,8 @@ export const defaultPageSize: number = 40; }) export class ClientService extends ApiImplementation { private readonly instance: LazySubject; - private readonly categories: LazySubject>; + private readonly levelCategories: LazySubject>; + private readonly userCategories: LazySubject>; private statistics: LazySubject; private usersCache: User[] = []; @@ -36,7 +38,8 @@ export class ClientService extends ApiImplementation { this.instance = new LazySubject(() => this.http.get("/instance")); this.instance.tryLoad(); - this.categories = new LazySubject>(() => this.http.get>("/levels?includePreviews=true")); + this.levelCategories = new LazySubject>(() => this.http.get>("/levels?includePreviews=true")); + this.userCategories = new LazySubject>(() => this.http.get>("/users?includePreviews=true")); this.statistics = this.getStatisticsInternal(); } @@ -46,7 +49,7 @@ export class ClientService extends ApiImplementation { } getLevelCategories() { - return this.categories.asObservable(); + return this.levelCategories.asObservable(); } private getStatisticsInternal() { @@ -120,6 +123,14 @@ export class ClientService extends ApiImplementation { else return this.getUserByUuid(uuid!) } + getUserCategories() { + return this.userCategories.asObservable(); + } + + getUsersInCategory(category: string, skip: number = 0, count: number = defaultPageSize, params: Params | null = null) { + return this.http.get>(`/users/${category}`, {params: this.setPageQuery(params, skip, count)}); + } + getActivityPage(skip: number = 0, count: number = defaultPageSize) { return this.http.get(`/activity`, {params: this.createPageQuery(skip, count)}); } From 2a97303b62800666421dd5704bbeee092aac35f8 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Fri, 13 Feb 2026 19:20:14 -0500 Subject: [PATCH 4/4] Fix user category previews --- src/app/api/types/categories/user-category.ts | 2 +- .../items/user-category.component.ts | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app/api/types/categories/user-category.ts b/src/app/api/types/categories/user-category.ts index b22cbacc..87ae2a39 100644 --- a/src/app/api/types/categories/user-category.ts +++ b/src/app/api/types/categories/user-category.ts @@ -2,5 +2,5 @@ import {Category} from "./category"; import {User} from "../users/user"; export interface UserCategory extends Category { - previewUser: User | undefined; + previewItem: User | undefined; } \ No newline at end of file diff --git a/src/app/components/items/user-category.component.ts b/src/app/components/items/user-category.component.ts index adb6a60a..0e6f426b 100644 --- a/src/app/components/items/user-category.component.ts +++ b/src/app/components/items/user-category.component.ts @@ -20,21 +20,21 @@ import {UserPreviewComponent} from "./user-preview.component"; UserPreviewComponent ], template: ` - - - - - {{category.name}} - - - -

{{category.description}}

- - @if (category.previewUser) { - - - } -
+ + + + + {{ category.name }} + + + +

{{ category.description }}

+ + @if (category.previewItem) { + + + } +
` }) export class UserCategoryComponent {