mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-25 14:04:23 +00:00
parent
aa6b29fe7e
commit
ba511e07b4
@ -508,19 +508,16 @@ td.generalLabel {
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress {
|
.piecesbarWrapper {
|
||||||
border: 1px solid #999;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress canvas {
|
.piecesbarCanvas {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
left: 0;
|
inset: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script src="scripts/progressbar.js?v=${CACHEID}"></script>
|
<script src="scripts/progressbar.js?v=${CACHEID}"></script>
|
||||||
|
<script src="scripts/piecesbar.js?v=${CACHEID}"></script>
|
||||||
<script src="scripts/file-tree.js?v=${CACHEID}"></script>
|
<script src="scripts/file-tree.js?v=${CACHEID}"></script>
|
||||||
<script src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script src="scripts/client.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/client.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
|
278
src/webui/www/private/scripts/piecesbar.js
Normal file
278
src/webui/www/private/scripts/piecesbar.js
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2022 Jesse Smick <jesse.smick@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
if (window.qBittorrent === undefined) {
|
||||||
|
window.qBittorrent = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.qBittorrent.PiecesBar = (() => {
|
||||||
|
const exports = () => {
|
||||||
|
return {
|
||||||
|
PiecesBar: PiecesBar
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_DOWNLOADING = 1;
|
||||||
|
const STATUS_DOWNLOADED = 2;
|
||||||
|
|
||||||
|
// absolute max width of 4096
|
||||||
|
// this is to support all browsers for size of canvas elements
|
||||||
|
// see https://github.com/jhildenbiddle/canvas-size#test-results
|
||||||
|
const MAX_CANVAS_WIDTH = 4096;
|
||||||
|
|
||||||
|
let piecesBarUniqueId = 0;
|
||||||
|
const PiecesBar = new Class({
|
||||||
|
initialize(pieces, parameters) {
|
||||||
|
const vals = {
|
||||||
|
'id': 'piecesbar_' + (piecesBarUniqueId++),
|
||||||
|
'width': 0,
|
||||||
|
'height': 0,
|
||||||
|
'downloadingColor': 'green',
|
||||||
|
'haveColor': 'blue',
|
||||||
|
'borderSize': 1,
|
||||||
|
'borderColor': '#999'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parameters && ($type(parameters) === 'object'))
|
||||||
|
$extend(vals, parameters);
|
||||||
|
vals.height = Math.max(vals.height, 12);
|
||||||
|
|
||||||
|
const obj = new Element('div', {
|
||||||
|
'id': vals.id,
|
||||||
|
'class': 'piecesbarWrapper',
|
||||||
|
'styles': {
|
||||||
|
'border': vals.borderSize.toString() + 'px solid ' + vals.borderColor,
|
||||||
|
'height': vals.height.toString() + 'px',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
obj.vals = vals;
|
||||||
|
obj.vals.pieces = $pick(pieces, []);
|
||||||
|
|
||||||
|
obj.vals.canvas = new Element('canvas', {
|
||||||
|
'id': vals.id + '_canvas',
|
||||||
|
'class': 'piecesbarCanvas',
|
||||||
|
'width': (vals.width - (2 * vals.borderSize)).toString(),
|
||||||
|
'height': '1' // will stretch vertically to take up the height of the parent
|
||||||
|
});
|
||||||
|
obj.appendChild(obj.vals.canvas);
|
||||||
|
|
||||||
|
obj.setPieces = setPieces;
|
||||||
|
obj.refresh = refresh;
|
||||||
|
obj.clear = setPieces.bind(obj, []);
|
||||||
|
obj._drawStatus = drawStatus;
|
||||||
|
|
||||||
|
if (vals.width > 0)
|
||||||
|
obj.setPieces(vals.pieces);
|
||||||
|
else
|
||||||
|
setTimeout(() => { checkForParent(obj.id); }, 1);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPieces(pieces) {
|
||||||
|
if (!Array.isArray(pieces))
|
||||||
|
pieces = [];
|
||||||
|
|
||||||
|
this.vals.pieces = pieces;
|
||||||
|
this.refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(force) {
|
||||||
|
const start = Date.now();
|
||||||
|
if (!this.parentNode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const pieces = this.vals.pieces;
|
||||||
|
|
||||||
|
// if the number of pieces is small, use that for the width,
|
||||||
|
// and have it stretch horizontally.
|
||||||
|
// this also limits the ratio below to >= 1
|
||||||
|
const width = Math.min(this.offsetWidth, pieces.length, MAX_CANVAS_WIDTH);
|
||||||
|
if ((this.vals.width === width) && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.vals.width = width;
|
||||||
|
|
||||||
|
// change canvas size to fit exactly in the space
|
||||||
|
this.vals.canvas.width = width - (2 * this.vals.borderSize);
|
||||||
|
|
||||||
|
const canvas = this.vals.canvas;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const imageWidth = canvas.width;
|
||||||
|
|
||||||
|
if (imageWidth.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let minStatus = Infinity;
|
||||||
|
let maxStatus = 0;
|
||||||
|
|
||||||
|
for (const status of pieces) {
|
||||||
|
if (status > maxStatus) {
|
||||||
|
maxStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status < minStatus) {
|
||||||
|
minStatus = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no progress then don't do anything
|
||||||
|
if (maxStatus === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if all pieces are downloaded, fill entire image at once
|
||||||
|
if (minStatus === STATUS_DOWNLOADED) {
|
||||||
|
ctx.fillStyle = this.vals.haveColor;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linear transformation from pieces to pixels.
|
||||||
|
*
|
||||||
|
* The canvas size can vary in width so this figures out what to draw at each pixel.
|
||||||
|
* Inspired by the GUI code here https://github.com/qbittorrent/qBittorrent/blob/25b3f2d1a6b14f0fe098fb79a3d034607e52deae/src/gui/properties/downloadedpiecesbar.cpp#L54
|
||||||
|
*
|
||||||
|
* example ratio > 1 (at least 2 pieces per pixel)
|
||||||
|
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
* pieces | 2 | 1 | 2 | 0 | 2 | 0 | 1 | 0 | 1 | 2 |
|
||||||
|
* +---------+---------+---------+---------+---------+---------+
|
||||||
|
* pixels | | | | | | |
|
||||||
|
* +---------+---------+---------+---------+---------+---------+
|
||||||
|
*
|
||||||
|
* example ratio < 1 (at most 2 pieces per pixel)
|
||||||
|
* This case shouldn't happen since the max pixels are limited to the number of pieces
|
||||||
|
* +---------+---------+---------+---------+----------+--------+
|
||||||
|
* pieces | 2 | 1 | 1 | 0 | 2 | 2 |
|
||||||
|
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
* pixels | | | | | | | | | | |
|
||||||
|
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ratio = pieces.length / imageWidth;
|
||||||
|
|
||||||
|
let lastValue = null;
|
||||||
|
let rectangleStart = 0;
|
||||||
|
|
||||||
|
// for each pixel compute its status based on the pieces
|
||||||
|
for (let x = 0; x < imageWidth; ++x) {
|
||||||
|
// find positions in the pieces array
|
||||||
|
const piecesFrom = x * ratio;
|
||||||
|
const piecesTo = (x + 1) * ratio;
|
||||||
|
const piecesToInt = Math.ceil(piecesTo);
|
||||||
|
|
||||||
|
const statusValues = {
|
||||||
|
[STATUS_DOWNLOADING]: 0,
|
||||||
|
[STATUS_DOWNLOADED]: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// aggregate the status of each piece that contributes to this pixel
|
||||||
|
for (let p = piecesFrom; p < piecesToInt; ++p) {
|
||||||
|
const piece = Math.floor(p);
|
||||||
|
const pieceStart = Math.max(piecesFrom, piece);
|
||||||
|
const pieceEnd = Math.min(piece + 1, piecesTo);
|
||||||
|
|
||||||
|
const amount = pieceEnd - pieceStart;
|
||||||
|
const status = pieces[piece];
|
||||||
|
|
||||||
|
if (status in statusValues)
|
||||||
|
statusValues[status] += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize to interval [0, 1]
|
||||||
|
statusValues[STATUS_DOWNLOADING] /= ratio;
|
||||||
|
statusValues[STATUS_DOWNLOADED] /= ratio;
|
||||||
|
|
||||||
|
// floats accumulate small errors, so smooth it out by rounding to hundredths place
|
||||||
|
// this effectively limits each status to a value 1 in 100
|
||||||
|
statusValues[STATUS_DOWNLOADING] = Math.round(statusValues[STATUS_DOWNLOADING] * 100) / 100;
|
||||||
|
statusValues[STATUS_DOWNLOADED] = Math.round(statusValues[STATUS_DOWNLOADED] * 100) / 100;
|
||||||
|
|
||||||
|
// float precision sometimes _still_ gives > 1
|
||||||
|
statusValues[STATUS_DOWNLOADING] = Math.min(statusValues[STATUS_DOWNLOADING], 1);
|
||||||
|
statusValues[STATUS_DOWNLOADED] = Math.min(statusValues[STATUS_DOWNLOADED], 1);
|
||||||
|
|
||||||
|
if (!lastValue) {
|
||||||
|
lastValue = statusValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// group contiguous colors together and draw as a single rectangle
|
||||||
|
if ((lastValue[STATUS_DOWNLOADING] === statusValues[STATUS_DOWNLOADING])
|
||||||
|
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectangleWidth = x - rectangleStart;
|
||||||
|
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||||
|
|
||||||
|
lastValue = statusValues;
|
||||||
|
rectangleStart = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill a rect at the end of the canvas
|
||||||
|
if (rectangleStart < imageWidth) {
|
||||||
|
const rectangleWidth = imageWidth - rectangleStart;
|
||||||
|
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStatus(ctx, start, width, statusValues) {
|
||||||
|
// mix the colors by using transparency and a composite mode
|
||||||
|
ctx.globalCompositeOperation = 'lighten';
|
||||||
|
|
||||||
|
if (statusValues[STATUS_DOWNLOADING]) {
|
||||||
|
ctx.globalAlpha = statusValues[STATUS_DOWNLOADING];
|
||||||
|
ctx.fillStyle = this.vals.downloadingColor;
|
||||||
|
ctx.fillRect(start, 0, width, ctx.canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusValues[STATUS_DOWNLOADED]) {
|
||||||
|
ctx.globalAlpha = statusValues[STATUS_DOWNLOADED];
|
||||||
|
ctx.fillStyle = this.vals.haveColor;
|
||||||
|
ctx.fillRect(start, 0, width, ctx.canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForParent(id) {
|
||||||
|
const obj = $(id);
|
||||||
|
if (!obj)
|
||||||
|
return;
|
||||||
|
if (!obj.parentNode)
|
||||||
|
return setTimeout(function() { checkForParent(id); }, 1);
|
||||||
|
|
||||||
|
obj.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports();
|
||||||
|
})();
|
||||||
|
|
||||||
|
Object.freeze(window.qBittorrent.PiecesBar);
|
@ -39,6 +39,11 @@ window.qBittorrent.PropGeneral = (function() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const piecesBar = new window.qBittorrent.PiecesBar.PiecesBar([], {
|
||||||
|
height: 16
|
||||||
|
});
|
||||||
|
$('progress').appendChild(piecesBar);
|
||||||
|
|
||||||
const clearData = function() {
|
const clearData = function() {
|
||||||
$('time_elapsed').set('html', '');
|
$('time_elapsed').set('html', '');
|
||||||
$('eta').set('html', '');
|
$('eta').set('html', '');
|
||||||
@ -65,9 +70,7 @@ window.qBittorrent.PropGeneral = (function() {
|
|||||||
$('torrent_hash_v2').set('html', '');
|
$('torrent_hash_v2').set('html', '');
|
||||||
$('save_path').set('html', '');
|
$('save_path').set('html', '');
|
||||||
$('comment').set('html', '');
|
$('comment').set('html', '');
|
||||||
|
piecesBar.clear();
|
||||||
const canvas = $('progress').getFirst('canvas');
|
|
||||||
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let loadTorrentDataTimer;
|
let loadTorrentDataTimer;
|
||||||
@ -231,43 +234,7 @@ window.qBittorrent.PropGeneral = (function() {
|
|||||||
$('error_div').set('html', '');
|
$('error_div').set('html', '');
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const canvas = $('progress').getFirst('canvas');
|
piecesBar.setPieces(data);
|
||||||
canvas.width = data.length;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
// Group contiguous colors together and draw as a single rectangle
|
|
||||||
let color = '';
|
|
||||||
let rectWidth = 1;
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; ++i) {
|
|
||||||
const status = data[i];
|
|
||||||
let newColor = '';
|
|
||||||
|
|
||||||
if (status === 1)
|
|
||||||
newColor = 'green';
|
|
||||||
else if (status === 2)
|
|
||||||
newColor = 'blue';
|
|
||||||
|
|
||||||
if (newColor === color) {
|
|
||||||
++rectWidth;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color !== '') {
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect((i - rectWidth), 0, rectWidth, canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
rectWidth = 1;
|
|
||||||
color = newColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill a rect at the end of the canvas if one is needed
|
|
||||||
if (color !== '') {
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect((data.length - rectWidth), 0, rectWidth, canvas.height);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
clearData();
|
clearData();
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
<table style="width: 100%; padding: 0 3px">
|
<table style="width: 100%; padding: 0 3px">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="text-align: right">QBT_TR(Progress:)QBT_TR[CONTEXT=PropertiesWidget]</td>
|
<td style="text-align: right">QBT_TR(Progress:)QBT_TR[CONTEXT=PropertiesWidget]</td>
|
||||||
<td id="progress">
|
<td id="progress" style="width: 100%"></td>
|
||||||
<canvas width="0" height="1"></canvas>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<file>private/scripts/lib/MooTools-More-1.6.0-compat-compressed.js</file>
|
<file>private/scripts/lib/MooTools-More-1.6.0-compat-compressed.js</file>
|
||||||
<file>private/scripts/misc.js</file>
|
<file>private/scripts/misc.js</file>
|
||||||
<file>private/scripts/mocha-init.js</file>
|
<file>private/scripts/mocha-init.js</file>
|
||||||
|
<file>private/scripts/piecesbar.js</file>
|
||||||
<file>private/scripts/preferences.js</file>
|
<file>private/scripts/preferences.js</file>
|
||||||
<file>private/scripts/progressbar.js</file>
|
<file>private/scripts/progressbar.js</file>
|
||||||
<file>private/scripts/prop-files.js</file>
|
<file>private/scripts/prop-files.js</file>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user