diff --git a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/IconSize.tsx b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/IconSize.tsx index 829e1ebe1..551ee2ede 100644 --- a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/IconSize.tsx +++ b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/IconSize.tsx @@ -15,8 +15,8 @@ export const IconSize = () => { const explorer = useExplorerContext(); const settings = explorer.useSettingsSnapshot(); - const defaultValue = useMemo( - () => sizes.findIndex((size) => size[0] === settings.listViewIconSize), + const value = useMemo( + () => sizes.indexMap.get(settings.listViewIconSize), [settings.listViewIconSize] ); @@ -25,11 +25,11 @@ export const IconSize = () => { {t('icon_size')} { - const size = value !== undefined && sizes[value]; - if (size) explorer.settingsStore.listViewIconSize = size[0]; + const size = value !== undefined && sizes.sizeMap.get(value); + if (size) explorer.settingsStore.listViewIconSize = size; }} /> diff --git a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/TextSize.tsx b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/TextSize.tsx index 4268bf6fa..d7cb992cd 100644 --- a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/TextSize.tsx +++ b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/TextSize.tsx @@ -16,7 +16,7 @@ export const TextSize = () => { const settings = explorer.useSettingsSnapshot(); const defaultValue = useMemo( - () => sizes.findIndex((size) => size[0] === settings.listViewTextSize), + () => sizes.indexMap.get(settings.listViewTextSize), [settings.listViewTextSize] ); @@ -25,11 +25,11 @@ export const TextSize = () => { {t('text_size')} { - const size = value !== undefined && sizes[value]; - if (size) explorer.settingsStore.listViewTextSize = size[0]; + const size = value !== undefined && sizes.sizeMap.get(value); + if (size) explorer.settingsStore.listViewTextSize = size; }} /> diff --git a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/util.ts b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/util.ts index 2dd8dd5b2..8ee118a32 100644 --- a/interface/app/$libraryId/Explorer/OptionsPanel/ListView/util.ts +++ b/interface/app/$libraryId/Explorer/OptionsPanel/ListView/util.ts @@ -1,3 +1,18 @@ export function getSizes(sizes: T) { - return (Object.entries(sizes) as [keyof T, T[keyof T]][]).sort((a, b) => a[1] - b[1]); + const sizesArr = (Object.entries(sizes) as [keyof T, T[keyof T]][]).sort((a, b) => a[1] - b[1]); + + // Map fo size to index + const indexMap = new Map(); + + // Map of index to size + const sizeMap = new Map(); + + for (let i = 0; i < sizesArr.length; i++) { + const size = sizesArr[i]; + if (!size) continue; + indexMap.set(size[0], i); + sizeMap.set(i, size[0]); + } + + return { indexMap, sizeMap }; } diff --git a/interface/app/$libraryId/Explorer/OptionsPanel/index.tsx b/interface/app/$libraryId/Explorer/OptionsPanel/index.tsx index c697384c4..a764c016f 100644 --- a/interface/app/$libraryId/Explorer/OptionsPanel/index.tsx +++ b/interface/app/$libraryId/Explorer/OptionsPanel/index.tsx @@ -108,7 +108,7 @@ export default () => { onValueChange={(value) => { explorer.settingsStore.gridItemSize = value[0] || 100; }} - defaultValue={[settings.gridItemSize]} + value={[settings.gridItemSize]} max={200} step={10} min={60} diff --git a/interface/app/$libraryId/Explorer/View/index.tsx b/interface/app/$libraryId/Explorer/View/index.tsx index 1a423093d..fa324d627 100644 --- a/interface/app/$libraryId/Explorer/View/index.tsx +++ b/interface/app/$libraryId/Explorer/View/index.tsx @@ -10,7 +10,7 @@ import { } from '@sd/client'; import { dialogManager } from '@sd/ui'; import { Loader } from '~/components'; -import { useKeyMatcher, useShortcut } from '~/hooks'; +import { useKeyMatcher, useMouseItemResize, useShortcut } from '~/hooks'; import { useRoutingContext } from '~/RoutingContext'; import { isNonEmpty } from '~/util'; @@ -139,6 +139,9 @@ export const View = ({ emptyNotice, ...contextProps }: ExplorerViewProps) => { return () => element.removeEventListener('wheel', handleWheel); }, [explorer.scrollRef, drag?.type]); + // Handle resizing of items in the Explorer grid and list view using the mouse wheel + useMouseItemResize(); + if (!explorer.layouts[layoutMode]) return null; return ( diff --git a/interface/hooks/index.ts b/interface/hooks/index.ts index 91834b80b..6f2fbdf72 100644 --- a/interface/hooks/index.ts +++ b/interface/hooks/index.ts @@ -18,6 +18,9 @@ export * from './useIsLocationIndexing'; export * from './useIsTextTruncated'; export * from './useKeyMatcher'; export * from './useLocale'; +export * from './useMouseItemResize'; +export * from './usePrefersReducedMotion'; +export * from './useRandomInterval'; export * from './useRedirectToNewLocation'; export * from './useRouteTitle'; export * from './useShortcut'; @@ -25,8 +28,6 @@ export * from './useShowControls'; export * from './useTheme'; export * from './useWindowSize'; export * from './useWindowState'; +export * from './useZodParams'; export * from './useZodRouteParams'; export * from './useZodSearchParams'; -export * from './usePrefersReducedMotion'; -export * from './useRandomInterval'; -export * from './useZodParams'; diff --git a/interface/hooks/useMouseItemResize.ts b/interface/hooks/useMouseItemResize.ts new file mode 100644 index 000000000..1fce52e14 --- /dev/null +++ b/interface/hooks/useMouseItemResize.ts @@ -0,0 +1,65 @@ +import { useCallback, useEffect } from 'react'; +import { useExplorerContext } from '~/app/$libraryId/Explorer/Context'; +import { LIST_VIEW_ICON_SIZES } from '~/app/$libraryId/Explorer/View/ListView/useTable'; + +import { useOperatingSystem } from './useOperatingSystem'; +import { getSizes } from '~/app/$libraryId/Explorer/OptionsPanel/ListView/util'; + +/** + * Hook that allows resizing of items in the Explorer views for GRID and LIST only - using the mouse wheel. + */ + +export const useMouseItemResize = () => { + const os = useOperatingSystem(); + const explorer = useExplorerContext(); + const { layoutMode } = explorer.useSettingsSnapshot(); + + const handleWheel = useCallback( + (e: WheelEvent) => { + const isList = layoutMode === 'list'; + const deltaYModifier = isList ? Math.sign(e.deltaY) : e.deltaY / 10; // Sensitivity adjustment + const newSize = + Number( + isList + ? explorer.settingsStore.listViewIconSize + : explorer.settingsStore.gridItemSize + ) + deltaYModifier; + + const minSize = isList ? 0 : 60; + const maxSize = isList ? 2 : 200; + const clampedSize = Math.max(minSize, Math.min(maxSize, newSize)); + + if (isList) { + const listSizes = getSizes(LIST_VIEW_ICON_SIZES); + explorer.settingsStore.listViewIconSize = listSizes.sizeMap.get(clampedSize) ?? "0" + } else if (layoutMode === 'grid') { + explorer.settingsStore.gridItemSize = Number(clampedSize.toFixed(0)); + } + }, + [explorer.settingsStore, layoutMode] + ); + + useEffect(() => { + if (os !== 'windows') return; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Control') { + document.addEventListener('wheel', handleWheel); + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === 'Control') { + document.removeEventListener('wheel', handleWheel); + } + }; + + document.addEventListener('keydown', handleKeyDown); + document.addEventListener('keyup', handleKeyUp); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('keyup', handleKeyUp); + }; + }, [os, handleWheel]); +};