-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebUI: Allow to move state icon to name column in torrents table #22118
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -271,8 +271,8 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
let width = this.startWidth + (event.event.pageX - this.dragStartX); | ||||||
if (width < 16) | ||||||
width = 16; | ||||||
this.columns[this.resizeTh.columnName].width = width; | ||||||
this.updateColumn(this.resizeTh.columnName); | ||||||
|
||||||
this._setColumnWidth(this.resizeTh.columnName, width); | ||||||
} | ||||||
}.bind(this); | ||||||
|
||||||
|
@@ -371,6 +371,7 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
this.columns[columnName].visible = show ? "1" : "0"; | ||||||
LocalPreferences.set(`column_${columnName}_visible_${this.dynamicTableDivId}`, show ? "1" : "0"); | ||||||
this.updateColumn(columnName); | ||||||
this.columns[columnName].onVisibilityChange?.(columnName); | ||||||
}, | ||||||
|
||||||
_calculateColumnBodyWidth: function(column) { | ||||||
|
@@ -397,6 +398,18 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
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) { | ||||||
const column = this.columns[columnName]; | ||||||
|
||||||
|
@@ -418,8 +431,7 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
width = Math.max(headTextWidth, bodyTextWidth); | ||||||
} | ||||||
|
||||||
column.width = width; | ||||||
this.updateColumn(column.name); | ||||||
this._setColumnWidth(column.name, width); | ||||||
this.saveColumnWidth(column.name); | ||||||
}, | ||||||
|
||||||
|
@@ -545,7 +557,11 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
td.textContent = value; | ||||||
td.title = value; | ||||||
}; | ||||||
column["isVisible"] = function() { | ||||||
return (this.visible === "1") && !this.force_hide; | ||||||
}; | ||||||
column["onResize"] = null; | ||||||
column["onVisibilityChange"] = null; | ||||||
column["staticWidth"] = null; | ||||||
column["calculateBuffer"] = () => 0; | ||||||
this.columns.push(column); | ||||||
|
@@ -612,31 +628,21 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
return -1; | ||||||
}, | ||||||
|
||||||
updateColumn: function(columnName) { | ||||||
updateColumn: function(columnName, updateCellData = false) { | ||||||
const column = this.columns[columnName]; | ||||||
const pos = this.getColumnPos(columnName); | ||||||
const visible = ((this.columns[pos].visible !== "0") && !this.columns[pos].force_hide); | ||||||
const ths = this.hiddenTableHeader.getElements("th"); | ||||||
const fths = this.fixedTableHeader.getElements("th"); | ||||||
const trs = this.tableBody.getElements("tr"); | ||||||
const style = `width: ${this.columns[pos].width}px; ${this.columns[pos].style}`; | ||||||
const ths = this.getRowCells(this.hiddenTableHeader); | ||||||
const fths = this.getRowCells(this.fixedTableHeader); | ||||||
const action = column.isVisible() ? "remove" : "add"; | ||||||
ths[pos].classList[action]("invisible"); | ||||||
fths[pos].classList[action]("invisible"); | ||||||
|
||||||
ths[pos].style.cssText = style; | ||||||
fths[pos].style.cssText = style; | ||||||
|
||||||
if (visible) { | ||||||
ths[pos].classList.remove("invisible"); | ||||||
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"); | ||||||
for (const tr of this.getTrs()) { | ||||||
const td = this.getRowCells(tr)[pos]; | ||||||
td.classList[action]("invisible"); | ||||||
if (updateCellData) | ||||||
column.updateTd(td, this.rows.get(tr.rowId)); | ||||||
} | ||||||
if (this.columns[pos].onResize !== null) | ||||||
this.columns[pos].onResize(columnName); | ||||||
}, | ||||||
|
||||||
getSortedColumn: function() { | ||||||
|
@@ -789,6 +795,14 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
} | ||||||
}, | ||||||
|
||||||
getTrs: function() { | ||||||
return [...this.tableBody.rows]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about this?
Suggested change
It should be enough if you only need iteration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both rows & cells properties return a live HTMLCollection which is automatically updated when the underlying document is changed, so I convert them to a static one to avoid it. IIRC getElements called with a simple tag does the same thing - it uses getElementsByTagName internally and then creates Elements collection (live -> static). Maybe it would be better to return this.tableBody.querySelectorAll("tr") instead? I can't tell if there is a difference in performance between these two approaches. |
||||||
}, | ||||||
|
||||||
getRowCells: (tr) => { | ||||||
return [...tr.cells]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too:
Suggested change
|
||||||
}, | ||||||
|
||||||
getRow: function(rowId) { | ||||||
return this.rows.get(rowId); | ||||||
}, | ||||||
|
@@ -895,9 +909,9 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
const row = this.rows.get(tr.rowId); | ||||||
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) { | ||||||
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); | ||||||
} | ||||||
row["data"] = {}; | ||||||
|
@@ -988,7 +1002,7 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
|
||||||
initColumns: function() { | ||||||
this.newColumn("priority", "", "#", 30, true); | ||||||
this.newColumn("state_icon", "cursor: default", "", 22, true); | ||||||
this.newColumn("state_icon", "", "QBT_TR(State Icon)QBT_TR[CONTEXT=TransferListModel]", 30, false); | ||||||
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("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("private", "", "QBT_TR(Private)QBT_TR[CONTEXT=TransferListModel]", 100, false); | ||||||
|
||||||
this.columns["state_icon"].onclick = ""; | ||||||
this.columns["state_icon"].dataProperties[0] = "state"; | ||||||
|
||||||
this.columns["name"].dataProperties.push("state"); | ||||||
this.columns["num_seeds"].dataProperties.push("num_complete"); | ||||||
this.columns["num_leechs"].dataProperties.push("num_incomplete"); | ||||||
this.columns["time_active"].dataProperties.push("seeding_time"); | ||||||
|
@@ -1037,83 +1050,92 @@ window.qBittorrent.DynamicTable ??= (() => { | |||||
}, | ||||||
|
||||||
initColumnsFunctions: function() { | ||||||
|
||||||
// state_icon | ||||||
this.columns["state_icon"].updateTd = function(td, row) { | ||||||
let state = this.getRowValue(row); | ||||||
let img_path; | ||||||
const getStateIconClasses = (state) => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't it be singular?
Suggested change
|
||||||
let stateClass = "stateUnknown"; | ||||||
// normalize states | ||||||
switch (state) { | ||||||
case "forcedDL": | ||||||
case "metaDL": | ||||||
case "forcedMetaDL": | ||||||
case "downloading": | ||||||
state = "downloading"; | ||||||
img_path = "images/downloading.svg"; | ||||||
stateClass = "stateDownloading"; | ||||||
break; | ||||||
case "forcedUP": | ||||||
case "uploading": | ||||||
state = "uploading"; | ||||||
img_path = "images/upload.svg"; | ||||||
stateClass = "stateUploading"; | ||||||
break; | ||||||
case "stalledUP": | ||||||
state = "stalledUP"; | ||||||
img_path = "images/stalledUP.svg"; | ||||||
stateClass = "stateStalledUP"; | ||||||
break; | ||||||
case "stalledDL": | ||||||
state = "stalledDL"; | ||||||
img_path = "images/stalledDL.svg"; | ||||||
stateClass = "stateStalledDL"; | ||||||
break; | ||||||
case "stoppedDL": | ||||||
state = "torrent-stop"; | ||||||
img_path = "images/stopped.svg"; | ||||||
stateClass = "stateStoppedDL"; | ||||||
break; | ||||||
case "stoppedUP": | ||||||
state = "checked-completed"; | ||||||
img_path = "images/checked-completed.svg"; | ||||||
stateClass = "stateStoppedUP"; | ||||||
break; | ||||||
case "queuedDL": | ||||||
case "queuedUP": | ||||||
state = "queued"; | ||||||
img_path = "images/queued.svg"; | ||||||
stateClass = "stateQueued"; | ||||||
break; | ||||||
case "checkingDL": | ||||||
case "checkingUP": | ||||||
case "queuedForChecking": | ||||||
case "checkingResumeData": | ||||||
state = "force-recheck"; | ||||||
img_path = "images/force-recheck.svg"; | ||||||
stateClass = "stateChecking"; | ||||||
break; | ||||||
case "moving": | ||||||
state = "moving"; | ||||||
img_path = "images/set-location.svg"; | ||||||
stateClass = "stateMoving"; | ||||||
break; | ||||||
case "error": | ||||||
case "unknown": | ||||||
case "missingFiles": | ||||||
state = "error"; | ||||||
img_path = "images/error.svg"; | ||||||
stateClass = "stateError"; | ||||||
break; | ||||||
default: | ||||||
break; // do nothing | ||||||
} | ||||||
|
||||||
if (td.getChildren("img").length > 0) { | ||||||
const img = td.getChildren("img")[0]; | ||||||
if (!img.src.includes(img_path)) { | ||||||
img.src = img_path; | ||||||
img.title = state; | ||||||
} | ||||||
return `stateIcon ${stateClass}`; | ||||||
}; | ||||||
|
||||||
// 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); | ||||||
} | ||||||
else { | ||||||
const img = document.createElement("img"); | ||||||
img.src = img_path; | ||||||
img.className = "stateIcon"; | ||||||
img.title = state; | ||||||
td.append(img); | ||||||
|
||||||
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 | ||||||
this.columns["status"].updateTd = function(td, row) { | ||||||
const state = this.getRowValue(row); | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Chocobo1, changes made around this method are not strictly necessery - name column could be updated from within onVisibilityChange callback but I thought it's better to improve updateColumn a bit instead (though things are definitely not perfect but I didn't want to change too much).