Add visual online/offline status dot indicator for Rapidgator links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-04 00:23:20 +01:00
parent 662c903bf3
commit 5574b50d20
5 changed files with 39 additions and 5 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.5.83", "version": "1.5.84",
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -1527,8 +1527,12 @@ export class DownloadManager extends EventEmitter {
for (const itemId of itemIds) { for (const itemId of itemIds) {
const item = this.session.items[itemId]; const item = this.session.items[itemId];
if (!item || item.status !== "queued") continue; if (!item || item.status !== "queued") continue;
item.onlineStatus = "checking";
itemsToCheck.push({ itemId, url: item.url }); itemsToCheck.push({ itemId, url: item.url });
} }
if (itemsToCheck.length > 0) {
this.emitState();
}
const uniqueUrls = [...new Set(itemsToCheck.map(i => i.url))]; const uniqueUrls = [...new Set(itemsToCheck.map(i => i.url))];
const concurrency = 4; const concurrency = 4;
@ -1555,16 +1559,15 @@ export class DownloadManager extends EventEmitter {
item.status = "failed"; item.status = "failed";
item.fullStatus = "Offline"; item.fullStatus = "Offline";
item.lastError = "Datei nicht gefunden auf Rapidgator"; item.lastError = "Datei nicht gefunden auf Rapidgator";
item.onlineStatus = "offline";
item.updatedAt = nowMs(); item.updatedAt = nowMs();
changed = true; changed = true;
} else { } else {
if (result.fileName && looksLikeOpaqueFilename(item.fileName)) { if (result.fileName && looksLikeOpaqueFilename(item.fileName)) {
item.fileName = sanitizeFilename(result.fileName); item.fileName = sanitizeFilename(result.fileName);
this.assignItemTargetPath(item, path.join(this.session.packages[item.packageId]?.outputDir || this.settings.outputDir, item.fileName)); this.assignItemTargetPath(item, path.join(this.session.packages[item.packageId]?.outputDir || this.settings.outputDir, item.fileName));
item.updatedAt = nowMs();
changed = true;
} }
item.fullStatus = "Online"; item.onlineStatus = "online";
item.updatedAt = nowMs(); item.updatedAt = nowMs();
changed = true; changed = true;
} }

View File

@ -3029,7 +3029,10 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
</div> </div>
{!collapsed && items.map((item) => ( {!collapsed && items.map((item) => (
<div key={item.id} className={`item-row${selectedIds.has(item.id) ? " item-selected" : ""}`} onClick={(e) => { e.stopPropagation(); onSelect(item.id, e.ctrlKey); }} onMouseDown={(e) => { e.stopPropagation(); onSelectMouseDown(item.id, e); }} onMouseEnter={() => onSelectMouseEnter(item.id)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, item.id, e.clientX, e.clientY); }}> <div key={item.id} className={`item-row${selectedIds.has(item.id) ? " item-selected" : ""}`} onClick={(e) => { e.stopPropagation(); onSelect(item.id, e.ctrlKey); }} onMouseDown={(e) => { e.stopPropagation(); onSelectMouseDown(item.id, e); }} onMouseEnter={() => onSelectMouseEnter(item.id)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, item.id, e.clientX, e.clientY); }}>
<span className="pkg-col pkg-col-name item-indent" title={item.fileName}>{item.fileName}</span> <span className="pkg-col pkg-col-name item-indent" title={item.fileName}>
{item.onlineStatus && <span className={`link-status-dot ${item.onlineStatus}`} title={item.onlineStatus === "online" ? "Online" : item.onlineStatus === "offline" ? "Offline" : "Wird geprüft..."} />}
{item.fileName}
</span>
<span className="pkg-col pkg-col-size">{(() => { <span className="pkg-col pkg-col-size">{(() => {
const total = item.totalBytes || item.downloadedBytes || 0; const total = item.totalBytes || item.downloadedBytes || 0;
const dl = item.downloadedBytes || 0; const dl = item.downloadedBytes || 0;

View File

@ -1315,6 +1315,33 @@ td {
padding-left: 32px; padding-left: 32px;
} }
.link-status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
flex-shrink: 0;
vertical-align: middle;
}
.link-status-dot.online {
background: #22c55e;
box-shadow: 0 0 4px #22c55e80;
}
.link-status-dot.offline {
background: #ef4444;
box-shadow: 0 0 4px #ef444480;
}
.link-status-dot.checking {
background: #f59e0b;
box-shadow: 0 0 4px #f59e0b80;
animation: pulse-dot 1s ease-in-out infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.item-remove { .item-remove {
background: none; background: none;
border: none; border: none;

View File

@ -101,6 +101,7 @@ export interface DownloadItem {
fullStatus: string; fullStatus: string;
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
onlineStatus?: "online" | "offline" | "checking";
} }
export interface PackageEntry { export interface PackageEntry {