Skip to content

Commit

Permalink
perf: search display and fix cache relative error (#1104)
Browse files Browse the repository at this point in the history
* perf: search display and fix cache relative error

* fix: switch view reset search params
  • Loading branch information
caoxing9 authored Nov 22, 2024
1 parent d5e0184 commit 4235c5f
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import type { IGridRef } from '@teable/sdk';
import { noop } from 'lodash';
import { create } from 'zustand';

interface IGridRefState {
gridRef: React.RefObject<IGridRef> | null;
setGridRef: (ref: React.RefObject<IGridRef>) => void;
searchCursor: [number, number] | null;
setSearchCursor: (cell: [number, number] | null) => void;
resetSearchHandler: () => void;
setResetSearchHandler: (fn: () => void) => void;
}

export const useGridSearchStore = create<IGridRefState>((set) => ({
gridRef: null,
searchCursor: null,
resetSearchHandler: noop,
setResetSearchHandler: (fn: () => void) => {
set((state) => {
return {
...state,
resetSearchHandler: fn,
};
});
},
setGridRef: (ref: React.RefObject<IGridRef>) => {
set((state) => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useTranslation } from 'next-i18next';
import { useState, useRef } from 'react';
import { useDownload } from '../../../hooks/useDownLoad';
import { VIEW_ICON_MAP } from '../constant';
import { useGridSearchStore } from '../grid/useGridSearchStore';
import { useDeleteView } from './useDeleteView';

interface IProps {
Expand All @@ -38,8 +39,10 @@ export const ViewListItem: React.FC<IProps> = ({ view, removable, isActive }) =>
downloadUrl: `/api/export/${tableId}?viewId=${view.id}`,
key: 'view',
});
const { resetSearchHandler } = useGridSearchStore();

const navigateHandler = () => {
resetSearchHandler?.();
router.push(
{
pathname: '/base/[baseId]/[tableId]/[viewId]',
Expand Down
204 changes: 112 additions & 92 deletions apps/nextjs-app/src/features/app/blocks/view/search/SearchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ export const SearchButton = (props: ISearchButtonProps) => {
const fields = useFields();
const tableId = useTableId();
const view = useView();
const viewId = view?.id;
const { fieldId, value, setFieldId, setValue, hideNotMatchRow, setHideNotMatchRow } = useSearch();

const [inputValue, setInputValue] = useState(value);
const [isFocused, setIsFocused] = useState(false);
const { t } = useTranslation(['common', 'table']);
const searchComposition = useRef(false);
const ref = useRef<HTMLInputElement>(null);
const { setSearchCursor } = useGridSearchStore();
const { setSearchCursor, setResetSearchHandler } = useGridSearchStore();
const [enableGlobalSearch, setEnableGlobalSearch] = useLocalStorage(
LocalStorageKeys.EnableGlobalSearch,
false
true
);
const [lsHideNotMatch, setLsHideNotMatchRow] = useLocalStorage<boolean>(
LocalStorageKeys.SearchHideNotMatchRow,
Expand All @@ -47,43 +49,6 @@ export const SearchButton = (props: ISearchButtonProps) => {
);
const searchPaginationRef = useRef<ISearchCountPaginationRef>(null);

useEffect(() => {
setHideNotMatchRow(lsHideNotMatch);
}, [lsHideNotMatch, setHideNotMatchRow]);

useEffect(() => {
if (!fieldId || fieldId === 'all_fields') {
return;
}
const selectedField = fieldId.split(',');
const hiddenFields: string[] = [];
const columnMeta = view?.columnMeta || {};
Object.entries(columnMeta).forEach(([key, value]) => {
value?.hidden && hiddenFields.push(key);
});
const filteredFields = selectedField.filter(
(f) => !hiddenFields.includes(f) && fields.map((f) => f.id).includes(f)
);
const defaultFieldId = fields?.[0]?.id;
if (!isEqual(filteredFields, selectedField)) {
tableId &&
setSearchFieldMap({
...searchFieldMapCache,
[tableId]: filteredFields?.length ? filteredFields : [defaultFieldId],
});
setFieldId(filteredFields.length > 0 ? filteredFields.join(',') : defaultFieldId);
}
}, [
fieldId,
fields,
searchFieldMapCache,
setFieldId,
setSearchFieldMap,
tableId,
value,
view?.columnMeta,
]);

useHotkeys(
`mod+f`,
(e) => {
Expand All @@ -110,12 +75,112 @@ export const SearchButton = (props: ISearchButtonProps) => {
setValue();
setInputValue('');
setSearchCursor(null);
setActive(false);
}, [cancel, setSearchCursor, setValue]);

useEffect(() => {
setActive(false);
resetSearch();
}, [resetSearch, view?.id]);
setResetSearchHandler(resetSearch);
}, [resetSearch, setResetSearchHandler]);

const initSearchParams = useCallback(() => {
if (!tableId || !viewId || fields.length === 0) {
return;
}

const localSearchKey = `${tableId}-${viewId}`;

if (view?.type === ViewType.Grid) {
setHideNotMatchRow(lsHideNotMatch);
} else {
// other view type only support filter search, causing the search hit highlight
setHideNotMatchRow(true);
}

if (enableGlobalSearch) {
setFieldId('all_fields');
return;
}

// set the first field as default search field
if (!searchFieldMapCache?.[localSearchKey]?.length) {
const newIds = [fields?.[0].id];
setFieldId(newIds.join(','));
setSearchFieldMap({ ...searchFieldMapCache, [localSearchKey]: newIds });
return;
}

const currentFieldIds = fields.map((f) => f.id);
const fieldIds = searchFieldMapCache[localSearchKey].filter((fieldId) =>
currentFieldIds.includes(fieldId)
);
setFieldId(fieldIds.join(','));

if (!isEqual(fieldIds, searchFieldMapCache[localSearchKey])) {
setSearchFieldMap({ ...searchFieldMapCache, [localSearchKey]: fieldIds });
}
}, [
enableGlobalSearch,
fields,
lsHideNotMatch,
searchFieldMapCache,
setFieldId,
setHideNotMatchRow,
setSearchFieldMap,
tableId,
view?.type,
viewId,
]);

useEffect(() => {
setSearchCursor(null);
}, [viewId, tableId, setSearchCursor]);

const onFieldChangeHandler = useCallback(
(fieldIds: string[] | null) => {
if (!tableId || !viewId) {
return;
}
const localSearchKey = `${tableId}-${viewId}`;
// change the search mode to field search the default from local cache or the first field
if (!fieldIds || fields.length === 0) {
if (searchFieldMapCache?.[localSearchKey]?.length) {
setFieldId(searchFieldMapCache[localSearchKey].join(','));
} else {
const newIds = [fields?.[0].id];
setFieldId(newIds.join(','));
setSearchFieldMap({ ...searchFieldMapCache, [tableId]: newIds });
}
setEnableGlobalSearch(false);
return;
}

// switch to global search or update search field
const ids = fieldIds.join(',');
if (ids === 'all_fields') {
setEnableGlobalSearch(true);
} else {
setEnableGlobalSearch(false);
setSearchFieldMap({ ...searchFieldMapCache, [localSearchKey]: fieldIds });
setFieldId(ids);
}
},
[
fields,
searchFieldMapCache,
setEnableGlobalSearch,
setFieldId,
setSearchFieldMap,
tableId,
viewId,
]
);

useEffect(() => {
if (active) {
ref.current?.focus();
initSearchParams();
}
}, [active, initSearchParams]);

useHotkeys<HTMLInputElement>(
`esc`,
Expand All @@ -130,35 +195,6 @@ export const SearchButton = (props: ISearchButtonProps) => {
}
);

useEffect(() => {
if (active) {
ref.current?.focus();
if (enableGlobalSearch) {
setFieldId('all_fields');
return;
}
// init fieldId
if (fieldId === undefined) {
if (tableId && searchFieldMapCache?.[tableId]?.length) {
setFieldId(searchFieldMapCache[tableId].join(','));
return;
}
setFieldId(fields[0].id);
}
}
}, [
active,
enableGlobalSearch,
fieldId,
fields,
hideNotMatchRow,
ref,
searchFieldMapCache,
setFieldId,
setSearchFieldMap,
tableId,
]);

const searchHeader = useMemo(() => {
if (fieldId === 'all_fields') {
return t('noun.global');
Expand Down Expand Up @@ -187,35 +223,19 @@ export const SearchButton = (props: ISearchButtonProps) => {
<Button
variant="ghost"
size={'xs'}
className="max-w-40 shrink-0 truncate rounded-none border-r"
className="flex w-[64px] shrink-0 items-center justify-center overflow-hidden truncate rounded-none border-r px-px"
>
<span className="truncate">{searchHeader}</span>
<span className="truncate" title={searchHeader}>
{searchHeader}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="max-w-96 p-1">
{fieldId && tableId && (
<SearchCommand
value={fieldId}
hideNotMatchRow={hideNotMatchRow}
onChange={(fieldIds) => {
// switch to field
if (!fieldIds || fields.length === 0) {
const newIds = searchFieldMapCache?.[tableId]?.length
? searchFieldMapCache?.[tableId]
: [fields?.[0].id];
setFieldId(newIds.join(','));
setEnableGlobalSearch(false);
return;
}
const ids = fieldIds.join(',');
if (ids === 'all_fields') {
setEnableGlobalSearch(true);
} else {
setEnableGlobalSearch(false);
tableId && setSearchFieldMap({ ...searchFieldMapCache, [tableId]: fieldIds });
}
setFieldId(ids);
}}
onChange={onFieldChangeHandler}
onHideSwitchChange={(checked) => {
setLsHideNotMatchRow(checked);
setHideNotMatchRow(checked);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useFields, useFieldStaticGetter } from '@teable/sdk/hooks';
import { ViewType } from '@teable/core';
import { useFields, useFieldStaticGetter, useView } from '@teable/sdk/hooks';
import {
Command,
CommandInput,
Expand Down Expand Up @@ -26,6 +27,7 @@ export const SearchCommand = (props: ISearchCommand) => {
const { onChange, value, hideNotMatchRow, onHideSwitchChange } = props;
const { t } = useTranslation('common');
const fields = useFields();
const view = useView();
const fieldStaticGetter = useFieldStaticGetter();

const selectedFields = useMemo(() => {
Expand Down Expand Up @@ -154,33 +156,35 @@ export const SearchCommand = (props: ISearchCommand) => {
</Toggle>
</div>

<div className="flex items-center justify-around gap-1">
<Toggle
pressed={!hideNotMatchRow}
onPressedChange={() => {
onHideSwitchChange(false);
}}
size={'sm'}
className="flex flex-1 items-center truncate p-0"
>
<span className="truncate text-sm" title={t('actions.hideNotMatchRow')}>
{t('actions.showAllRow')}
</span>
</Toggle>
{view?.type === ViewType.Grid && (
<div className="flex items-center justify-around gap-1">
<Toggle
pressed={!hideNotMatchRow}
onPressedChange={() => {
onHideSwitchChange(false);
}}
size={'sm'}
className="flex flex-1 items-center truncate p-0"
>
<span className="truncate text-sm" title={t('actions.hideNotMatchRow')}>
{t('actions.showAllRow')}
</span>
</Toggle>

<Toggle
pressed={!!hideNotMatchRow}
onPressedChange={() => {
onHideSwitchChange(true);
}}
size={'sm'}
className="flex flex-1 items-center truncate p-0"
>
<span className="truncate text-sm" title={t('actions.hideNotMatchRow')}>
{t('actions.hideNotMatchRow')}
</span>
</Toggle>
</div>
<Toggle
pressed={!!hideNotMatchRow}
onPressedChange={() => {
onHideSwitchChange(true);
}}
size={'sm'}
className="flex flex-1 items-center truncate p-0"
>
<span className="truncate text-sm" title={t('actions.hideNotMatchRow')}>
{t('actions.hideNotMatchRow')}
</span>
</Toggle>
</div>
)}
</div>
</Command>
);
Expand Down

0 comments on commit 4235c5f

Please sign in to comment.