Enhance mobile responsiveness of torrent table component

This commit is contained in:
Sylvain DUARTE 2026-03-14 18:24:31 +01:00
parent 004ed45144
commit 0d6369750f
No known key found for this signature in database
3 changed files with 104 additions and 99 deletions

View file

@ -21,97 +21,99 @@
</div> </div>
} }
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-hoverable desktop-table"> @if (!isMobile) {
<thead> <table class="table is-fullwidth is-hoverable">
<tr> <thead>
<th>
<input
type="checkbox"
(click)="toggleDeleteSelectAll($event)"
[checked]="selectedTorrents.length > 0 && selectedTorrents.length === torrents.length"
/>
</th>
<th (click)="sort('rdName')">Name</th>
<th (click)="sort('category')">Category</th>
<th (click)="sort('priority')">Priority</th>
<th (click)="sort('rdSeeders')">Seeders</th>
<th (click)="sort('files.length')">Files</th>
<th (click)="sort('downloads.length')">Downloads</th>
<th (click)="sort('rdSize')">Size</th>
<th (click)="sort('added')">Requested</th>
<th (click)="sort('status')">Status</th>
</tr>
</thead>
<tbody>
@for (torrent of torrents | sort: sortProperty : sortDirection; track torrent.torrentId) {
<tr> <tr>
<td> <th>
<input
type="checkbox"
(click)="toggleDeleteSelectAll($event)"
[checked]="selectedTorrents.length > 0 && selectedTorrents.length === torrents.length"
/>
</th>
<th (click)="sort('rdName')">Name</th>
<th (click)="sort('category')">Category</th>
<th (click)="sort('priority')">Priority</th>
<th (click)="sort('rdSeeders')">Seeders</th>
<th (click)="sort('files.length')">Files</th>
<th (click)="sort('downloads.length')">Downloads</th>
<th (click)="sort('rdSize')">Size</th>
<th (click)="sort('added')">Requested</th>
<th (click)="sort('status')">Status</th>
</tr>
</thead>
<tbody>
@for (torrent of torrents | sort: sortProperty : sortDirection; track torrent.torrentId) {
<tr>
<td>
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.includes(torrent.torrentId)"
/>
</td>
<td (click)="openTorrent(torrent.torrentId)" class="break-all">
{{ torrent.rdName }}
</td>
<td>
{{ torrent.category }}
</td>
<td>
{{ torrent.priority }}
</td>
<td>
{{ torrent.rdSeeders }}
</td>
<td>
{{ torrent.files.length | number }}
</td>
<td>
{{ torrent.downloads.length | number }}
</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent.added | date: "medium" }}
</td>
<td>
{{ torrent | status }}
</td>
</tr>
}
</tbody>
</table>
} @else {
<div class="mobile-cards">
@for (torrent of torrents | sort: sortProperty : sortDirection; track torrent.torrentId) {
<div class="box mobile-card" [class.is-selected]="selectedTorrents.includes(torrent.torrentId)">
<div class="mobile-card-checkbox">
<input <input
type="checkbox" type="checkbox"
(click)="toggleSelect(torrent.torrentId)" (click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.includes(torrent.torrentId)" [checked]="selectedTorrents.includes(torrent.torrentId)"
/> />
</td> </div>
<td (click)="openTorrent(torrent.torrentId)" class="break-all"> <div class="mobile-card-body" (click)="openTorrent(torrent.torrentId)">
{{ torrent.rdName }} <p class="mobile-card-name">{{ torrent.rdName }}</p>
</td> <div class="tags mb-2">
<td> <span class="tag is-info is-light">{{ torrent | status }}</span>
{{ torrent.category }} @if (torrent.category) {
</td> <span class="tag is-light">{{ torrent.category }}</span>
<td> }
{{ torrent.priority }} </div>
</td> <div class="mobile-card-details">
<td> <span class="mobile-card-detail"><span class="detail-label">Size</span> {{ torrent.rdSize | filesize }}</span>
{{ torrent.rdSeeders }} <span class="mobile-card-detail"><span class="detail-label">Seeders</span> {{ torrent.rdSeeders }}</span>
</td> <span class="mobile-card-detail"><span class="detail-label">Files</span> {{ torrent.files.length | number }}</span>
<td> <span class="mobile-card-detail"><span class="detail-label">Downloads</span> {{ torrent.downloads.length | number }}</span>
{{ torrent.files.length | number }} </div>
</td> </div>
<td> </div>
{{ torrent.downloads.length | number }}
</td>
<td>
{{ torrent.rdSize | filesize }}
</td>
<td>
{{ torrent.added | date: "medium" }}
</td>
<td>
{{ torrent | status }}
</td>
</tr>
} }
</tbody> </div>
</table> }
<div class="mobile-cards">
@for (torrent of torrents | sort: sortProperty : sortDirection; track torrent.torrentId) {
<div class="box mobile-card" [class.is-selected]="selectedTorrents.includes(torrent.torrentId)">
<div class="mobile-card-checkbox">
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.includes(torrent.torrentId)"
/>
</div>
<div class="mobile-card-body" (click)="openTorrent(torrent.torrentId)">
<p class="mobile-card-name">{{ torrent.rdName }}</p>
<div class="tags mb-2">
<span class="tag is-info is-light">{{ torrent | status }}</span>
@if (torrent.category) {
<span class="tag is-light">{{ torrent.category }}</span>
}
</div>
<div class="mobile-card-details">
<span class="mobile-card-detail"><span class="detail-label">Size</span> {{ torrent.rdSize | filesize }}</span>
<span class="mobile-card-detail"><span class="detail-label">Seeders</span> {{ torrent.rdSeeders }}</span>
<span class="mobile-card-detail"><span class="detail-label">Files</span> {{ torrent.files.length | number }}</span>
<span class="mobile-card-detail"><span class="detail-label">Downloads</span> {{ torrent.downloads.length | number }}</span>
</div>
</div>
</div>
}
</div>
<div class="flex-container"> <div class="flex-container">
@if (torrents.length > 0) { @if (torrents.length > 0) {

View file

@ -7,20 +7,8 @@ table {
} }
} }
.desktop-table {
display: table;
@media screen and (max-width: 768px) {
display: none;
}
}
.mobile-cards { .mobile-cards {
display: none; display: block;
@media screen and (max-width: 768px) {
display: block;
}
} }
.mobile-card { .mobile-card {

View file

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Torrent } from '../models/torrent.model'; import { Torrent } from '../models/torrent.model';
import { DiskSpaceStatus } from '../models/disk-space-status.model'; import { DiskSpaceStatus } from '../models/disk-space-status.model';
@ -18,7 +18,7 @@ import { FileSizePipe } from '../filesize.pipe';
imports: [FormsModule, NgClass, DecimalPipe, DatePipe, TorrentStatusPipe, SortPipe, FileSizePipe], imports: [FormsModule, NgClass, DecimalPipe, DatePipe, TorrentStatusPipe, SortPipe, FileSizePipe],
standalone: true, standalone: true,
}) })
export class TorrentTableComponent implements OnInit { export class TorrentTableComponent implements OnInit, OnDestroy {
public torrents: Torrent[] = []; public torrents: Torrent[] = [];
public selectedTorrents: string[] = []; public selectedTorrents: string[] = [];
public error: string; public error: string;
@ -53,12 +53,23 @@ export class TorrentTableComponent implements OnInit {
public diskSpaceStatus: DiskSpaceStatus | null = null; public diskSpaceStatus: DiskSpaceStatus | null = null;
public rateLimitStatus: RateLimitStatus | null = null; public rateLimitStatus: RateLimitStatus | null = null;
public isMobile = false;
private mobileQuery: MediaQueryList;
private mobileQueryListener: (e: MediaQueryListEvent) => void;
constructor( constructor(
private router: Router, private router: Router,
private torrentService: TorrentService, private torrentService: TorrentService,
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.mobileQuery = window.matchMedia('(max-width: 768px)');
this.isMobile = this.mobileQuery.matches;
this.mobileQueryListener = (e: MediaQueryListEvent) => {
this.isMobile = e.matches;
};
this.mobileQuery.addEventListener('change', this.mobileQueryListener);
// Load persisted sort settings (if any) // Load persisted sort settings (if any)
try { try {
const sp = localStorage.getItem('torrentTable.sortProperty'); const sp = localStorage.getItem('torrentTable.sortProperty');
@ -320,4 +331,8 @@ export class TorrentTableComponent implements OnInit {
updateDeleteSelectAll() { updateDeleteSelectAll() {
this.deleteSelectAll = this.deleteData && this.deleteRdTorrent && this.deleteLocalFiles; this.deleteSelectAll = this.deleteData && this.deleteRdTorrent && this.deleteLocalFiles;
} }
ngOnDestroy(): void {
this.mobileQuery?.removeEventListener('change', this.mobileQueryListener);
}
} }