Skip to content

Commit

Permalink
Improve file search, add eslint-plugin-react-hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
TimboKZ committed Jun 21, 2020
1 parent f3f1034 commit 84f690f
Show file tree
Hide file tree
Showing 31 changed files with 405 additions and 379 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
Expand Down Expand Up @@ -49,6 +50,7 @@ module.exports = {
'simple-import-sort/sort': 'error',
'react/prop-types': 'off',
'react/display-name': 'off',
'react-hooks/exhaustive-deps': 'error',
'import/first': 'error',
'import/no-unresolved': 'off',
'import/no-namespace': 'error',
Expand All @@ -69,5 +71,8 @@ module.exports = {
},
settings: {
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
react: {
version: 'detect',
},
},
};
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -63,6 +63,7 @@
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.20.0",
"eslint-plugin-react-hooks": "^4.0.4",
"eslint-plugin-simple-import-sort": "^5.0.3",
"noty": "^3.2.0-beta",
"prettier": "^2.0.5",
Expand Down
2 changes: 1 addition & 1 deletion src/components/external/ChonkyIcon.tsx
Expand Up @@ -3,8 +3,8 @@ import { faGitAlt } from '@fortawesome/free-brands-svg-icons/faGitAlt';
import { faLinux } from '@fortawesome/free-brands-svg-icons/faLinux';
import { faNodeJs } from '@fortawesome/free-brands-svg-icons/faNodeJs';
import { faPhp } from '@fortawesome/free-brands-svg-icons/faPhp';
import { faRust } from '@fortawesome/free-brands-svg-icons/faRust';
import { faPython } from '@fortawesome/free-brands-svg-icons/faPython';
import { faRust } from '@fortawesome/free-brands-svg-icons/faRust';
import { faUbuntu } from '@fortawesome/free-brands-svg-icons/faUbuntu';
import { faWindows } from '@fortawesome/free-brands-svg-icons/faWindows';
import { faArrowDown } from '@fortawesome/free-solid-svg-icons/faArrowDown';
Expand Down
8 changes: 4 additions & 4 deletions src/components/external/DropdownButton.tsx
Expand Up @@ -8,8 +8,8 @@ import React from 'react';

import { FileAction } from '../../types/file-actions.types';
import { ChonkyIconName } from '../../types/icons.types';
import { useFileActionTrigger } from '../../util/file-actions';
import { ChonkyIconFA } from './ChonkyIcon';
import { useSmartToolbarButtonProps } from './ToolbarButton-hooks';

export interface DropdownButtonProps {
text: string;
Expand Down Expand Up @@ -48,16 +48,16 @@ export const SmartDropdownButton: React.FC<SmartDropdownButtonProps> = (props) =
const { fileAction: action } = props;

const { toolbarButton: button } = action;
if (!button) return null;

const { onClick, disabled } = useSmartToolbarButtonProps(action);
const { disabled, triggerAction } = useFileActionTrigger(action);
if (!button) return null;

return (
<DropdownButton
text={button.name}
tooltip={button.tooltip}
icon={button.icon}
onClick={onClick}
onClick={triggerAction}
disabled={disabled}
/>
);
Expand Down
24 changes: 11 additions & 13 deletions src/components/external/FileBrowser.tsx
Expand Up @@ -26,7 +26,7 @@ import { useClickListener, useStaticValue } from '../../util/hooks-helpers';
import { useFilteredFiles, useSearch } from '../../util/search';
import { useSelection } from '../../util/selection';
import { useSpecialActionDispatcher } from '../../util/special-actions';
import { useFileBrowserValidation } from '../../util/validation';
import { useFileArrayValidation } from '../../util/validation';
import { ContextComposer, ContextProviderData } from '../internal/ContextComposer';
import { DnDFileListDragLayer } from '../internal/DnDFileListDragLayer';
import { ErrorMessage } from '../internal/ErrorMessage';
Expand Down Expand Up @@ -102,7 +102,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = (props) => {
const disableSelection = !!props.disableSelection;
const enableDragAndDrop = !!props.enableDragAndDrop;

const validationResult = useFileBrowserValidation(files, folderChain);
const validationResult = useFileArrayValidation(files, folderChain);

const sortedFiles = validationResult.cleanFiles;
const cleanFolderChain = validationResult.cleanFolderChain;
Expand All @@ -112,16 +112,18 @@ export const FileBrowser: React.FC<FileBrowserProps> = (props) => {
selection,
selectionSize,
selectionUtilRef,
selectFiles,
toggleSelection,
clearSelection,
selectionModifiers,
} = useSelection(sortedFiles, disableSelection);

// TODO: Validate file actions
// TODO: Remove duplicates if they are default actions, otherwise error on
// duplicates.
const extendedFileActions = [...fileActions, ...DefaultActions];

// Deal with file text search
const { searchState, searchContexts } = useSearch();
const filteredFiles = useFilteredFiles(sortedFiles, searchState.searchFilter);

const dispatchFileAction = useFileActionDispatcher(
extendedFileActions,
onFileAction
Expand All @@ -130,19 +132,14 @@ export const FileBrowser: React.FC<FileBrowserProps> = (props) => {
sortedFiles,
selection,
selectionUtilRef.current,
selectFiles,
toggleSelection,
clearSelection,
selectionModifiers,
searchState.setSearchBarVisible,
dispatchFileAction
);

// Deal with file text search
const { searchState, searchContexts } = useSearch();
const filteredFiles = useFilteredFiles(sortedFiles, searchState.searchFilter);

// Deal with clicks outside of Chonky
const chonkyRootRef = useClickListener({
onOutsideClick: clearSelection,
onOutsideClick: selectionModifiers.clearSelection,
});

type ExtractContextType<P> = P extends React.Context<infer T> ? T : never;
Expand Down Expand Up @@ -216,6 +213,7 @@ export const FileBrowser: React.FC<FileBrowserProps> = (props) => {
provider: data.context.Provider,
value: data.value,
})),
// eslint-disable-next-line react-hooks/exhaustive-deps
contexts.map((data) => data.value)
);

Expand Down
6 changes: 2 additions & 4 deletions src/components/external/FileList-virtualization.tsx
Expand Up @@ -45,7 +45,6 @@ export const useEntryRenderer = (files: FileArray) => {
const selection = useContext(ChonkySelectionContext);
const enableDragAndDrop = useContext(ChonkyEnableDragAndDropContext);
// All hook parameters should go into `deps` array
const deps = [files, selection, enableDragAndDrop];
const entryRenderer = useCallback(
(
virtualKey: string,
Expand Down Expand Up @@ -96,7 +95,7 @@ export const useEntryRenderer = (files: FileArray) => {
</div>
);
},
deps
[files, selection, enableDragAndDrop]
);

return entryRenderer;
Expand Down Expand Up @@ -128,7 +127,6 @@ export const useGridRenderer = (
thumbsGridRef: React.Ref<Nilable<Grid>>,
fillParentContainer: boolean
) => {
const deps = [files, entrySize, entryRenderer, thumbsGridRef, fillParentContainer];
return useCallback(({ width, height }) => {
const isMobile = isMobileDevice();
const gutter = isMobile ? 5 : 8;
Expand Down Expand Up @@ -173,5 +171,5 @@ export const useGridRenderer = (
tabIndex={null}
/>
);
}, deps);
}, [files, entrySize, entryRenderer, thumbsGridRef, fillParentContainer]);
};
57 changes: 32 additions & 25 deletions src/components/external/FileSearch.tsx
Expand Up @@ -8,7 +8,6 @@ import c from 'classnames';
import React, { useCallback, useContext, useEffect, useState } from 'react';

import { ChonkyIconName } from '../../types/icons.types';
import { INTENTIONAL_EMPTY_DEPS } from '../../util/constants';
import {
ChonkySearchBarVisibleContext,
ChonkySearchFilterContext,
Expand All @@ -30,43 +29,50 @@ export const FileSearch: React.FC<FileSearchProps> = () => {
useEffect(() => {
setSearchBarEnabled(true);
return () => setSearchBarEnabled(false);
}, INTENTIONAL_EMPTY_DEPS);
}, [setSearchBarEnabled]);

// Show a loading indicator during debounce periods to help user realise that a
// debounce period is in effect.
const [showLoadingIndicator, setShowLoadingIndicator] = useState<boolean>(false);

// Define a local search filter, and update it when global search filter updates
const [localSearchFilter, setLocalSearchFilter] = useState<string>(
globalSearchFilter
);
useEffect(
() => {
setShowLoadingIndicator(false);
if (globalSearchFilter === localSearchFilter) return;
setLocalSearchFilter(globalSearchFilter);
},

// `localSearchFilter` is deliberately not included in the deps below. This
// is because we don't want to re-set local search filter to itself.
[globalSearchFilter, setShowLoadingIndicator, setLocalSearchFilter]
);
// Define a local search filter and its debounced version
const [localFilter, setLocalFilter] = useState<string>(globalSearchFilter);
const [debouncedFilter, setDebouncedFilter] = useDebounce(localFilter, 500);

// Set global search filter to local search filter with debounce
const debouncedLocalSearchFilter = useDebounce(localSearchFilter, 500);
// === Debounced global filter update
useEffect(() => {
setShowLoadingIndicator(false);
const trimmedFilter = debouncedLocalSearchFilter.trim();
if (trimmedFilter === globalSearchFilter) return;
const trimmedFilter = debouncedFilter.trim();
setGlobalSearchFilter(trimmedFilter);
}, [globalSearchFilter, setShowLoadingIndicator, debouncedLocalSearchFilter]);
}, [debouncedFilter, setShowLoadingIndicator, setGlobalSearchFilter]);

// === Search bar showing/hiding logic
const inputRef = React.useRef<HTMLInputElement>(null);
useEffect(() => {
if (searchBarVisible) {
// When the search bar is shown, focus the input
if (inputRef.current) inputRef.current.focus();
} else {
// When the search bar is hidden, clear out the search filter
setShowLoadingIndicator(false);
setLocalFilter('');
setDebouncedFilter('');
}
}, [
inputRef,
searchBarVisible,
setShowLoadingIndicator,
setLocalFilter,
setDebouncedFilter,
]);

// === Text input handler
const handleInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setShowLoadingIndicator(true);
setLocalSearchFilter(event.target.value);
setLocalFilter(event.target.value);
},
[setShowLoadingIndicator, setLocalSearchFilter]
[setShowLoadingIndicator, setLocalFilter]
);

const className = c({
Expand All @@ -80,9 +86,10 @@ export const FileSearch: React.FC<FileSearchProps> = () => {
<ChonkyIconFA icon={ChonkyIconName.search} fixedWidth={true} />
</label>
<input
ref={inputRef}
type="text"
id="chonky-file-search"
value={localSearchFilter}
value={localFilter}
placeholder="Type to search..."
onChange={handleInputChange}
/>
Expand Down
7 changes: 2 additions & 5 deletions src/components/external/FileToolbar-hooks.tsx
Expand Up @@ -19,8 +19,6 @@ import { ToolbarButtonGroup } from './ToolbarButtonGroup';
export const useFolderChainComponent = () => {
const folderChain = useContext(ChonkyFolderChainContext);
const dispatchChonkyAction = useContext(ChonkyDispatchFileActionContext);
// All hook params should go into `deps`
const deps = [folderChain, dispatchChonkyAction];
const folderChainComponent = useMemo(() => {
if (!folderChain) return folderChain;

Expand Down Expand Up @@ -76,13 +74,12 @@ export const useFolderChainComponent = () => {
}
}
return <div className="chonky-folder-chain">{comps}</div>;
}, deps);
}, [folderChain, dispatchChonkyAction]);
return folderChainComponent;
};

export const useToolbarButtonGroups = () => {
const fileActions = useContext(ChonkyFileActionsContext);
const deps = [fileActions];
return useMemo(() => {
// Create an array for normal toolbar buttons
const buttonGroups: ToolbarButtonGroup[] = [];
Expand Down Expand Up @@ -136,5 +133,5 @@ export const useToolbarButtonGroups = () => {
}

return { buttonGroups, openParentFolderButtonGroup, searchButtonGroup };
}, deps);
}, [fileActions]);
};
85 changes: 0 additions & 85 deletions src/components/external/ToolbarButton-hooks.tsx

This file was deleted.

0 comments on commit 84f690f

Please sign in to comment.