WebUI: Allow to move state icon to name column in torrents table

PR #22118.
This commit is contained in:
skomerko 2025-01-28 07:46:09 +01:00 committed by GitHub
commit af65ddd012
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 148 additions and 75 deletions

View file

@ -9,7 +9,7 @@
} }
#transferList .dynamicTable td { #transferList .dynamicTable td {
padding: 3px 2px; padding: 2px;
} }
.dynamicTableDiv table.dynamicTable tbody tr.selected { .dynamicTableDiv table.dynamicTable tbody tr.selected {
@ -22,10 +22,61 @@
color: var(--color-text-white); color: var(--color-text-white);
} }
#transferList img.stateIcon { #transferList .stateIcon {
height: 1.3em; background: left center / contain no-repeat;
margin-bottom: -1px; margin-left: 3px;
vertical-align: middle; padding-left: 1.65em;
&.stateIconColumn {
height: 14px;
margin: auto;
padding-left: 0;
width: 14px;
}
&.stateDownloading {
background-image: url("../images/downloading.svg");
}
&.stateUploading {
background-image: url("../images/upload.svg");
}
&.stateStalledUP {
background-image: url("../images/stalledUP.svg");
}
&.stateStalledDL {
background-image: url("../images/stalledDL.svg");
}
&.stateStoppedDL {
background-image: url("../images/stopped.svg");
}
&.stateStoppedUP {
background-image: url("../images/checked-completed.svg");
}
&.stateQueued {
background-image: url("../images/queued.svg");
}
&.stateChecking {
background-image: url("../images/force-recheck.svg");
}
&.stateMoving {
background-image: url("../images/set-location.svg");
}
&.stateError {
background-image: url("../images/error.svg");
}
&.stateUnknown {
background-image: none;
}
} }
#transferList #transferList_pad { #transferList #transferList_pad {

View file

@ -271,8 +271,8 @@ window.qBittorrent.DynamicTable ??= (() => {
let width = this.startWidth + (event.event.pageX - this.dragStartX); let width = this.startWidth + (event.event.pageX - this.dragStartX);
if (width < 16) if (width < 16)
width = 16; width = 16;
this.columns[this.resizeTh.columnName].width = width;
this.updateColumn(this.resizeTh.columnName); this._setColumnWidth(this.resizeTh.columnName, width);
} }
}.bind(this); }.bind(this);
@ -371,6 +371,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns[columnName].visible = show ? "1" : "0"; this.columns[columnName].visible = show ? "1" : "0";
LocalPreferences.set(`column_${columnName}_visible_${this.dynamicTableDivId}`, show ? "1" : "0"); LocalPreferences.set(`column_${columnName}_visible_${this.dynamicTableDivId}`, show ? "1" : "0");
this.updateColumn(columnName); this.updateColumn(columnName);
this.columns[columnName].onVisibilityChange?.(columnName);
}, },
_calculateColumnBodyWidth: function(column) { _calculateColumnBodyWidth: function(column) {
@ -397,6 +398,18 @@ window.qBittorrent.DynamicTable ??= (() => {
return longestTd.width + 10; return longestTd.width + 10;
}, },
_setColumnWidth: function(columnName, width) {
const column = this.columns[columnName];
column.width = width;
const pos = this.getColumnPos(column.name);
const style = `width: ${column.width}px; ${column.style}`;
this.getRowCells(this.hiddenTableHeader)[pos].style.cssText = style;
this.getRowCells(this.fixedTableHeader)[pos].style.cssText = style;
column.onResize?.(column.name);
},
autoResizeColumn: function(columnName) { autoResizeColumn: function(columnName) {
const column = this.columns[columnName]; const column = this.columns[columnName];
@ -418,8 +431,7 @@ window.qBittorrent.DynamicTable ??= (() => {
width = Math.max(headTextWidth, bodyTextWidth); width = Math.max(headTextWidth, bodyTextWidth);
} }
column.width = width; this._setColumnWidth(column.name, width);
this.updateColumn(column.name);
this.saveColumnWidth(column.name); this.saveColumnWidth(column.name);
}, },
@ -545,7 +557,11 @@ window.qBittorrent.DynamicTable ??= (() => {
td.textContent = value; td.textContent = value;
td.title = value; td.title = value;
}; };
column["isVisible"] = function() {
return (this.visible === "1") && !this.force_hide;
};
column["onResize"] = null; column["onResize"] = null;
column["onVisibilityChange"] = null;
column["staticWidth"] = null; column["staticWidth"] = null;
column["calculateBuffer"] = () => 0; column["calculateBuffer"] = () => 0;
this.columns.push(column); this.columns.push(column);
@ -612,31 +628,21 @@ window.qBittorrent.DynamicTable ??= (() => {
return -1; return -1;
}, },
updateColumn: function(columnName) { updateColumn: function(columnName, updateCellData = false) {
const column = this.columns[columnName];
const pos = this.getColumnPos(columnName); const pos = this.getColumnPos(columnName);
const visible = ((this.columns[pos].visible !== "0") && !this.columns[pos].force_hide); const ths = this.getRowCells(this.hiddenTableHeader);
const ths = this.hiddenTableHeader.getElements("th"); const fths = this.getRowCells(this.fixedTableHeader);
const fths = this.fixedTableHeader.getElements("th"); const action = column.isVisible() ? "remove" : "add";
const trs = this.tableBody.getElements("tr"); ths[pos].classList[action]("invisible");
const style = `width: ${this.columns[pos].width}px; ${this.columns[pos].style}`; fths[pos].classList[action]("invisible");
ths[pos].style.cssText = style; for (const tr of this.getTrs()) {
fths[pos].style.cssText = style; const td = this.getRowCells(tr)[pos];
td.classList[action]("invisible");
if (visible) { if (updateCellData)
ths[pos].classList.remove("invisible"); column.updateTd(td, this.rows.get(tr.rowId));
fths[pos].classList.remove("invisible");
for (let i = 0; i < trs.length; ++i)
trs[i].getElements("td")[pos].classList.remove("invisible");
} }
else {
ths[pos].classList.add("invisible");
fths[pos].classList.add("invisible");
for (let j = 0; j < trs.length; ++j)
trs[j].getElements("td")[pos].classList.add("invisible");
}
if (this.columns[pos].onResize !== null)
this.columns[pos].onResize(columnName);
}, },
getSortedColumn: function() { getSortedColumn: function() {
@ -789,6 +795,14 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
}, },
getTrs: function() {
return this.tableBody.querySelectorAll("tr");
},
getRowCells: (tr) => {
return tr.querySelectorAll("td, th");
},
getRow: function(rowId) { getRow: function(rowId) {
return this.rows.get(rowId); return this.rows.get(rowId);
}, },
@ -895,9 +909,9 @@ window.qBittorrent.DynamicTable ??= (() => {
const row = this.rows.get(tr.rowId); const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? "full_data" : "data"]; const data = row[fullUpdate ? "full_data" : "data"];
const tds = tr.getElements("td"); const tds = this.getRowCells(tr);
for (let i = 0; i < this.columns.length; ++i) { for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0])) if (this.columns[i].dataProperties.some(prop => Object.hasOwn(data, prop)))
this.columns[i].updateTd(tds[i], row); this.columns[i].updateTd(tds[i], row);
} }
row["data"] = {}; row["data"] = {};
@ -988,7 +1002,7 @@ window.qBittorrent.DynamicTable ??= (() => {
initColumns: function() { initColumns: function() {
this.newColumn("priority", "", "#", 30, true); this.newColumn("priority", "", "#", 30, true);
this.newColumn("state_icon", "cursor: default", "", 22, true); this.newColumn("state_icon", "", "QBT_TR(Status Icon)QBT_TR[CONTEXT=TransferListModel]", 30, false);
this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TransferListModel]", 200, true); this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TransferListModel]", 200, true);
this.newColumn("size", "", "QBT_TR(Size)QBT_TR[CONTEXT=TransferListModel]", 100, true); this.newColumn("size", "", "QBT_TR(Size)QBT_TR[CONTEXT=TransferListModel]", 100, true);
this.newColumn("total_size", "", "QBT_TR(Total Size)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("total_size", "", "QBT_TR(Total Size)QBT_TR[CONTEXT=TransferListModel]", 100, false);
@ -1026,9 +1040,8 @@ window.qBittorrent.DynamicTable ??= (() => {
this.newColumn("reannounce", "", "QBT_TR(Reannounce In)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("reannounce", "", "QBT_TR(Reannounce In)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.newColumn("private", "", "QBT_TR(Private)QBT_TR[CONTEXT=TransferListModel]", 100, false); this.newColumn("private", "", "QBT_TR(Private)QBT_TR[CONTEXT=TransferListModel]", 100, false);
this.columns["state_icon"].onclick = "";
this.columns["state_icon"].dataProperties[0] = "state"; this.columns["state_icon"].dataProperties[0] = "state";
this.columns["name"].dataProperties.push("state");
this.columns["num_seeds"].dataProperties.push("num_complete"); this.columns["num_seeds"].dataProperties.push("num_complete");
this.columns["num_leechs"].dataProperties.push("num_incomplete"); this.columns["num_leechs"].dataProperties.push("num_incomplete");
this.columns["time_active"].dataProperties.push("seeding_time"); this.columns["time_active"].dataProperties.push("seeding_time");
@ -1037,83 +1050,92 @@ window.qBittorrent.DynamicTable ??= (() => {
}, },
initColumnsFunctions: function() { initColumnsFunctions: function() {
const getStateIconClasses = (state) => {
// state_icon let stateClass = "stateUnknown";
this.columns["state_icon"].updateTd = function(td, row) {
let state = this.getRowValue(row);
let img_path;
// normalize states // normalize states
switch (state) { switch (state) {
case "forcedDL": case "forcedDL":
case "metaDL": case "metaDL":
case "forcedMetaDL": case "forcedMetaDL":
case "downloading": case "downloading":
state = "downloading"; stateClass = "stateDownloading";
img_path = "images/downloading.svg";
break; break;
case "forcedUP": case "forcedUP":
case "uploading": case "uploading":
state = "uploading"; stateClass = "stateUploading";
img_path = "images/upload.svg";
break; break;
case "stalledUP": case "stalledUP":
state = "stalledUP"; stateClass = "stateStalledUP";
img_path = "images/stalledUP.svg";
break; break;
case "stalledDL": case "stalledDL":
state = "stalledDL"; stateClass = "stateStalledDL";
img_path = "images/stalledDL.svg";
break; break;
case "stoppedDL": case "stoppedDL":
state = "torrent-stop"; stateClass = "stateStoppedDL";
img_path = "images/stopped.svg";
break; break;
case "stoppedUP": case "stoppedUP":
state = "checked-completed"; stateClass = "stateStoppedUP";
img_path = "images/checked-completed.svg";
break; break;
case "queuedDL": case "queuedDL":
case "queuedUP": case "queuedUP":
state = "queued"; stateClass = "stateQueued";
img_path = "images/queued.svg";
break; break;
case "checkingDL": case "checkingDL":
case "checkingUP": case "checkingUP":
case "queuedForChecking": case "queuedForChecking":
case "checkingResumeData": case "checkingResumeData":
state = "force-recheck"; stateClass = "stateChecking";
img_path = "images/force-recheck.svg";
break; break;
case "moving": case "moving":
state = "moving"; stateClass = "stateMoving";
img_path = "images/set-location.svg";
break; break;
case "error": case "error":
case "unknown": case "unknown":
case "missingFiles": case "missingFiles":
state = "error"; stateClass = "stateError";
img_path = "images/error.svg";
break; break;
default: default:
break; // do nothing break; // do nothing
} }
if (td.getChildren("img").length > 0) { return `stateIcon ${stateClass}`;
const img = td.getChildren("img")[0];
if (!img.src.includes(img_path)) {
img.src = img_path;
img.title = state;
}
}
else {
const img = document.createElement("img");
img.src = img_path;
img.className = "stateIcon";
img.title = state;
td.append(img);
}
}; };
// state_icon
this.columns["state_icon"].updateTd = function(td, row) {
const state = this.getRowValue(row);
let div = td.firstElementChild;
if (div === null) {
div = document.createElement("div");
td.append(div);
}
div.className = `${getStateIconClasses(state)} stateIconColumn`;
};
this.columns["state_icon"].onVisibilityChange = (columnName) => {
// show state icon in name column only when standalone
// state icon column is hidden
this.updateColumn("name", true);
};
// name
this.columns["name"].updateTd = function(td, row) {
const name = this.getRowValue(row, 0);
const state = this.getRowValue(row, 1);
let span = td.firstElementChild;
if (span === null) {
span = document.createElement("span");
td.append(span);
}
span.className = this.isStateIconShown() ? `${getStateIconClasses(state)}` : "";
span.textContent = name;
td.title = name;
};
this.columns["name"].isStateIconShown = () => !this.columns["state_icon"].isVisible();
// status // status
this.columns["status"].updateTd = function(td, row) { this.columns["status"].updateTd = function(td, row) {
const state = this.getRowValue(row); const state = this.getRowValue(row);