mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-09 07:28:44 +00:00
updated directory structure to fix root Service Worker issues and performance improvements
This commit is contained in:
415
torrent/app.js
Normal file
415
torrent/app.js
Normal file
@@ -0,0 +1,415 @@
|
||||
/* global WebTorrent, angular, moment */
|
||||
|
||||
const VERSION = "1.1";
|
||||
|
||||
// A smaller set of WebSocket trackers for reliability.
|
||||
const trackers = [
|
||||
"wss://tracker.btorrent.xyz",
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
];
|
||||
|
||||
// Basic torrent options.
|
||||
const torrentOpts = { announce: trackers };
|
||||
const trackerOpts = { announce: trackers };
|
||||
|
||||
// Simple debug logger.
|
||||
function dbg(msg) {
|
||||
console.log("[DEBUG]", msg);
|
||||
}
|
||||
|
||||
// Create a WebTorrent client.
|
||||
const client = new WebTorrent({ tracker: trackerOpts });
|
||||
|
||||
// Angular app definition.
|
||||
const app = angular.module("BTorrent", [
|
||||
"ngRoute",
|
||||
"ui.grid",
|
||||
"ui.grid.resizeColumns",
|
||||
"ui.grid.selection",
|
||||
"ngFileUpload",
|
||||
"ngNotify",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Optional: inline CSS for row lines in the grid.
|
||||
*/
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.textContent = `
|
||||
.ui-grid-row {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
// Configure Angular routes.
|
||||
app.config([
|
||||
"$compileProvider",
|
||||
"$locationProvider",
|
||||
"$routeProvider",
|
||||
function ($compileProvider, $locationProvider, $routeProvider) {
|
||||
// Allow magnet, blob, etc. in Angular URLs.
|
||||
$compileProvider.aHrefSanitizationWhitelist(
|
||||
/^\s*(https?|magnet|blob|javascript):/
|
||||
);
|
||||
$locationProvider.html5Mode(false).hashPrefix("");
|
||||
|
||||
// Define basic routes.
|
||||
$routeProvider
|
||||
.when("/view", {
|
||||
templateUrl: "views/view.html",
|
||||
controller: "ViewCtrl",
|
||||
})
|
||||
.when("/download", {
|
||||
templateUrl: "views/download.html",
|
||||
controller: "DownloadCtrl",
|
||||
})
|
||||
.otherwise({
|
||||
templateUrl: "views/full.html",
|
||||
controller: "FullCtrl",
|
||||
});
|
||||
},
|
||||
]);
|
||||
|
||||
// Warn user before they unload if torrents are still active.
|
||||
app.run([
|
||||
"$rootScope",
|
||||
function ($rootScope) {
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if ($rootScope.client && $rootScope.client.torrents.length > 0) {
|
||||
e.preventDefault();
|
||||
e.returnValue =
|
||||
"Transfers are in progress. Are you sure you want to leave or refresh?";
|
||||
return e.returnValue;
|
||||
}
|
||||
});
|
||||
},
|
||||
]);
|
||||
|
||||
// Main BTorrent controller.
|
||||
app.controller("BTorrentCtrl", [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$http",
|
||||
"$log",
|
||||
"ngNotify",
|
||||
function ($scope, $rootScope, $http, $log, ngNotify) {
|
||||
dbg("Starting app.js version " + VERSION);
|
||||
|
||||
if (!WebTorrent.WEBRTC_SUPPORT) {
|
||||
ngNotify.set("Please use a browser with WebRTC support.", "error");
|
||||
}
|
||||
|
||||
$rootScope.client = client;
|
||||
$rootScope.selectedTorrent = null;
|
||||
$rootScope.processing = false;
|
||||
|
||||
// Global error handler.
|
||||
client.on("error", (err) => {
|
||||
dbg("Torrent client error: " + err);
|
||||
ngNotify.set(err.message || err, "error");
|
||||
$rootScope.processing = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Called whenever a new torrent is added or we seed files.
|
||||
*/
|
||||
$rootScope.onTorrent = function (torrent, isSeed) {
|
||||
dbg("Torrent added: " + torrent.magnetURI);
|
||||
$rootScope.processing = false;
|
||||
|
||||
if (!isSeed) {
|
||||
ngNotify.set(`Received ${torrent.name} metadata`);
|
||||
}
|
||||
if (!$rootScope.selectedTorrent) {
|
||||
$rootScope.selectedTorrent = torrent;
|
||||
}
|
||||
|
||||
// Generate file.blobURL for direct downloading in the file table.
|
||||
torrent.files.forEach((file) => {
|
||||
file.getBlobURL((err, url) => {
|
||||
if (!err) {
|
||||
file.url = url;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a magnet link or .torrent URL.
|
||||
*/
|
||||
$rootScope.addMagnet = function (magnet) {
|
||||
if (!magnet) return;
|
||||
$rootScope.processing = true;
|
||||
dbg("Adding magnet: " + magnet);
|
||||
client.add(magnet, torrentOpts, (torrent) => {
|
||||
$rootScope.onTorrent(torrent, false);
|
||||
$scope.$applyAsync();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Seed local files.
|
||||
*/
|
||||
$rootScope.seedFiles = function (files) {
|
||||
if (!files || !files.length) return;
|
||||
$rootScope.processing = true;
|
||||
dbg(`Seeding ${files.length} file(s)`);
|
||||
client.seed(files, torrentOpts, (torrent) => {
|
||||
$rootScope.onTorrent(torrent, true);
|
||||
$scope.$applyAsync();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove/destroy a selected torrent.
|
||||
*/
|
||||
$rootScope.destroyedTorrent = function (err) {
|
||||
if (err) {
|
||||
console.error("Failed to destroy torrent:", err);
|
||||
}
|
||||
dbg("Destroyed torrent", $rootScope.selectedTorrent);
|
||||
$rootScope.selectedTorrent = null;
|
||||
$rootScope.processing = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the priority of an individual file (high, low, or don't download).
|
||||
*/
|
||||
$rootScope.changePriority = function (file) {
|
||||
if (file.priority === "-1") {
|
||||
file.deselect();
|
||||
dbg("Deselected file", file);
|
||||
} else {
|
||||
file.select(file.priority);
|
||||
dbg(`Selected with priority ${file.priority}`, file);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Download all files in the selected torrent if it's 100% done.
|
||||
* Creates a blob for each file, triggers a native download with <a>.
|
||||
*/
|
||||
$rootScope.downloadAll = function (torrent) {
|
||||
if (!torrent) return;
|
||||
if (torrent.progress < 1) {
|
||||
alert("Torrent is not finished downloading yet.");
|
||||
return;
|
||||
}
|
||||
torrent.files.forEach((file) => {
|
||||
file.getBlob((err, blob) => {
|
||||
if (err) {
|
||||
console.error("Failed to get blob for file:", file.name, err);
|
||||
return;
|
||||
}
|
||||
// Create an anchor to trigger the download.
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = blobUrl;
|
||||
a.download = file.name;
|
||||
// Append, click, remove, revoke.
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy the magnet URI of a torrent to the clipboard.
|
||||
*/
|
||||
$rootScope.copyMagnetURI = function (torrent) {
|
||||
if (!torrent || !torrent.magnetURI) return;
|
||||
const magnetURI = torrent.magnetURI;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard
|
||||
.writeText(magnetURI)
|
||||
.then(() =>
|
||||
ngNotify.set("Magnet URI copied to clipboard!", "success")
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error("Clipboard error:", err);
|
||||
ngNotify.set("Failed to copy magnet URI", "error");
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = magnetURI;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-9999px";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textarea);
|
||||
ngNotify.set("Magnet URI copied (fallback)!", "success");
|
||||
} catch (err) {
|
||||
console.error("Clipboard fallback error:", err);
|
||||
ngNotify.set("Failed to copy magnet URI", "error");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the .torrent file itself (via torrent.torrentFileBlobURL).
|
||||
*/
|
||||
$rootScope.saveTorrentFile = function (torrent) {
|
||||
if (!torrent || !torrent.torrentFileBlobURL) return;
|
||||
const fileName = torrent.fileName || `${torrent.name}.torrent`;
|
||||
// Create a hidden <a> to force download of the .torrent file.
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = torrent.torrentFileBlobURL;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
};
|
||||
},
|
||||
]);
|
||||
|
||||
// FullCtrl: sets up the ui-grid and magnet input for /full route.
|
||||
app.controller("FullCtrl", [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$location",
|
||||
"ngNotify",
|
||||
function ($scope, $rootScope, $location, ngNotify) {
|
||||
// Handle magnet input
|
||||
$scope.addMagnet = function () {
|
||||
$rootScope.addMagnet($scope.torrentInput);
|
||||
$scope.torrentInput = "";
|
||||
};
|
||||
|
||||
// If we have a #magnet in the URL, add it automatically
|
||||
if ($location.hash()) {
|
||||
$rootScope.addMagnet($location.hash());
|
||||
}
|
||||
|
||||
// Define columns for ui-grid.
|
||||
$scope.columns = [
|
||||
{ field: "name", displayName: "Name", minWidth: 200 },
|
||||
{
|
||||
field: "progress",
|
||||
displayName: "Progress",
|
||||
cellFilter: "progress",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: "downloadSpeed",
|
||||
displayName: "↓ Speed",
|
||||
cellFilter: "pbytes:1",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: "uploadSpeed",
|
||||
displayName: "↑ Speed",
|
||||
cellFilter: "pbytes:1",
|
||||
width: 100,
|
||||
},
|
||||
{ field: "numPeers", displayName: "Peers", width: 80 },
|
||||
{
|
||||
field: "ratio",
|
||||
displayName: "Ratio",
|
||||
cellFilter: "number:2",
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
|
||||
// Create gridOptions and update each second.
|
||||
$scope.gridOptions = {
|
||||
columnDefs: $scope.columns,
|
||||
enableColumnResizing: true,
|
||||
enableColumnMenus: false,
|
||||
enableRowSelection: true,
|
||||
enableRowHeaderSelection: false,
|
||||
multiSelect: false,
|
||||
data: [],
|
||||
};
|
||||
|
||||
setInterval(() => {
|
||||
$scope.gridOptions.data =
|
||||
($rootScope.client && $rootScope.client.torrents) || [];
|
||||
$scope.$applyAsync();
|
||||
}, 1000);
|
||||
|
||||
// On row selection, set the selectedTorrent
|
||||
$scope.gridOptions.onRegisterApi = function (gridApi) {
|
||||
$scope.gridApi = gridApi;
|
||||
gridApi.selection.on.rowSelectionChanged($scope, function (row) {
|
||||
if (row.isSelected) {
|
||||
$rootScope.selectedTorrent = row.entity;
|
||||
} else {
|
||||
$rootScope.selectedTorrent = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
]);
|
||||
|
||||
// DownloadCtrl / ViewCtrl are minimal in this example.
|
||||
app.controller("DownloadCtrl", [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$location",
|
||||
function ($scope, $rootScope, $location) {
|
||||
$scope.addMagnet = function () {
|
||||
$rootScope.addMagnet($scope.torrentInput);
|
||||
$scope.torrentInput = "";
|
||||
};
|
||||
if ($location.hash()) {
|
||||
$rootScope.addMagnet($location.hash());
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
app.controller("ViewCtrl", [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$location",
|
||||
function ($scope, $rootScope, $location) {
|
||||
$scope.addMagnet = function () {
|
||||
$rootScope.addMagnet($scope.torrentInput);
|
||||
$scope.torrentInput = "";
|
||||
};
|
||||
if ($location.hash()) {
|
||||
$rootScope.addMagnet($location.hash());
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
// Angular filters for size, progress, etc.
|
||||
app.filter("pbytes", function () {
|
||||
return function (num, speed) {
|
||||
if (isNaN(num)) return "";
|
||||
if (num < 1) return speed ? "" : "0 B";
|
||||
const units = ["B", "kB", "MB", "GB", "TB"];
|
||||
const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), 4);
|
||||
const val = (num / Math.pow(1000, exponent)).toFixed(1) * 1;
|
||||
return `${val} ${units[exponent]}${speed ? "/s" : ""}`;
|
||||
};
|
||||
});
|
||||
|
||||
app.filter("progress", function () {
|
||||
return function (val) {
|
||||
if (typeof val !== "number") return "";
|
||||
return (val * 100).toFixed(1) + "%";
|
||||
};
|
||||
});
|
||||
|
||||
app.filter("html", [
|
||||
"$sce",
|
||||
function ($sce) {
|
||||
return function (input) {
|
||||
return $sce.trustAsHtml(input);
|
||||
};
|
||||
},
|
||||
]);
|
||||
|
||||
app.filter("humanTime", function () {
|
||||
return function (millis) {
|
||||
if (millis < 1000) return "";
|
||||
const duration = moment.duration(millis).humanize();
|
||||
return duration.charAt(0).toUpperCase() + duration.slice(1);
|
||||
};
|
||||
});
|
181
torrent/beacon.html
Normal file
181
torrent/beacon.html
Normal file
@@ -0,0 +1,181 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="BTorrent" lang="en">
|
||||
<head>
|
||||
<base href="./" />
|
||||
<meta charset="UTF-8" />
|
||||
<title>bitvid βeacon | A Browser Based WebTorrent Client</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="bitvid βeacon uses code from βTorrent. βTorrent is a fully-featured Browser WebTorrent Client"
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="bitvid, client, webtorrent, browser, torrent, stream, bittorrent, torrenting, sharing, filesharing"
|
||||
/>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
|
||||
<!-- Scripts: webtorrent, angular, etc. -->
|
||||
<script src="bundle.min.js"></script>
|
||||
<!--<script src="https://cdn.jsdelivr.net/combine/npm/webtorrent/webtorrent.min.js,npm/moment@2,npm/angular@1.5/angular.min.js,npm/angular-route@1.5/angular-route.min.js,npm/angular-sanitize@1.5/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-upload@12.2.13/dist/ng-file-upload.min.js"></script>-->
|
||||
|
||||
<!-- External styles -->
|
||||
<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"
|
||||
/>
|
||||
<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="">
|
||||
<!-- Header with container -->
|
||||
<header class="header-torrent">
|
||||
<div class="container">
|
||||
<div class="logo-container">
|
||||
<img
|
||||
src="../assets/svg/bitvid-logo-light-mode.svg"
|
||||
alt="bitvid Logo"
|
||||
class="bitvid-logo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main content -->
|
||||
<div id="viewer" ng-style="$root.viewerStyle"></div>
|
||||
<div id="view" ng-view></div>
|
||||
|
||||
<!-- Footer with container -->
|
||||
<footer class="footer-torrent">
|
||||
<div class="container">
|
||||
<a
|
||||
href="http://bitvid.network/"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
bitvid.network
|
||||
</a>
|
||||
|
|
||||
<a
|
||||
href="https://bitvid.btc.us"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
bitvid.btc.us
|
||||
</a>
|
||||
|
|
||||
<a
|
||||
href="https://bitvid.eth.limo"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
bitvid.eth.limo
|
||||
</a>
|
||||
<div class="mt-2 space-x-4">
|
||||
<a
|
||||
href="community-guidelines.html"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Community Guidelines
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/PR0M3TH3AN/bitvid"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Nostr
|
||||
</a>
|
||||
<a
|
||||
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href="getting-started.html"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Getting Started
|
||||
</a>
|
||||
<a
|
||||
href="about.html"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href="roadmap.html"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Roadmap
|
||||
</a>
|
||||
<a
|
||||
href="https://beta.bitvid.network/"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_self"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Beta
|
||||
</a>
|
||||
<a
|
||||
href="beacon.html"
|
||||
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
βeacon
|
||||
</a>
|
||||
</div>
|
||||
<p
|
||||
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
|
||||
>
|
||||
IPNS:
|
||||
<a href="ipns.html" class="text-blue-600 underline">
|
||||
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Processing Spinner -->
|
||||
<div class="spinner" ng-show="client.processing">
|
||||
<i class="fa fa-spinner fa-spin spinner-icon"></i>
|
||||
</div>
|
||||
|
||||
<!-- Local Angular code -->
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
432
torrent/bundle.min.js
vendored
Normal file
432
torrent/bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
torrent/favicon.ico
Normal file
BIN
torrent/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
68
torrent/style.css
Normal file
68
torrent/style.css
Normal file
@@ -0,0 +1,68 @@
|
||||
:root {
|
||||
--color-bg: #ffffff;
|
||||
--color-card: #1e293b;
|
||||
--color-primary: #8b5cf6;
|
||||
--color-secondary: #f43f5e;
|
||||
--color-text: #7b7b7b;
|
||||
--color-muted: #94a3b8;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Container used in header & footer */
|
||||
.container {
|
||||
width: 95%;
|
||||
max-width: 95%;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
.header-torrent {
|
||||
background-color: var(--color-bg);
|
||||
margin-bottom: 2rem;
|
||||
/* No extra horizontal padding so it lines up with .container */
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bitvid-logo {
|
||||
height: 6rem;
|
||||
width: auto;
|
||||
/* Adjust spacing around the logo if needed */
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
/* Footer styling */
|
||||
.footer-torrent {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
margin-top: 2rem;
|
||||
background-color: var(--color-bg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Additional styling remains unchanged... */
|
172
torrent/views/full.html
Normal file
172
torrent/views/full.html
Normal file
@@ -0,0 +1,172 @@
|
||||
<!-- MAIN CONTENT AREA -->
|
||||
<div class="container">
|
||||
<!-- 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>
|
||||
|
||||
<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="three columns">
|
||||
<!-- "Open torrent file" button -->
|
||||
<button
|
||||
type="file"
|
||||
ngf-select="$root.openTorrentFile($file)"
|
||||
ng-disabled="$root.disabled"
|
||||
class="button button-danger"
|
||||
>
|
||||
<i class="fa fa-folder-open"></i> Open torrent file
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<!-- ui-grid for active torrents (Name, Progress, Speeds, Peers, Ratio) -->
|
||||
<div
|
||||
class="row grid"
|
||||
ui-grid="gridOptions"
|
||||
ui-grid-resize-columns="ui-grid-resize-columns"
|
||||
ui-grid-selection="ui-grid-selection"
|
||||
></div>
|
||||
|
||||
<!-- Selected torrent details: Pause/Resume, Download, Remove -->
|
||||
<div class="row" ng-if="selectedTorrent">
|
||||
<div class="six columns" style="overflow: auto">
|
||||
<h5>
|
||||
{{ selectedTorrent.name }}
|
||||
|
||||
<!-- 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>
|
||||
<!-- Download .torrent file button -->
|
||||
<button
|
||||
class="button button-small"
|
||||
ng-click="$root.saveTorrentFile(selectedTorrent)"
|
||||
>
|
||||
Download .torrent
|
||||
</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)"
|
||||
>
|
||||
<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 class="center">
|
||||
<strong>
|
||||
Client Stats: ↓ {{ client.downloadSpeed | pbytes }}/s · ↑ {{
|
||||
client.uploadSpeed | pbytes }}/s · Ratio: {{ client.ratio | number:2 }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Processing Spinner -->
|
||||
<div class="spinner" ng-show="client.processing">
|
||||
<i class="fa fa-spinner fa-spin spinner-icon"></i>
|
||||
</div>
|
Reference in New Issue
Block a user