fix(dashboard): keep the name column readable beside "Shared by"

In a listing between the phone layout and about 700px wide, the fixed
160px "Shared by" column squeezed an auto-width name down to a few
characters. Beside an auto-width name, "Shared by" now takes at most 40%
of the room the two split, and resizing it stops at the same width.
This commit is contained in:
Nariman Jelveh committed 2026-10-24 15:54:21 -07:00
1 parent 60bfe4d54e
commit 291aee2e53
4 files changed
+84 -4

No files matched your search

+17 -3
View File
@@ -46,7 +46,7 @@ import { parent_path_for, parse_shared_path, shared_crumbs_for, shared_uids_from
import resolve_shared_item from '../../helpers/resolveSharedItem.js';
import { is_owned_by_me } from '../../helpers/pathOwner.js';
import parse_item_metadata from '../../helpers/parseItemMetadata.js';
import { compareSharers, filterBySharer, sharedByCellHtml, sharerFilterHtml, summarizeSharers } from './sharedBy.js';
import { compareSharers, filterBySharer, fitSharedByWidth, sharedByCellHtml, sharerFilterHtml, summarizeSharers } from './sharedBy.js';
const { html_encode, SelectionArea } = window;
@@ -2064,6 +2064,11 @@ const TabFiles = {
}
}
this.appliedColumnWidths = { name: nameWidth, sharedBy: sharedByWidth, size: sizeWidth, modified: modifiedWidth };
// Narrowed beside an auto-width name on every pass, so a resize of
// another column doesn't save the narrowed width.
if ( hasSharedBy && ! nameWidth && containerWidth > 0 ) {
sharedByWidth = fitSharedByWidth(sharedByWidth, containerWidth - COLUMN_FIXED_WIDTH - 4 - sizeWidth - modifiedWidth);
}
const nameCol = nameWidth ? `${nameWidth}px` : 'auto';
const sharedByCols = hasSharedBy ? `${sharedByWidth}px 4px ` : '';
@@ -2124,12 +2129,21 @@ const TabFiles = {
modified: this.columnWidths.modified || 120,
};
let fixed = COLUMN_FIXED_WIDTH;
// Beside an auto-width name, "Shared by" is narrowed as applyColumnWidths lays it out.
const narrowsSharedBy = this.currentPath === window.shared_path && ! this.columnWidths.name;
if ( this.currentPath === window.shared_path ) {
widths.sharedBy = this.columnWidths.sharedBy || 160;
fixed += 4;
widths.sharedBy = this.columnWidths.sharedBy || 160;
if ( narrowsSharedBy ) {
widths.sharedBy = fitSharedByWidth(widths.sharedBy, containerWidth - fixed - widths.size - widths.modified);
}
}
const others = Object.keys(widths).filter(c => c !== column).reduce((sum, c) => sum + widths[c], 0);
return Math.max(min, Math.min(width, containerWidth - fixed - others));
const clamped = Math.max(min, Math.min(width, containerWidth - fixed - others));
if ( column === 'sharedBy' && narrowsSharedBy ) {
return fitSharedByWidth(clamped, containerWidth - fixed - widths.size - widths.modified);
}
return clamped;
},
/**
+40 -1
View File
@@ -18,7 +18,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import jQuery from '../../lib/jquery-3.6.1/jquery-3.6.1.min.js';
// The tab's window and dialog plumbing isn't what these tests exercise.
@@ -535,3 +535,42 @@ describe('TabFiles.applyHiddenRule', () => {
expect($('.files .row').first().hasClass('item-revealed')).toBe(false);
});
});
describe('TabFiles "Shared by" column width', () => {
const SHARED = 'puter://shared';
beforeEach(() => {
// Earlier suites stub applyColumnWidths out.
vi.restoreAllMocks();
window.shared_path = SHARED;
window.trash_path = TRASH;
mountListing([], SHARED);
TabFiles.columnWidths = { name: null, sharedBy: 160, size: 100, modified: 120 };
// jsdom lays nothing out; give the listing a width.
vi.spyOn($.fn, 'width').mockImplementation(function () {
return this.hasClass('files') ? 530 : 0;
});
vi.spyOn(TabFiles, 'fitItemNames').mockImplementation(() => {});
});
afterEach(() => vi.restoreAllMocks());
it('leaves the auto-width name the larger part in a narrow listing', () => {
TabFiles.applyColumnWidths();
expect(TabFiles.columnGridTemplate).toBe('24px auto 4px 92px 4px 100px 4px 120px 4px 20px');
});
it('keeps its own width when another column is resized', () => {
TabFiles.applyColumnWidths();
TabFiles.adoptAppliedColumnWidths();
expect(TabFiles.columnWidths.sharedBy).toBe(160);
});
it('leaves other columns room to resize as it is laid out', () => {
expect(TabFiles.clampColumnWidth('size', 90)).toBe(90);
});
it('is resized only as far as it is laid out', () => {
expect(TabFiles.clampColumnWidth('sharedBy', 200)).toBe(92);
});
});
+11
View File
@@ -77,6 +77,17 @@ export const compareSharers = (a, b) => compareNames(sharerOf(a), sharerOf(b));
const compareNames = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' });
/**
* The "Shared by" column's width beside an auto-width name column: `width`,
* held to 40% of the `room` the two split so the name keeps the larger part,
* and never under the 60px every resizable column keeps.
*
* @param {number} width
* @param {number} room
* @returns {number}
*/
export const fitSharedByWidth = (width, room) => Math.max(60, Math.min(width, Math.floor(room * 0.4)));
/**
* The initial-and-color avatar the share modal uses, sized by `className`.
*
+16
View File
@@ -31,6 +31,7 @@ globalThis.window = {
const {
compareSharers,
filterBySharer,
fitSharedByWidth,
sharedByCellHtml,
sharedByTitle,
sharerFilterHtml,
@@ -95,6 +96,21 @@ describe('compareSharers', () => {
});
});
describe('fitSharedByWidth', () => {
it('keeps the width while the name has the larger part', () => {
expect(fitSharedByWidth(160, 1200)).toBe(160);
});
it('yields to the name in a narrow listing', () => {
// A 530px listing leaves 230px for the name and "Shared by" together.
expect(fitSharedByWidth(160, 230)).toBe(92);
});
it('never goes under the column minimum', () => {
expect(fitSharedByWidth(160, 50)).toBe(60);
});
});
describe('sharedByTitle', () => {
it('names the sharer', () => {
expect(sharedByTitle(row('ann'))).toBe('shared_by_user:ann');