Fix: React to external URL changes and cap tag-row height in library

Clicking the Reactbin home link (or any navigation to / that removes
?page=) now resets the displayed page by subscribing to queryParamMap
for post-init URL changes. Cards with many tags no longer push the
pagination bar down since the tag row is clamped to one line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 21:24:44 +00:00
parent a280d8c761
commit 8cbf1e527a
2 changed files with 22 additions and 8 deletions

View File

@@ -8,12 +8,10 @@ import { ImageService } from '../services/image.service';
import { routes } from '../app.routes'; import { routes } from '../app.routes';
function makeActivatedRoute(queryParams: Record<string, string> = {}) { function makeActivatedRoute(queryParams: Record<string, string> = {}) {
const paramMap = { get: (key: string) => queryParams[key] ?? null };
return { return {
snapshot: { snapshot: { queryParamMap: paramMap },
queryParamMap: { queryParamMap: of(paramMap),
get: (key: string) => queryParams[key] ?? null,
},
},
}; };
} }

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Router, RouterLink, ActivatedRoute } from '@angular/router'; import { Router, RouterLink, ActivatedRoute } from '@angular/router';
import { Subject, debounceTime, distinctUntilChanged, share, timer } from 'rxjs'; import { Subject, debounceTime, distinctUntilChanged, share, skip, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { ImageRecord, ImageService } from '../services/image.service'; import { ImageRecord, ImageService } from '../services/image.service';
import { TagService } from '../services/tag.service'; import { TagService } from '../services/tag.service';
@@ -21,7 +21,7 @@ const PLACEHOLDER_SVG = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/s
template: ` template: `
<div class="library"> <div class="library">
<header> <header>
<h1>Reactbin</h1> <h1><a class="home-link" (click)="router.navigate(['/'])">Reactbin</a></h1>
<div class="header-actions"> <div class="header-actions">
<a routerLink="/tags" class="tags-link">Browse tags</a> <a routerLink="/tags" class="tags-link">Browse tags</a>
<button class="upload-btn" (click)="router.navigate(['/upload'])">Upload</button> <button class="upload-btn" (click)="router.navigate(['/upload'])">Upload</button>
@@ -118,7 +118,8 @@ const PLACEHOLDER_SVG = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/s
.image-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.4); } .image-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.4); }
.image-card img { width: 100%; height: 160px; object-fit: cover; display: block; } .image-card img { width: 100%; height: 160px; object-fit: cover; display: block; }
.card-skeleton { height: 200px; } .card-skeleton { height: 200px; }
.tag-row { padding: 6px; display: flex; flex-wrap: wrap; gap: 4px; } .tag-row { padding: 6px; display: flex; flex-wrap: wrap; gap: 4px; overflow: hidden; max-height: 2rem; }
.home-link { color: inherit; text-decoration: none; cursor: pointer; }
.empty-state { text-align: center; padding: 60px 0; color: var(--text-muted); } .empty-state { text-align: center; padding: 60px 0; color: var(--text-muted); }
.empty-icon { display: block; font-size: 2rem; margin-bottom: 12px; } .empty-icon { display: block; font-size: 2rem; margin-bottom: 12px; }
.upload-link { display: inline-block; margin-top: 16px; color: var(--accent); text-decoration: none; font-weight: 600; } .upload-link { display: inline-block; margin-top: 16px; color: var(--accent); text-decoration: none; font-weight: 600; }
@@ -166,6 +167,21 @@ export class LibraryComponent implements OnInit {
this.currentPage = Math.max(1, parseInt(pageParam, 10) || 1); this.currentPage = Math.max(1, parseInt(pageParam, 10) || 1);
} }
this.load(); this.load();
this.route.queryParamMap.pipe(skip(1)).subscribe((params) => {
const newPage = params.get('page') ? Math.max(1, parseInt(params.get('page')!, 10) || 1) : 1;
const newTagsParam = params.get('tags');
const newTags = newTagsParam
? newTagsParam.split(',').map((t) => t.trim()).filter((t) => t.length > 0)
: [];
const pageChanged = newPage !== this.currentPage;
const tagsChanged = JSON.stringify(newTags) !== JSON.stringify(this.activeFilters);
if (pageChanged || tagsChanged) {
this.currentPage = newPage;
this.activeFilters = newTags;
this.images = [];
this.load();
}
});
this.filterChange$.pipe(debounceTime(300), distinctUntilChanged()).subscribe((q) => { this.filterChange$.pipe(debounceTime(300), distinctUntilChanged()).subscribe((q) => {
if (q) { if (q) {
this.tagService.list(q, 10).subscribe((r) => { this.tagService.list(q, 10).subscribe((r) => {