Skip to content

Commit

Permalink
Support auto resizing table columns
Browse files Browse the repository at this point in the history
Auto resize can be triggered by:
1. Double clicking the column's resize handle (its rightmost edge)
2. The table header's context menu
  • Loading branch information
Piccirello committed Oct 24, 2024
1 parent 25dbea1 commit 80bef22
Showing 1 changed file with 117 additions and 5 deletions.
122 changes: 117 additions & 5 deletions src/webui/www/private/scripts/dynamicTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ window.qBittorrent.DynamicTable ??= (() => {
resetElementBorderStyle(this.lastHoverTh);
el.style.backgroundColor = "";
if (this.currentHeaderAction === "resize")
LocalPreferences.set("column_" + this.resizeTh.columnName + "_width_" + this.dynamicTableDivId, this.columns[this.resizeTh.columnName].width);
this.saveColumnWidth(this.resizeTh.columnName);
if ((this.currentHeaderAction === "drag") && (el !== this.lastHoverTh)) {
this.saveColumnsOrder();
const val = LocalPreferences.get("columns_order_" + this.dynamicTableDivId).split(",");
Expand All @@ -260,7 +260,10 @@ window.qBittorrent.DynamicTable ??= (() => {

const onCancel = function(el) {
this.currentHeaderAction = "";
this.setSortedColumn(el.columnName);

// ignore click/touch events performed when on the column's resize area
if (!this.canResize)
this.setSortedColumn(el.columnName);
}.bind(this);

const onTouch = function(e) {
Expand All @@ -269,13 +272,26 @@ window.qBittorrent.DynamicTable ??= (() => {
this.setSortedColumn(column);
}.bind(this);

const onDoubleClick = function(e) {
e.preventDefault();
this.currentHeaderAction = "";

// only resize when hovering on the column's resize area
if (this.canResize) {
this.currentHeaderAction = "resize";
this.autoResizeColumn(e.target.columnName);
onComplete(e.target);
}
}.bind(this);

const ths = this.fixedTableHeader.getElements("th");

for (let i = 0; i < ths.length; ++i) {
const th = ths[i];
th.addEventListener("mousemove", mouseMoveFn);
th.addEventListener("mouseout", mouseOutFn);
th.addEventListener("touchend", onTouch, { passive: true });
th.addEventListener("dblclick", onDoubleClick);
th.makeResizable({
modifiers: {
x: "",
Expand Down Expand Up @@ -311,6 +327,58 @@ window.qBittorrent.DynamicTable ??= (() => {
this.updateColumn(columnName);
},

_calculateColumnBodyWidth: function(column) {
const columnIndex = this.getColumnPos(column.name);
const bodyColumn = document.getElementById(this.dynamicTableDivId).querySelectorAll("tr>th")[columnIndex];
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
context.font = window.getComputedStyle(bodyColumn, null).getPropertyValue("font");

const longestTd = { value: "", buffer: 0 };
for (const tr of this.tableBody.querySelectorAll("tr")) {
const tds = tr.querySelectorAll("td");
const td = tds[columnIndex];

const buffer = column.calculateBuffer(tr.rowId);
const valueLength = td.textContent.length;
if ((valueLength + buffer) > (longestTd.value.length + longestTd.buffer)) {
longestTd.value = td.textContent;
longestTd.buffer = buffer;
}
}

return context.measureText(longestTd.value).width + longestTd.buffer;
},

autoResizeColumn: function(columnName) {
const column = this.columns[columnName];

let width = column.staticWidth ?? 0;
if (column.staticWidth === null) {
// check required min body width
const bodyTextWidth = this._calculateColumnBodyWidth(column);

// check required min header width
const columnIndex = this.getColumnPos(column.name);
const headColumn = document.getElementById(this.dynamicTableFixedHeaderDivId).querySelectorAll("tr>th")[columnIndex];
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
context.font = window.getComputedStyle(headColumn, null).getPropertyValue("font");
const columnTitle = column.caption;
const headTextWidth = context.measureText(columnTitle).width;

width = Math.max(headTextWidth, bodyTextWidth) + 20;
}

column.width = width;
this.updateColumn(column.name);
this.saveColumnWidth(column.name);
},

saveColumnWidth: function(columnName) {
LocalPreferences.set(`column_${columnName}_width_${this.dynamicTableDivId}`, this.columns[columnName].width);
},

setupHeaderMenu: function() {
this.setupDynamicTableHeaderContextMenuClass();

Expand All @@ -337,7 +405,16 @@ window.qBittorrent.DynamicTable ??= (() => {
return listItem;
};

const actions = {};
const actions = {
autoResizeAction: function(element, ref, action) {
this.autoResizeColumn(element.columnName);
}.bind(this),

autoResizeAllAction: function(element, ref, action) {
for (const { name } of this.columns)
this.autoResizeColumn(name);
}.bind(this),
};

const onMenuItemClicked = function(element, ref, action) {
this.showColumn(action, this.columns[action].visible === "0");
Expand All @@ -357,10 +434,30 @@ window.qBittorrent.DynamicTable ??= (() => {
actions[this.columns[i].name] = onMenuItemClicked;
}

const createResizeElement = function (text, href) {
const anchor = document.createElement("a");
anchor.href = href;
anchor.textContent = text;

const spacer = document.createElement("span");
spacer.style = "display: inline-block; width: calc(.5em + 16px);";
anchor.prepend(spacer);

const li = document.createElement("li");
li.appendChild(anchor);
return li;
};

const autoResizeAllElement = createResizeElement("Resize All", "#autoResizeAllAction");
const autoResizeElement = createResizeElement("Resize", "#autoResizeAction");

ul.firstChild.classList.add("separator");
ul.insertBefore(autoResizeAllElement, ul.firstChild);
ul.insertBefore(autoResizeElement, ul.firstChild);
ul.inject(document.body);

this.headerContextMenu = new DynamicTableHeaderContextMenuClass({
targets: "#" + this.dynamicTableFixedHeaderDivId + " tr",
targets: "#" + this.dynamicTableFixedHeaderDivId + " tr th",
actions: actions,
menu: menuId,
offsets: {
Expand Down Expand Up @@ -402,6 +499,8 @@ window.qBittorrent.DynamicTable ??= (() => {
td.title = value;
};
column["onResize"] = null;
column["staticWidth"] = null;
column["calculateBuffer"] = () => 0;
this.columns.push(column);
this.columns[name] = column;

Expand Down Expand Up @@ -1163,7 +1262,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.resized = false;
}
};

this.columns["progress"].staticWidth = 100;
this.columns["progress"].onResize = function(columnName) {
const pos = this.getColumnPos(columnName);
const trs = this.tableBody.getElements("tr");
Expand Down Expand Up @@ -1721,6 +1820,7 @@ window.qBittorrent.DynamicTable ??= (() => {

// relevance
this.columns["relevance"].updateTd = this.columns["progress"].updateTd;
this.columns["relevance"].staticWidth = 100;

// files
this.columns["files"].updateTd = function(td, row) {
Expand Down Expand Up @@ -2088,6 +2188,7 @@ window.qBittorrent.DynamicTable ??= (() => {
checkbox.indeterminate = false;
td.adopt(treeImg, checkbox);
};
this.columns["checked"].staticWidth = 50;

// original
this.columns["original"].updateTd = function(td, row) {
Expand Down Expand Up @@ -2410,6 +2511,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.adopt(treeImg, window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value));
}
};
this.columns["checked"].staticWidth = 50;

// name
this.columns["name"].updateTd = function(td, row) {
Expand Down Expand Up @@ -2464,6 +2566,12 @@ window.qBittorrent.DynamicTable ??= (() => {
td.replaceChildren(span);
}
};
this.columns["name"].calculateBuffer = function(rowId) {
const node = that.getNode(rowId);
// folders add 20px for folder icon and 15px for collapse icon
const folderBuffer = node.isFolder ? 35 : 0;
return (node.depth * 20) + folderBuffer;
};

// size
this.columns["size"].updateTd = displaySize;
Expand All @@ -2484,6 +2592,7 @@ window.qBittorrent.DynamicTable ??= (() => {
progressBar.setValue(value.toFloat());
}
};
this.columns["progress"].staticWidth = 100;

// priority
this.columns["priority"].updateTd = function(td, row) {
Expand All @@ -2495,6 +2604,7 @@ window.qBittorrent.DynamicTable ??= (() => {
else
td.adopt(window.qBittorrent.PropFiles.createPriorityCombo(id, row.full_data.fileId, value));
};
this.columns["priority"].staticWidth = 140;

// remaining, availability
this.columns["remaining"].updateTd = displaySize;
Expand Down Expand Up @@ -2892,6 +3002,7 @@ window.qBittorrent.DynamicTable ??= (() => {
$("cbRssDlRule" + row.rowId).checked = row.full_data.checked;
}
};
this.columns["checked"].staticWidth = 50;
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
Expand Down Expand Up @@ -2983,6 +3094,7 @@ window.qBittorrent.DynamicTable ??= (() => {
$("cbRssDlFeed" + row.rowId).checked = row.full_data.checked;
}
};
this.columns["checked"].staticWidth = 50;
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
Expand Down

0 comments on commit 80bef22

Please sign in to comment.