TFS TaskBoard Resize

Optimizes the column widths of a Visual Studio Team Foundation Server 2015 taskboard to minimize scrolling.

// ==UserScript==
// @name        TFS TaskBoard Resize
// @description Optimizes the column widths of a Visual Studio Team Foundation Server 2015 taskboard to minimize scrolling.
// @version     0.7
// @author      Amedeo Amato
// @license MIT
// @match       https://*.tfspreview.com/*/_backlogs*
// @match       https://*.tfspreview.com/*/_workitems*
// @match       https://*.tfspreview.com/*/_workItems*
// @match       http://*/tfs/*/_backlogs*
// @match       http://*/tfs/*/_workitems*
// @match       http://*/tfs/*/_workItems*
// @match       http://*/tfs2/*/_backlogs*
// @match       http://*/tfs2/*/_workitems*
// @match       http://*/tfs2/*/_workItems*
// @grant       none
// @namespace   https://greasyfork.org/users/302581
// ==/UserScript==
/* jshint esversion: 6 */
 
waitForTaskboardTableToLoad();
 
function waitForTaskboardTableToLoad() {
    var taskboardTable = document.getElementById("taskboard-table-body");
 
    if (!taskboardTable) {
        setTimeout(waitForTaskboardTableToLoad, 500);
    } else {
        resizeTaskboardColumns();
    }
}
 
function resizeTaskboardColumns() {
    var numberOfColumns = getNumberOfColumns();
    var optimalColumnWidths = calculateOptimalColumnWidth(numberOfColumns);
    setColumnWidths(optimalColumnWidths, numberOfColumns);
    minimizeDoneTasks(numberOfColumns);
    minimizeResolvedTasks(numberOfColumns);
    refreshTaskAlignment();
}
 
function calculateOptimalColumnWidth(numberOfColumns) {
    var taskboardCells = document.getElementsByClassName("taskboard-cell ui-droppable");
 
    // Find the biggest cell in each column, by finding the maximal total height of tasks boxes per cell
    const totalTaskHeightPerColumn = Array(numberOfColumns).fill(0);
    Array.from(taskboardCells).forEach((cell, i) => {
        const colNr = i % numberOfColumns;
        const divsInCell = cell.getElementsByClassName("childTbTile");
        const totalHeight = [...divsInCell].reduce((sum, div) => sum + div.offsetHeight, 0);
        totalTaskHeightPerColumn[colNr] = Math.max(totalTaskHeightPerColumn[colNr], totalHeight);
    });
 
    // Reduce last "Resolved and Done" column, because it's not important
    totalTaskHeightPerColumn[numberOfColumns - 1] /= 3.5;
    totalTaskHeightPerColumn[numberOfColumns - 2] /= 1.5;
 
    // Set minimal with if only few tasks are in column, to avoid unusable space
    var approxCellHeight = Math.ceil(Math.sqrt(Math.max(...totalTaskHeightPerColumn))) + 1;
    for (let i = 0; i < totalTaskHeightPerColumn.length; i++) {
        if (totalTaskHeightPerColumn[i] <= approxCellHeight) {
            totalTaskHeightPerColumn[i] = 0;
        }
    }
 
    return percent(totalTaskHeightPerColumn);
}
 
function percent(array) {
    var sum = 0;
    for (let i = 0; i < array.length; i++) {
        sum += array[i];
    }
 
    var factor = 100.0 / sum;
 
    for (let i = 0; i < array.length; i++) {
        array[i] = Math.floor(array[i] * factor);
    }
 
    return array;
}
 
function setColumnWidths(widths, numberOfColumns) {
    for (let i = 0; i < numberOfColumns; i++) {
        document.getElementById("taskboard-table-header_s" + i).style.width = widths[i] + '%';
    }
 
    var taskboardCells = document.getElementsByClassName("taskboard-cell ui-droppable");
    for (let i = 0; i < taskboardCells.length; i++) {
        const cell = taskboardCells[i];
        cell.style.width = widths[i % numberOfColumns] + '%';
    }
}
 
function refreshTaskAlignment() {
    document.styleSheets[0].insertRule(".subColumn {display: contents}", 0); // Make disruptive subColumns ineffective
    document.styleSheets[0].insertRule(".childTbTile {float: left; margin: 2px !important}", 0);
 
    var fullscreenButton = document.querySelector("li[command='fullscreen-toggle']");
    fullscreenButton.click();
    fullscreenButton.click();
}
 
function minimizeResolvedTasks(numberOfColumns) {
    var resolvedTaskSelector = "td[axis='taskboard-table-body_s" + (numberOfColumns - 2) + "'] .childTbTile";
 
    // Hide additional fields
    document.styleSheets[0].insertRule(resolvedTaskSelector + " .additional-field {display: none}", 0);
    document.styleSheets[0].insertRule(resolvedTaskSelector + ":hover .additional-field {display: inherit}", 0);
 
    // Hide Tags
    document.styleSheets[0].insertRule(resolvedTaskSelector + " .tags {display: none}", 0);
    document.styleSheets[0].insertRule(resolvedTaskSelector + ":hover .tags {display: inherit}", 0);
}
 
function minimizeDoneTasks(numberOfColumns) {
    var doneTaskSelector = "td[axis='taskboard-table-body_s" + (numberOfColumns - 1) + "'] .childTbTile";
    document.styleSheets[0].insertRule(doneTaskSelector + " {height: 25px; overflow: hidden;}", 0);
    document.styleSheets[0].insertRule(doneTaskSelector + ":hover {height: auto;}", 0);
}
 
function getNumberOfColumns() {
    for (let i = 0; i < 100; i++) {
        if (document.getElementById("taskboard-table-header_s" + i) === null) {
            return i;
        }
    }
}