5 Commits

Author SHA1 Message Date
443887ea93 Chore: Bump manifests for v1.2.1 2026-05-09 17:31:28 -04:00
e4bfe13072 Feat: Add gradient fade on truncated tag rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 21:30:18 +00:00
0a76bb03b5 Fix: Prevent partial second tag row on image cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 21:27:39 +00:00
8cbf1e527a 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>
2026-05-09 21:24:44 +00:00
a280d8c761 Chore: Bump manifests for v1.2.0 release 2026-05-09 17:10:03 -04:00
4 changed files with 26 additions and 11 deletions

View File

@@ -15,7 +15,7 @@ spec:
spec:
initContainers:
- name: migrate
image: git.juggalol.com/juggalol/reactbin-api:v1.1.2
image: git.juggalol.com/juggalol/reactbin-api:v1.2.1
command: ["alembic", "upgrade", "head"]
workingDir: /app
envFrom:
@@ -26,7 +26,7 @@ spec:
runAsUser: 1001
containers:
- name: api
image: git.juggalol.com/juggalol/reactbin-api:v1.1.2
image: git.juggalol.com/juggalol/reactbin-api:v1.2.1
ports:
- containerPort: 8000
envFrom:

View File

@@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: ui
image: git.juggalol.com/juggalol/reactbin-ui:v1.1.2
image: git.juggalol.com/juggalol/reactbin-ui:v1.2.1
ports:
- containerPort: 8080
livenessProbe:

View File

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

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/core';
import { CommonModule } from '@angular/common';
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 { ImageRecord, ImageService } from '../services/image.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: `
<div class="library">
<header>
<h1>Reactbin</h1>
<h1><a class="home-link" (click)="router.navigate(['/'])">Reactbin</a></h1>
<div class="header-actions">
<a routerLink="/tags" class="tags-link">Browse tags</a>
<button class="upload-btn" (click)="router.navigate(['/upload'])">Upload</button>
@@ -118,7 +118,9 @@ 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 img { width: 100%; height: 160px; object-fit: cover; display: block; }
.card-skeleton { height: 200px; }
.tag-row { padding: 6px; display: flex; flex-wrap: wrap; gap: 4px; }
.tag-row { padding: 6px; display: flex; flex-wrap: nowrap; gap: 4px; overflow: hidden; position: relative; }
.tag-row::after { content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 2rem; background: linear-gradient(to right, transparent, var(--surface)); pointer-events: none; }
.home-link { color: inherit; text-decoration: none; cursor: pointer; }
.empty-state { text-align: center; padding: 60px 0; color: var(--text-muted); }
.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; }
@@ -166,6 +168,21 @@ export class LibraryComponent implements OnInit {
this.currentPage = Math.max(1, parseInt(pageParam, 10) || 1);
}
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) => {
if (q) {
this.tagService.list(q, 10).subscribe((r) => {