diff --git a/src/gui/src/UI/Dashboard/TabFiles.js b/src/gui/src/UI/Dashboard/TabFiles.js index 7be30f33b..1f1b41bcb 100644 --- a/src/gui/src/UI/Dashboard/TabFiles.js +++ b/src/gui/src/UI/Dashboard/TabFiles.js @@ -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; }, /** diff --git a/src/gui/src/UI/Dashboard/TabFiles.test.js b/src/gui/src/UI/Dashboard/TabFiles.test.js index 401105890..d90675a08 100644 --- a/src/gui/src/UI/Dashboard/TabFiles.test.js +++ b/src/gui/src/UI/Dashboard/TabFiles.test.js @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -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); + }); +}); diff --git a/src/gui/src/UI/Dashboard/sharedBy.js b/src/gui/src/UI/Dashboard/sharedBy.js index 8bb8b372f..4e71b398c 100644 --- a/src/gui/src/UI/Dashboard/sharedBy.js +++ b/src/gui/src/UI/Dashboard/sharedBy.js @@ -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`. * diff --git a/src/gui/src/UI/Dashboard/sharedBy.test.js b/src/gui/src/UI/Dashboard/sharedBy.test.js index bafcf7db0..8e0b32ba4 100644 --- a/src/gui/src/UI/Dashboard/sharedBy.test.js +++ b/src/gui/src/UI/Dashboard/sharedBy.test.js @@ -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');