Merge pull request #444 from PR0M3TH3AN/codex/refactor-torrent-layout-to-use-tailwind

Refactor beacon torrent UI to design system
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-11 15:03:36 -04:00
commit 87d8dd7411
5 files changed
+389 -191

No files matched your search

+13
View File
@@ -229,6 +229,19 @@ We continue to expose aliases such as `.profile-switcher` and nested variations
- Modal templates across the app now mount `.bv-modal`, `.bv-modal-backdrop`, and `.bv-modal__panel` primitives directly. Controllers such as `ProfileModalController` and the upload modal orchestrator were updated to expect these primitives, so any new modal should follow the same structure for consistent accessibility and animation hooks.
- The compatibility shim keeps the `.profile-switcher` selector family alive temporarily so third-party embeds and cached HTML fragments continue to render while operators redeploy updated templates. Keep the aliases until the April 2025 removal window above, then delete the shim and update any last callers to the new `.card`/`.btn-ghost` structure.
## Beacon client migration
The torrent beacon now shares the Tailwind bundle used across bitvid. The standalone `torrent/beacon.html` drops Skeleton/FoA CDNs in favour of `css/tailwind.generated.css`, adopts the `.card`, `.btn`, `.btn-ghost`, and `.input` primitives, and relies on `.bv-stack` for spacing inside Angular partials. Inline SVG icons replace FontAwesome so controls stay lightweight while inheriting tokenised focus and hover states.
Angular templates under `torrent/views/` should follow the refreshed structure:
- Wrap primary sections in `.card` and layer Tailwind utilities for layout (`grid`, `gap-xl`, `overflow-auto`).
- Use `.input`, `.select`, `.btn`, and `.btn-ghost` (with `data-variant="critical"` for destructive actions) instead of legacy `.u-full-width`, `.button`, or `.button-danger` classes.
- Prefer token-backed utilities for spacing and typography (`px-6`, `py-6`, `text-muted`) over ad-hoc CSS.
- Reuse the `.spinner-overlay` markup for async states so overlays stay consistent with the new palette.
When adding new torrent affordances, mirror the existing markup: inline 24×24 SVG strokes at 2px, `.focus-ring` on interactive anchors, and `.bv-stack` for vertical rhythm. This keeps the beacon aligned with the design system without reintroducing the deprecated Skeleton grid.
## When to Mix Utilities
Use the primitives for foundation and lean on Tailwind utilities for:
+1 -1
View File
@@ -36,7 +36,7 @@ const app = angular.module("BTorrent", [
const styleEl = document.createElement("style");
styleEl.textContent = `
.ui-grid-row {
border-bottom: 1px solid var(--color-border-subtle, #d1d5db);
border-bottom: 1px solid var(--color-border-subtle);
}
`;
document.head.appendChild(styleEl);
+39 -21
View File
@@ -21,37 +21,34 @@
<script src="bundle.min.js"></script>
<!--<script src="https://cdn.jsdelivr.net/combine/npm/webtorrent/webtorrent.min.js,npm/moment@2,npm/[email protected]/angular.min.js,npm/angular-[email protected]/angular-route.min.js,npm/angular-[email protected]/angular-sanitize.min.js,npm/angular-ui-grid@3/ui-grid.min.js,gh/matowens/ng-notify/dist/ng-notify.min.js,npm/ng-file-[email protected]/dist/ng-file-upload.min.js"></script>-->
<!-- External styles -->
<!-- Local styles -->
<link rel="stylesheet" href="../css/tailwind.generated.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/g/normalize@3,skeleton@2,angular.ng-notify@0.8(ng-notify.min.css)"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/fontawesome/4/css/font-awesome.min.css"
href="https://cdn.jsdelivr.net/gh/matowens/ng-notify/dist/ng-notify.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/angular.ui-grid/3/ui-grid.min.css"
/>
<!-- Local CSS -->
<link rel="stylesheet" href="style.css" />
</head>
<body ng-controller="BTorrentCtrl" ng-cloak="">
<body class="bg-surface font-sans text-text" ng-controller="BTorrentCtrl" ng-cloak="">
<!-- Header with container -->
<header class="header-torrent">
<div class="container">
<div class="logo-container">
<a href="../index.html" aria-label="Return to the bitvid home page">
<img
src="../assets/svg/bitvid-logo-light-mode.svg"
alt="bitvid logo"
class="bitvid-logo"
/>
</a>
</div>
<div class="header-torrent__inner">
<a
class="header-torrent__logo focus-ring"
href="../index.html"
aria-label="Return to the bitvid home page"
>
<img
src="../assets/svg/bitvid-logo-light-mode.svg"
alt="bitvid logo"
class="header-torrent__logo-image"
/>
</a>
</div>
</header>
@@ -60,8 +57,29 @@
<div id="view" ng-view></div>
<!-- Processing Spinner -->
<div class="spinner" ng-show="client.processing">
<i class="fa fa-spinner fa-spin spinner-icon"></i>
<div class="spinner-overlay" ng-show="client.processing">
<svg
class="spinner-overlay__icon"
viewBox="0 0 24 24"
role="img"
aria-hidden="true"
>
<circle
class="spinner-overlay__track"
cx="12"
cy="12"
r="9"
fill="none"
stroke-width="2"
></circle>
<path
class="spinner-overlay__indicator"
d="M21 12a9 9 0 0 0-9-9"
fill="none"
stroke-width="2"
stroke-linecap="round"
></path>
</svg>
</div>
<!-- Local Angular code -->
+68 -35
View File
@@ -1,58 +1,91 @@
@import "../css/tokens.css";
html,
body {
min-height: 100%;
}
body {
margin: 0;
font-family: var(--font-family-sans, system-ui, -apple-system, sans-serif);
background-color: var(--color-surface);
color: var(--color-text);
line-height: 1.5;
height: 100%;
width: 100%;
line-height: var(--line-height-base, 1.5);
}
/* Container used in header & footer */
.container {
width: 95%;
max-width: 95%;
margin: 0 auto;
padding: 1rem;
}
/* Header styling */
.header-torrent {
background-color: var(--color-surface);
margin-bottom: 2rem;
/* No extra horizontal padding so it lines up with .container */
border-bottom: 1px solid var(--color-border-translucent-medium);
}
.logo-container {
.header-torrent__inner {
margin: 0 auto;
max-width: 72rem;
padding: var(--space-lg) var(--space-md);
display: flex;
align-items: center;
}
.bitvid-logo {
height: 6rem;
.header-torrent__logo {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-xs);
border-radius: var(--radius-full);
color: var(--color-text);
text-decoration: none;
transition: color var(--motion-duration-fast, 120ms)
var(--motion-ease-out, ease-out),
background-color var(--motion-duration-fast, 120ms)
var(--motion-ease-out, ease-out);
}
.header-torrent__logo:hover,
.header-torrent__logo:focus-visible {
background-color: var(--color-panel-hover);
color: var(--color-text-strong);
}
.header-torrent__logo-image {
height: 3.5rem;
width: auto;
/* Adjust spacing around the logo if needed */
margin-bottom: 0.5rem;
margin-top: 4rem;
}
/* Footer styling */
.footer-torrent {
border-top: 1px solid var(--color-border-translucent-medium);
padding-top: 2rem;
padding-bottom: 2rem;
margin-top: 2rem;
background-color: var(--color-surface);
text-align: center;
.spinner-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 60;
background-color: var(--color-overlay-muted, rgba(15, 23, 42, 0.35));
backdrop-filter: blur(6px);
}
/* If you want to override the generic footer rules, remove them or unify here:
footer {
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 4rem;
padding-top: 2rem;
.spinner-overlay__icon {
height: 3rem;
width: 3rem;
stroke: var(--color-info-strong);
animation: spinner-rotate
calc(var(--motion-duration-base, 160ms) * 6)
linear
infinite;
}
*/
/* Additional styling remains unchanged... */
.spinner-overlay__track {
stroke: var(--color-border-translucent);
opacity: 0.35;
}
.spinner-overlay__indicator {
stroke: currentColor;
}
@keyframes spinner-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
+268 -134
View File
@@ -1,153 +1,287 @@
<!-- MAIN CONTENT AREA -->
<div class="container">
<div class="mx-auto max-w-5xl px-4 py-6 sm:px-6 lg:px-8 bv-stack">
<!-- Input row for adding or opening torrents -->
<div class="row">
<div class="four columns">
<input
class="u-full-width"
type="text"
placeholder="magnet, hash or http(s) .torrent"
ng-model="torrentInput"
ng-disabled="$root.disabled"
/>
</div>
<section class="card p-6">
<form
class="bv-stack bv-stack--tight md:flex md:flex-row md:items-end md:gap-md"
ng-submit="addMagnet()"
>
<div class="bv-stack bv-stack--tight flex-1">
<label
class="text-xs font-semibold uppercase tracking-wide text-muted-strong"
for="torrentInput"
>
Magnet, hash, or HTTPS .torrent URL
</label>
<input
id="torrentInput"
class="input"
type="text"
placeholder="magnet:?xt=…"
ng-model="torrentInput"
ng-disabled="$root.disabled"
/>
</div>
<div class="two columns download-button">
<!-- "Download" button for adding a magnet/torrent -->
<button
ng-click="addMagnet()"
ng-disabled="!torrentInput.length || $root.disabled"
class="button button-danger"
>
<i class="fa fa-download"></i> Download
</button>
</div>
<div class="flex flex-wrap items-center gap-3 md:justify-end">
<button
type="submit"
class="btn inline-flex items-center gap-2"
ng-disabled="!torrentInput.length || $root.disabled"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 3v14"></path>
<path d="m6 11 6 6 6-6"></path>
<path d="M19 21H5"></path>
</svg>
Download
</button>
<div class="three columns"></div>
<div class="three columns u-pull-right">
<!-- "Seed files" button -->
<button
class="button button-danger u-pull-right"
ngf-select="$root.seedFiles($files)"
multiple="true"
ng-disabled="$root.disabled"
>
<i class="fa fa-upload"></i> Seed files
</button>
</div>
</div>
<button
type="button"
class="btn-ghost inline-flex items-center gap-2"
ngf-select="$root.seedFiles($files)"
multiple="true"
ng-disabled="$root.disabled"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 21V7"></path>
<path d="m6 13 6-6 6 6"></path>
<path d="M5 21h14"></path>
</svg>
Seed files
</button>
</div>
</form>
</section>
<!-- ui-grid for active torrents (Name, Progress, Speeds, Peers, Ratio) -->
<div
class="row grid"
<section
class="card overflow-hidden"
ui-grid="gridOptions"
ui-grid-resize-columns="ui-grid-resize-columns"
ui-grid-selection="ui-grid-selection"
></div>
aria-label="Active torrents"
></section>
<!-- Selected torrent details: Pause/Resume, Download, Remove -->
<div class="row" ng-if="selectedTorrent">
<div class="six columns" style="overflow: auto">
<h5>
{{ selectedTorrent.name }}
<section class="card p-6" ng-if="selectedTorrent">
<div class="grid gap-xl lg:grid-cols-2">
<div class="bv-stack bv-stack--tight overflow-auto">
<div class="bv-stack bv-stack--tight">
<h2 class="text-lg font-semibold text-text">
{{ selectedTorrent.name }}
</h2>
<!-- Pause/Resume buttons -->
<button
ng-if="!selectedTorrent.paused"
ng-click="selectedTorrent.pause()"
>
<i class="fa fa-pause"></i> Pause
</button>
<button
ng-if="selectedTorrent.paused"
ng-click="selectedTorrent.resume()"
>
<i class="fa fa-play"></i> Resume
</button>
<!-- Download ALL files (only if progress == 1) -->
<button
class="button button-danger"
ng-click="$root.downloadAll(selectedTorrent)"
ng-disabled="selectedTorrent.progress < 1"
>
<i class="fa fa-download"></i> Download
</button>
<!-- Remove button -->
<button
class="button button-danger"
ng-click="selectedTorrent.destroy($root.destroyedTorrent)"
>
<i class="fa fa-times"></i> Remove
</button>
</h5>
<h6>Share</h6>
<ul>
<li>
<!-- Copy magnet button -->
<button
class="button button-small"
ng-click="$root.copyMagnetURI(selectedTorrent)"
>
Copy Magnet
</button>
</li>
<li><strong>Hash:</strong> {{ selectedTorrent.infoHash }}</li>
</ul>
</div>
<div class="six columns">
<h5>Files</h5>
<table class="u-full-width">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
<tr class="files" ng-repeat="file in selectedTorrent.files">
<!-- If file isn't finished, just show the name. -->
<td ng-hide="file.done">{{ file.name }}</td>
<!-- If file is done, show direct link to download it. -->
<td ng-show="file.done">
<a ng-href="{{ file.url }}" download="{{ file.name }}">
{{ file.name }}
</a>
</td>
<td>{{ file.length | pbytes }}</td>
<td>
<select
class="no-margin"
name="{{ file.name }}Priority"
ng-model="file.priority"
ng-init="file.priority = '0'"
ng-change="$root.changePriority(file)"
<div class="flex flex-wrap gap-2">
<button
type="button"
class="btn-ghost inline-flex items-center gap-2"
ng-if="!selectedTorrent.paused"
ng-click="selectedTorrent.pause()"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<option value="1">High Priority</option>
<option value="0" selected="">Low Priority</option>
<option value="-1">Don't download</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<rect x="6" y="4" width="4" height="16"></rect>
<rect x="14" y="4" width="4" height="16"></rect>
</svg>
Pause
</button>
<div class="center">
<strong>
Client Stats: ↓ {{ client.downloadSpeed | pbytes }}/s · ↑ {{
client.uploadSpeed | pbytes }}/s · Ratio: {{ client.ratio | number:2 }}
</strong>
<button
type="button"
class="btn-ghost inline-flex items-center gap-2"
ng-if="selectedTorrent.paused"
ng-click="selectedTorrent.resume()"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<polygon points="6 4 20 12 6 20 6 4"></polygon>
</svg>
Resume
</button>
<button
type="button"
class="btn inline-flex items-center gap-2"
ng-click="$root.downloadAll(selectedTorrent)"
ng-disabled="selectedTorrent.progress < 1"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 3v14"></path>
<path d="m6 11 6 6 6-6"></path>
</svg>
Download all
</button>
<button
type="button"
class="btn-ghost inline-flex items-center gap-2"
data-variant="critical"
ng-click="selectedTorrent.destroy($root.destroyedTorrent)"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M18 6L6 18"></path>
<path d="M6 6l12 12"></path>
</svg>
Remove
</button>
</div>
</div>
<div class="bv-stack bv-stack--tight">
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted">
Share
</h3>
<ul class="bv-stack bv-stack--tight text-sm text-muted-strong">
<li>
<button
type="button"
class="btn-ghost inline-flex items-center gap-2 text-xs"
ng-click="$root.copyMagnetURI(selectedTorrent)"
>
Copy magnet
</button>
</li>
<li>
<strong class="font-semibold text-text">Hash:</strong>
{{ selectedTorrent.infoHash }}
</li>
</ul>
</div>
</div>
<div class="bv-stack bv-stack--tight">
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted">
Files
</h3>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border-translucent text-sm">
<thead class="bg-surface-alt text-muted-strong">
<tr>
<th class="px-3 py-2 text-left font-medium">Name</th>
<th class="px-3 py-2 text-left font-medium">Size</th>
<th class="px-3 py-2 text-left font-medium">Priority</th>
</tr>
</thead>
<tbody class="divide-y divide-border-translucent bg-surface">
<tr class="align-top" ng-repeat="file in selectedTorrent.files">
<td class="px-3 py-2">
<span ng-hide="file.done">{{ file.name }}</span>
<a
class="text-info hover:text-info-strong"
ng-show="file.done"
ng-href="{{ file.url }}"
download="{{ file.name }}"
>
{{ file.name }}
</a>
</td>
<td class="px-3 py-2 text-muted-strong">
{{ file.length | pbytes }}
</td>
<td class="px-3 py-2">
<select
class="select"
name="{{ file.name }}Priority"
ng-model="file.priority"
ng-init="file.priority = '0'"
ng-change="$root.changePriority(file)"
aria-label="Set priority for {{ file.name }}"
>
<option value="1">High priority</option>
<option value="0" selected="">Low priority</option>
<option value="-1">Don't download</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<div class="text-center text-sm font-semibold text-muted">
Client stats: ↓ {{ client.downloadSpeed | pbytes }}/s · ↑ {{
client.uploadSpeed | pbytes }}/s · Ratio: {{ client.ratio | number:2 }}
</div>
</div>
<!-- Processing Spinner -->
<div class="spinner" ng-show="client.processing">
<i class="fa fa-spinner fa-spin spinner-icon"></i>
<div class="spinner-overlay" ng-show="client.processing">
<svg
class="spinner-overlay__icon"
viewBox="0 0 24 24"
role="img"
aria-hidden="true"
>
<circle
class="spinner-overlay__track"
cx="12"
cy="12"
r="9"
fill="none"
stroke-width="2"
></circle>
<path
class="spinner-overlay__indicator"
d="M21 12a9 9 0 0 0-9-9"
fill="none"
stroke-width="2"
stroke-linecap="round"
></path>
</svg>
</div>