d40ace87860e49440a8ffcd09a4a68a682ad07ec chmalee Thu Apr 23 12:57:12 2026 -0700 add nowrap rules to more settings in the hubSpace data table to prevent row heights from growing when a filename is too long for the current window size and forces an adjustment by Data Tables. adjust columns when the window size grows dynamically. this should ensure the view in buttons are always the same size regardless of screen width or table content, refs Max/Baihe email diff --git src/hg/js/hgMyData.js src/hg/js/hgMyData.js index 143b8b3f6be..d9298b42ef0 100644 --- src/hg/js/hgMyData.js +++ src/hg/js/hgMyData.js @@ -1185,31 +1185,30 @@ }); } }); updateSelectedFileDiv(data, selectedRow.data().fileType === "dir"); } function createOneCrumb(table, dirName, dirFullPath, doAddEvent) { // make a new span that can be clicked to nav through the table let newSpan = document.createElement("span"); newSpan.id = dirName; newSpan.textContent = decodeURIComponent(dirName); newSpan.classList.add("breadcrumb"); if (doAddEvent) { newSpan.addEventListener("click", function(e) { dataTableShowDir(table, dirName, dirFullPath); - table.draw(); dataTableCustomOrder(table, {"fullPath": dirFullPath}); table.draw(); }); } else { // can't click the final crumb so don't underline it newSpan.style.textDecoration = "unset"; } return newSpan; } function dataTableEmptyBreadcrumb(table) { let currBreadcrumb = document.getElementById("breadcrumb"); currBreadcrumb.replaceChildren(currBreadcrumb.firstChild); } @@ -1248,32 +1247,32 @@ clearSearch(table); // deselect any selected rows like Finder et al when moving into/upto a directory table.rows({selected: true}).deselect(); table.search.fixed("showRoot", function(searchStr, rowData, rowIx) { return !rowData.parentDir; }); uiState.currentHub = ""; hideHubBanner(); } function dataTableShowDir(table, dirName, dirFullPath) { // show the directory and all immediate children of the directory clearSearch(table); // deselect any selected rows like Finder et al when moving into/upto a directory table.rows({selected: true}).deselect(); - table.draw(); - // NOTE that the below does not actually render until the next table.draw() call + // Callers must call table.draw() after this so filter + order changes + // from showDir/customOrder render in a single redraw. table.search.fixed("oneHub", function(searchStr, rowData, rowIx) { // calculate the fullPath of this rows parentDir in case the dirName passed // to this function has the same name as a parentDir further up in the // listing. For example, consider a test/test/tmp.txt layout, where "test" // is the parentDir of tmp.txt and the test subdirectory let parentDirFull = rowData.fullPath.split("/").slice(0,-1).join("/"); if (rowData.parentDir === dirName && parentDirFull === dirFullPath) { return true; } else if (rowData.fullPath === dirFullPath) { // also return the directory itself return true; } else { return false; } }); @@ -1431,30 +1430,31 @@ console.log("folder click"); let table = $("#filesTable").DataTable(); let trow = $(e.target).closest("tr"); let row = table.row(trow); dataTableShowDir(table, rowData.fileName, rowData.fullPath); dataTableCustomOrder(table, rowData); table.draw(); }); return folderIcon; } else { // only offer the button if this is a track file if (rowData.fileType !== "hub.txt" && rowData.fileType !== "text" && rowData.fileType !== "tabixIndex" && rowData.fileType !== "bamIndex" && rowData.fileType !== "2bit" && rowData.fileType in extensionMap) { let container = document.createElement("div"); let viewBtn = document.createElement("button"); viewBtn.textContent = "View in Genome Browser"; + viewBtn.style.whiteSpace = "nowrap"; viewBtn.type = 'button'; viewBtn.addEventListener("click", function(e) { e.stopPropagation(); viewInGenomeBrowser(rowData.fileName, rowData.fileType, rowData.genome, rowData.parentDir, rowData.hubType); }); container.appendChild(viewBtn); return container; } else { return null; } } } function deleteFileFromTable(pathList) { // req is an object with properties of an uploaded file, make a new row @@ -1536,63 +1536,59 @@ for (let j = 0; j < allRows.length; j++) { let rowData = table.row(allRows[j]).data(); if (rowData.fullPath === obj.fullPath) { table.row(allRows[j]).invalidate(); break; } } } } // show all the new rows we just added, note the double draw, we need // to have the new rows rendered to do the order because the order // will copy the actual DOM node parseFileListIntoHash(uiState.fileList); dataTableShowDir(table, hubDirData.fileName, hubDirData.fullPath); - table.draw(); dataTableCustomOrder(table, hubDirData); - // Flush dataTableCustomOrder's row.remove() so DataTables internal state is clean, - // then defer columns.adjust() to allow browser reflow after header DOM manipulation table.draw(); - setTimeout(function() { - table.columns.adjust(); - }, 0); } function doRowSelect(evtype, table, indexes) { let selectedRow = table.row(indexes); let rowTr = selectedRow.node(); if (rowTr) { handleCheckboxSelect(evtype, table, selectedRow); } } function indentActionButton(rowTr, rowData) { let numIndents = "0px"; //data.parentDir !== "" ? data.fullPath.split('/').length - 1: 0; if (rowData.fileType !== "dir") { numIndents = "10px"; } rowTr.childNodes[1].style.textIndent = numIndents; } let tableInitOptions = { select: { items: 'row', selector: 'td:first-child', style: 'multi+shift', // default to a single click is all that's needed }, pageLength: 25, + autoWidth: false, // let the browser's default table-layout:auto size columns, + // so they shrink/grow with the container on window resize scrollY: 600, scrollCollapse: true, // when less than scrollY height is needed, make the table shorter deferRender: true, // only draw into the DOM the nodes we need for each page orderCellsTop: true, // when viewing a subdirectory, the directory becomes a part of // the header, this option prevents those cells from being used to // sort the table layout: { top2Start: { div: { className: "", id: "breadcrumb", html: "My Data", } }, topStart: { @@ -1622,63 +1618,63 @@ return ''; } }, { targets: 2, render: function(data, type, row, meta) { let decodedName = decodeURIComponent(data); if (type !== "display" || row.fileType === "dir") { return decodedName; } if (typeof uiState.userUrl === "undefined" || uiState.userUrl.length === 0) { return decodedName; } let fileUrl = uiState.userUrl + cgiEncode(row.fullPath); let copyIcon = ''; - return '' + decodedName + '' + copyIcon; + return '' + decodedName + '' + copyIcon + ''; } }, { targets: 3, render: function(data, type, row, meta) { if (type === "display") { return dataTablePrintSize(data, type, row, meta); } return data; } }, { targets: 5, render: function(data, type, row) { if (type === "display") { return dataTablePrintGenome(data); } return data; } }, { targets: 6, render: function(data, type, row) { if (type === "display") { return cgiDecode(data); } return data; } }, { - // The upload time column - targets: 8, + targets: [7, 8], + className: "nowrap", visible: true, searchable: false, orderable: true, }, { targets: 9, visible: false, searchable: false, orderable: true, } ], columns: [ {data: "", }, {data: "", }, {data: "fileName", title: "File name"}, @@ -1732,30 +1728,38 @@ if (uiState.isLoggedIn) { tableInitOptions.language = {emptyTable: "Uploaded files will appear here. Click \"Upload\" to get started"}; } else { tableInitOptions.language = {emptyTable: "You are not logged in, please log in or create an account to begin uploading files"}; } DataTable.feature.register('quota', function(settings, opts) { let options = Object.assign({option1: false, option2: false}, opts); let container = document.createElement("div"); container.id = "quotaDiv"; if (uiState.isLoggedIn) { container.textContent = `Using ${prettyFileSize(uiState.userQuota)} of ${prettyFileSize(uiState.maxQuota)}`; } return container; }); let table = new DataTable("#filesTable", tableInitOptions); + // Re-sync the scrollY head/body column widths after a resize settles. + let resizeTimer = null; + window.addEventListener("resize", function() { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(function() { + table.columns.adjust(); + }, 100); + }); if (uiState.isLoggedIn) { table.buttons(".uploadButton").enable(); document.getElementById("rootBreadcrumb").addEventListener("click", function(e) { dataTableShowTopLevel(table); dataTableCustomOrder(table); dataTableEmptyBreadcrumb(table); table.draw(); }); } else { table.buttons(".uploadButton").disable(); } let hubBannerBtn = document.getElementById("hubBannerViewBtn"); if (hubBannerBtn) { hubBannerBtn.addEventListener("click", function(e) { viewHubInGenomeBrowser(uiState.currentHub); @@ -1819,31 +1823,30 @@ } } } }); return table; } function handleGetFileList(jsonData, textStatus) { _.assign(uiState, jsonData.userFiles); if (uiState.fileList) { parseFileListIntoHash(uiState.fileList); } // first add the top level directories/files let table = showExistingFiles(uiState.fileList); - table.columns.adjust().draw(); uppy.use(Uppy.Dashboard, uppyOptions); // define this in init so globals are available at runtime let tusOptions = { endpoint: getTusdEndpoint(), withCredentials: true, retryDelays: null, removeFingerprintOnSuccess: true, // clean up localStorage after successful upload }; uppy.use(Uppy.Tus, tusOptions); uppy.use(BatchChangePlugin, {target: Uppy.Dashboard}); uppy.on('upload-success', (file, response) => { const metadata = file.meta;