mirror of
https://github.com/aleksey-hoffman/sigma-file-manager.git
synced 2026-10-19 01:31:05 -05:00
Refine: right mouse click on navigator pane removes file selection
This commit is contained in:
1 parent
f35d1b505c
commit
a6139049cb
8 files changed
+219
-29
No files matched your search
+41
-2
@@ -2,7 +2,9 @@
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
afterEach, describe, expect, it, vi,
|
||||
} from 'vitest';
|
||||
import type { DirEntry } from '@/types/dir-entry';
|
||||
import {
|
||||
createFileBrowserVirtualRows,
|
||||
@@ -152,7 +154,11 @@ describe('getGridColumnCount', () => {
|
||||
});
|
||||
|
||||
describe('resolveViewportContentWidth', () => {
|
||||
it('prefers entries container width over viewport width', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('prefers entries container content-box width over viewport width', () => {
|
||||
const viewport = document.createElement('div');
|
||||
const contentInner = document.createElement('div');
|
||||
const entriesContainer = document.createElement('div');
|
||||
@@ -178,6 +184,39 @@ describe('resolveViewportContentWidth', () => {
|
||||
expect(resolveViewportContentWidth(viewport)).toBe(850);
|
||||
});
|
||||
|
||||
it('subtracts horizontal padding from the entries container width', () => {
|
||||
const viewport = document.createElement('div');
|
||||
const contentInner = document.createElement('div');
|
||||
const entriesContainer = document.createElement('div');
|
||||
|
||||
contentInner.className = 'file-browser__content-inner';
|
||||
entriesContainer.className = 'file-browser__entries-container';
|
||||
contentInner.appendChild(entriesContainer);
|
||||
viewport.appendChild(contentInner);
|
||||
|
||||
Object.defineProperty(entriesContainer, 'clientWidth', {
|
||||
value: 900,
|
||||
configurable: true,
|
||||
});
|
||||
vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
|
||||
if (element === entriesContainer) {
|
||||
return {
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
} as CSSStyleDeclaration;
|
||||
}
|
||||
|
||||
return {
|
||||
paddingLeft: '0px',
|
||||
paddingRight: '0px',
|
||||
} as CSSStyleDeclaration;
|
||||
});
|
||||
|
||||
expect(resolveViewportContentWidth(viewport)).toBe(860);
|
||||
expect(getGridColumnCount(resolveViewportContentWidth(viewport))).toBe(4);
|
||||
expect(getGridColumnCount(entriesContainer.clientWidth)).toBe(5);
|
||||
});
|
||||
|
||||
it('falls back to content inner width while entries container is absent', () => {
|
||||
const viewport = document.createElement('div');
|
||||
const contentInner = document.createElement('div');
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import {
|
||||
collectFileBrowserBoxSelectionEntries,
|
||||
type FileBrowserBoxSelectionBox,
|
||||
} from '../utils/file-browser-box-selection-hit-test';
|
||||
import { getElementContentBoxClientRect } from '../utils/file-browser-content-box';
|
||||
import type { FileBrowserVirtualRow } from '../utils/file-browser-virtual-rows';
|
||||
import { clearDocumentTextSelection } from '@/utils/document-selection';
|
||||
|
||||
@@ -162,7 +163,7 @@ export function useFileBrowserBoxSelection(options: {
|
||||
}
|
||||
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
const contentRect = entriesContainer.getBoundingClientRect();
|
||||
const contentRect = getElementContentBoxClientRect(entriesContainer);
|
||||
const boxSelectionEntries = collectFileBrowserBoxSelectionEntries({
|
||||
rows: options.virtualRows.value,
|
||||
selectionBox,
|
||||
|
||||
+6
-23
@@ -19,7 +19,7 @@ import { useDeleteJobsStore } from '@/stores/runtime/delete-jobs';
|
||||
import { toast, ToastProgress, ToastStatic } from '@/components/ui/toaster';
|
||||
import { useLanShare } from '@/composables/use-lan-share';
|
||||
import { useCopyMoveWithConflicts } from '@/composables/use-copy-move-with-conflicts';
|
||||
import normalizePath, { getPathDisplayName, joinPath } from '@/utils/normalize-path';
|
||||
import normalizePath, { joinPath } from '@/utils/normalize-path';
|
||||
import { isVirtualLocationPath } from '@/utils/virtual-locations';
|
||||
import {
|
||||
getSharedSourceDirectory,
|
||||
@@ -32,6 +32,7 @@ import { basenameFromPath } from '@/utils/source-display-name';
|
||||
import { usePermanentDeleteConfirm } from '@/composables/use-permanent-delete-confirm';
|
||||
import { usePlatformStore } from '@/stores/runtime/platform';
|
||||
import { isContextMenuActionVisible } from '@/modules/navigator/components/file-browser/utils/context-menu-action-visibility';
|
||||
import { applyBackgroundContextMenu } from '@/modules/navigator/components/file-browser/utils/file-browser-background-context-menu';
|
||||
import { resolveNavigableItemTarget } from '@/utils/resolve-navigable-item-target';
|
||||
import type { CreateLinksResult, LinkCreationKind } from '@/utils/link-operations';
|
||||
import { getFileBrowserVisualEntryOrder } from '../file-browser-entry-groups';
|
||||
@@ -300,28 +301,10 @@ export function useFileBrowserSelection(
|
||||
}
|
||||
|
||||
function handleBackgroundContextMenu() {
|
||||
clearSelection();
|
||||
|
||||
const currentDirEntry: DirEntry = {
|
||||
name: getPathDisplayName(currentPathRef.value) || currentPathRef.value,
|
||||
path: currentPathRef.value,
|
||||
is_dir: true,
|
||||
is_file: false,
|
||||
is_hidden: false,
|
||||
is_symlink: false,
|
||||
size: 0,
|
||||
created_time: 0,
|
||||
modified_time: 0,
|
||||
accessed_time: 0,
|
||||
item_count: null,
|
||||
ext: null,
|
||||
mime: null,
|
||||
};
|
||||
|
||||
contextMenu.value = {
|
||||
targetEntry: currentDirEntry,
|
||||
selectedEntries: [currentDirEntry],
|
||||
};
|
||||
contextMenu.value = applyBackgroundContextMenu({
|
||||
clearFileSelection: clearSelection,
|
||||
currentPath: currentPathRef.value,
|
||||
});
|
||||
}
|
||||
|
||||
function closeContextMenu() {
|
||||
|
||||
+4
-3
@@ -15,6 +15,7 @@ import type { DirEntry } from '@/types/dir-entry';
|
||||
import normalizePath from '@/utils/normalize-path';
|
||||
import { groupFileBrowserEntries } from '../file-browser-entry-groups';
|
||||
import { getFileBrowserGridGap } from '../utils/file-browser-layout-gaps';
|
||||
import { getElementContentBoxWidth } from '../utils/file-browser-content-box';
|
||||
import {
|
||||
getGridColumnCount,
|
||||
type FileBrowserGridEntryVariant,
|
||||
@@ -173,16 +174,16 @@ export function resolveViewportContentWidth(viewport: HTMLElement): number {
|
||||
const entriesContainer = viewport.querySelector<HTMLElement>(ENTRIES_CONTAINER_SELECTOR);
|
||||
|
||||
if (entriesContainer) {
|
||||
return entriesContainer.clientWidth;
|
||||
return getElementContentBoxWidth(entriesContainer);
|
||||
}
|
||||
|
||||
const contentInner = viewport.querySelector<HTMLElement>(CONTENT_INNER_SELECTOR);
|
||||
|
||||
if (contentInner) {
|
||||
return contentInner.clientWidth;
|
||||
return getElementContentBoxWidth(contentInner);
|
||||
}
|
||||
|
||||
return viewport.clientWidth;
|
||||
return getElementContentBoxWidth(viewport);
|
||||
}
|
||||
|
||||
function createListRows(
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
applyBackgroundContextMenu,
|
||||
createBackgroundContextMenuState,
|
||||
createCurrentDirectoryContextMenuEntry,
|
||||
} from '../file-browser-background-context-menu';
|
||||
|
||||
describe('file-browser-background-context-menu', () => {
|
||||
it('builds a directory entry for the current path', () => {
|
||||
expect(createCurrentDirectoryContextMenuEntry('/tmp/photos')).toMatchObject({
|
||||
name: 'photos',
|
||||
path: '/tmp/photos',
|
||||
is_dir: true,
|
||||
is_file: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('clears file selection and opens the directory context menu', () => {
|
||||
const clearFileSelection = vi.fn();
|
||||
const contextMenu = applyBackgroundContextMenu({
|
||||
clearFileSelection,
|
||||
currentPath: '/tmp/photos',
|
||||
});
|
||||
|
||||
expect(clearFileSelection).toHaveBeenCalledOnce();
|
||||
expect(contextMenu).toEqual(createBackgroundContextMenuState('/tmp/photos'));
|
||||
expect(contextMenu.selectedEntries).toEqual([
|
||||
expect.objectContaining({
|
||||
path: '/tmp/photos',
|
||||
is_dir: true,
|
||||
}),
|
||||
]);
|
||||
expect(contextMenu.targetEntry?.path).toBe('/tmp/photos');
|
||||
});
|
||||
});
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
import {
|
||||
afterEach, describe, expect, it, vi,
|
||||
} from 'vitest';
|
||||
import {
|
||||
getElementContentBoxClientRect,
|
||||
getElementContentBoxWidth,
|
||||
} from '../file-browser-content-box';
|
||||
|
||||
describe('file-browser-content-box', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('returns content-box width excluding horizontal padding', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
Object.defineProperty(element, 'clientWidth', {
|
||||
value: 900,
|
||||
configurable: true,
|
||||
});
|
||||
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
} as CSSStyleDeclaration);
|
||||
|
||||
expect(getElementContentBoxWidth(element)).toBe(860);
|
||||
});
|
||||
|
||||
it('returns a content-box client rect inset by padding and border', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
Object.defineProperty(element, 'clientWidth', {
|
||||
value: 900,
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(element, 'clientHeight', {
|
||||
value: 600,
|
||||
configurable: true,
|
||||
});
|
||||
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(new DOMRect(100, 200, 940, 620));
|
||||
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
||||
paddingLeft: '20px',
|
||||
paddingRight: '20px',
|
||||
paddingTop: '0px',
|
||||
paddingBottom: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
borderTopWidth: '0px',
|
||||
} as CSSStyleDeclaration);
|
||||
|
||||
expect(getElementContentBoxClientRect(element)).toEqual(new DOMRect(120, 200, 860, 600));
|
||||
});
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
import type { DirEntry } from '@/types/dir-entry';
|
||||
import type { ContextMenuState } from '@/modules/navigator/components/file-browser/types';
|
||||
import { getPathDisplayName } from '@/utils/normalize-path';
|
||||
|
||||
export function createCurrentDirectoryContextMenuEntry(currentPath: string): DirEntry {
|
||||
return {
|
||||
name: getPathDisplayName(currentPath) || currentPath,
|
||||
path: currentPath,
|
||||
is_dir: true,
|
||||
is_file: false,
|
||||
is_hidden: false,
|
||||
is_symlink: false,
|
||||
size: 0,
|
||||
created_time: 0,
|
||||
modified_time: 0,
|
||||
accessed_time: 0,
|
||||
item_count: null,
|
||||
ext: null,
|
||||
mime: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createBackgroundContextMenuState(currentPath: string): ContextMenuState {
|
||||
const currentDirEntry = createCurrentDirectoryContextMenuEntry(currentPath);
|
||||
|
||||
return {
|
||||
targetEntry: currentDirEntry,
|
||||
selectedEntries: [currentDirEntry],
|
||||
};
|
||||
}
|
||||
|
||||
export function applyBackgroundContextMenu(options: {
|
||||
clearFileSelection: () => void;
|
||||
currentPath: string;
|
||||
}): ContextMenuState {
|
||||
options.clearFileSelection();
|
||||
return createBackgroundContextMenuState(options.currentPath);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
export function getElementContentBoxWidth(element: HTMLElement): number {
|
||||
const style = getComputedStyle(element);
|
||||
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
||||
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
||||
|
||||
return Math.max(0, element.clientWidth - paddingLeft - paddingRight);
|
||||
}
|
||||
|
||||
export function getElementContentBoxClientRect(element: HTMLElement): DOMRect {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
||||
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
||||
const paddingTop = Number.parseFloat(style.paddingTop) || 0;
|
||||
const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
|
||||
const borderLeft = Number.parseFloat(style.borderLeftWidth) || 0;
|
||||
const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
|
||||
|
||||
return new DOMRect(
|
||||
rect.left + borderLeft + paddingLeft,
|
||||
rect.top + borderTop + paddingTop,
|
||||
Math.max(0, element.clientWidth - paddingLeft - paddingRight),
|
||||
Math.max(0, element.clientHeight - paddingTop - paddingBottom),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user