Skip to content

Commit

Permalink
refactor: Property filter controller operates internal objects (#2618)
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot authored Aug 27, 2024
1 parent d42b614 commit f195031
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 80 deletions.
73 changes: 40 additions & 33 deletions src/property-filter/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0

import { AutosuggestProps } from '../autosuggest/interfaces';
import { AutosuggestInputRef } from '../internal/components/autosuggest-input';
import { fireNonCancelableEvent, NonCancelableEventHandler } from '../internal/events';
import { I18nStringsOperators, operatorToDescription } from './i18n-utils';
import {
Expand All @@ -12,55 +11,63 @@ import {
InternalFilteringOption,
InternalFilteringProperty,
InternalFreeTextFiltering,
InternalQuery,
InternalToken,
JoinOperation,
ParsedText,
Query,
Token,
} from './interfaces';
import { matchFilteringProperty, matchOperator, matchOperatorPrefix, removeOperator, trimStart } from './utils';
import {
matchFilteringProperty,
matchOperator,
matchOperatorPrefix,
matchTokenValue,
removeOperator,
trimStart,
} from './utils';

type I18nStringsController = I18nStringsOperators &
Pick<I18nStrings, 'operatorsText' | 'groupPropertiesText' | 'groupValuesText'>;

export const getQueryActions = (
query: Query,
onChange: NonCancelableEventHandler<Query>,
inputRef: React.RefObject<AutosuggestInputRef>
) => {
const { tokens, operation } = query;
export const getQueryActions = ({
query,
onChange,
filteringOptions,
}: {
query: InternalQuery;
onChange: NonCancelableEventHandler<Query>;
filteringOptions: readonly InternalFilteringOption[];
}) => {
const fireOnChange = (tokens: readonly Token[], operation: JoinOperation) =>
fireNonCancelableEvent(onChange, { tokens, operation });
const setToken = (index: number, newToken: Token) => {
const newTokens = [...tokens];
if (newTokens && index < newTokens.length) {
newTokens[index] = newToken;
}
fireOnChange(newTokens, operation);

const setQuery = ({ operation, tokens }: InternalQuery) => {
const matchedTokens = tokens.map(token => matchTokenValue(token, filteringOptions));
fireOnChange(matchedTokens, operation);
};
const removeToken = (index: number) => {
const newTokens = tokens.filter((_, i) => i !== index);
fireOnChange(newTokens, operation);
inputRef.current?.focus({ preventDropdown: true });

const addToken = (token: InternalToken) => {
setQuery({ ...query, tokens: [...query.tokens, token] });
};
const removeAllTokens = () => {
fireOnChange([], operation);
inputRef.current?.focus({ preventDropdown: true });

const updateToken = (updateIndex: number, newToken: InternalToken) => {
setQuery({ ...query, tokens: query.tokens.map((token, index) => (index === updateIndex ? newToken : token)) });
};
const addToken = (newToken: Token) => {
const newTokens = [...tokens];
newTokens.push(newToken);
fireOnChange(newTokens, operation);

const updateOperation = (operation: JoinOperation) => {
setQuery({ ...query, operation });
};
const setOperation = (newOperation: JoinOperation) => {
fireOnChange(tokens, newOperation);

const removeToken = (removeIndex: number) => {
setQuery({ ...query, tokens: query.tokens.filter((_, index) => index !== removeIndex) });
};
return {
setToken,
removeToken,
removeAllTokens,
addToken,
setOperation,

const removeAllTokens = () => {
setQuery({ ...query, tokens: [] });
};

return { addToken, updateToken, updateOperation, removeToken, removeAllTokens };
};

export const getAllowedOperators = (property: InternalFilteringProperty): ComparisonOperator[] => {
Expand Down
54 changes: 30 additions & 24 deletions src/property-filter/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@ import {
InternalFilteringProperty,
InternalFreeTextFiltering,
InternalQuery,
InternalToken,
ParsedText,
PropertyFilterProps,
Ref,
Token,
} from './interfaces';
import { PropertyEditor } from './property-editor';
import PropertyFilterAutosuggest, { PropertyFilterAutosuggestProps } from './property-filter-autosuggest';
import { TokenButton } from './token';
import { useLoadItems } from './use-load-items';
import { matchTokenValue } from './utils';

import styles from './styles.css.js';

Expand Down Expand Up @@ -95,11 +94,6 @@ const PropertyFilterInternal = React.forwardRef(

useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
const showResults = !!query.tokens?.length && !disabled && !!countText;
const { addToken, removeToken, setToken, setOperation, removeAllTokens } = getQueryActions(
query,
onChange,
inputRef
);
const [filteringText, setFilteringText] = useState<string>('');

const { internalProperties, internalOptions, internalQuery, internalFreeText } = (() => {
Expand Down Expand Up @@ -149,6 +143,12 @@ const PropertyFilterInternal = React.forwardRef(
return { internalProperties: [...propertyByKey.values()], internalOptions, internalQuery, internalFreeText };
})();

const { addToken, updateToken, updateOperation, removeToken, removeAllTokens } = getQueryActions({
query: internalQuery,
filteringOptions: internalOptions,
onChange,
});

const parsedText = parseText(filteringText, internalProperties, internalFreeText);
const autosuggestOptions = getAutosuggestOptions(
parsedText,
Expand All @@ -160,28 +160,27 @@ const PropertyFilterInternal = React.forwardRef(

const createToken = (currentText: string) => {
const parsedText = parseText(currentText, internalProperties, internalFreeText);
let newToken: Token;
let newToken: InternalToken;
switch (parsedText.step) {
case 'property': {
newToken = matchTokenValue(
{
property: parsedText.property,
operator: parsedText.operator,
value: parsedText.value,
},
internalOptions
);
newToken = {
property: parsedText.property,
operator: parsedText.operator,
value: parsedText.value,
};
break;
}
case 'free-text': {
newToken = {
property: null,
operator: parsedText.operator || internalFreeText.defaultOperator,
value: parsedText.value,
};
break;
}
case 'operator': {
newToken = {
property: null,
operator: internalFreeText.defaultOperator,
value: currentText,
};
Expand Down Expand Up @@ -342,17 +341,21 @@ const PropertyFilterInternal = React.forwardRef(
items={internalQuery.tokens}
limitShowFewerAriaLabel={tokenLimitShowFewerAriaLabel}
limitShowMoreAriaLabel={tokenLimitShowMoreAriaLabel}
renderItem={(token, tokenIndex) => (
renderItem={(_, tokenIndex) => (
<TokenButton
token={token}
first={tokenIndex === 0}
operation={internalQuery.operation}
removeToken={() => {
query={internalQuery}
tokenIndex={tokenIndex}
onUpdateToken={token => {
updateToken(tokenIndex, token);
}}
onUpdateOperation={operation => {
updateOperation(operation);
}}
onRemoveToken={() => {
removeToken(tokenIndex);
inputRef.current?.focus({ preventDropdown: true });
setRemovedTokenIndex(tokenIndex);
}}
setToken={(newToken: Token) => setToken(tokenIndex, newToken)}
setOperation={setOperation}
filteringProperties={internalProperties}
filteringOptions={internalOptions}
asyncProps={asyncProps}
Expand All @@ -377,7 +380,10 @@ const PropertyFilterInternal = React.forwardRef(
) : (
<InternalButton
formAction="none"
onClick={removeAllTokens}
onClick={() => {
removeAllTokens();
inputRef.current?.focus({ preventDropdown: true });
}}
className={styles['remove-all']}
disabled={disabled}
>
Expand Down
6 changes: 3 additions & 3 deletions src/property-filter/property-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, { useState } from 'react';
import InternalButton from '../button/internal';
import InternalFormField from '../form-field/internal';
import { I18nStringsInternal } from './i18n-utils';
import { ComparisonOperator, ExtendedOperatorForm, InternalFilteringProperty, Token } from './interfaces';
import { ComparisonOperator, ExtendedOperatorForm, InternalFilteringProperty, InternalToken } from './interfaces';

import styles from './styles.css.js';

Expand All @@ -16,7 +16,7 @@ interface PropertyEditorProps<TokenValue> {
filter: string;
operatorForm: ExtendedOperatorForm<TokenValue>;
onCancel: () => void;
onSubmit: (value: Token) => void;
onSubmit: (value: InternalToken) => void;
i18nStrings: I18nStringsInternal;
}

Expand All @@ -30,7 +30,7 @@ export function PropertyEditor<TokenValue = any>({
i18nStrings,
}: PropertyEditorProps<TokenValue>) {
const [value, onChange] = useState<null | TokenValue>(null);
const submitToken = () => onSubmit({ propertyKey: property.propertyKey, operator, value });
const submitToken = () => onSubmit({ property, operator, value });
return (
<div className={styles['property-editor']}>
<div className={styles['property-editor-form']}>
Expand Down
39 changes: 19 additions & 20 deletions src/property-filter/token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import {
InternalFilteringOption,
InternalFilteringProperty,
InternalFreeTextFiltering,
InternalQuery,
InternalToken,
JoinOperation,
LoadItemsDetail,
Token,
} from './interfaces';
import { TokenEditor } from './token-editor';
import { matchTokenValue } from './utils';

import styles from './styles.css.js';

interface TokenProps {
query: InternalQuery;
tokenIndex: number;
onUpdateToken: (newToken: InternalToken) => void;
onUpdateOperation: (newOperation: JoinOperation) => void;
onRemoveToken: () => void;
asyncProperties?: boolean;
asyncProps: DropdownStatusProps;
customGroupsText: readonly GroupText[];
Expand All @@ -32,27 +36,20 @@ interface TokenProps {
expandToViewport?: boolean;
filteringProperties: readonly InternalFilteringProperty[];
filteringOptions: readonly InternalFilteringOption[];
first?: boolean;
hideOperations?: boolean;
i18nStrings: I18nStringsInternal;
onLoadItems?: NonCancelableEventHandler<LoadItemsDetail>;
operation: JoinOperation;
removeToken: () => void;
setOperation: (newOperation: JoinOperation) => void;
setToken: (newToken: Token) => void;
token: InternalToken;
enableTokenGroups: boolean;
}

const emptyHandler = () => {};

export const TokenButton = ({
token,
operation = 'and',
first,
removeToken,
setToken,
setOperation,
query,
onUpdateToken,
onUpdateOperation,
onRemoveToken,
tokenIndex,
filteringProperties,
filteringOptions,
asyncProps,
Expand All @@ -67,6 +64,8 @@ export const TokenButton = ({
enableTokenGroups,
}: TokenProps) => {
const tokenRef = useRef<FilteringTokenRef>(null);
const token = query.tokens[tokenIndex];
const first = tokenIndex === 0;
const formattedToken = i18nStrings.formatToken(token);
const [temporaryToken, setTemporaryToken] = useState<InternalToken>(token);
return (
Expand All @@ -84,12 +83,12 @@ export const TokenButton = ({
},
]}
showOperation={!first && !hideOperations}
operation={operation}
operation={query.operation}
andText={i18nStrings.operationAndText ?? ''}
orText={i18nStrings.operationOrText ?? ''}
operationAriaLabel={i18nStrings.tokenOperatorAriaLabel ?? ''}
onChangeOperation={setOperation}
onDismissToken={removeToken}
onChangeOperation={onUpdateOperation}
onDismissToken={onRemoveToken}
disabled={disabled}
editorContent={
<TokenEditor
Expand All @@ -110,7 +109,7 @@ export const TokenButton = ({
freeTextFiltering={freeTextFiltering}
onDismiss={() => tokenRef.current?.closeEditor()}
onSubmit={() => {
setToken(matchTokenValue(temporaryToken, filteringOptions));
onUpdateToken(temporaryToken);
tokenRef.current?.closeEditor();
}}
/>
Expand All @@ -121,9 +120,9 @@ export const TokenButton = ({
onEditorOpen={() => setTemporaryToken(token)}
// The properties below are only relevant for grouped tokens that are not supported
// by the property filter component yet.
groupOperation={operation}
groupOperation={query.operation}
groupAriaLabel={i18nStrings.formatToken(token).formattedText}
groupEditAriaLabel={i18nStrings.groupEditAriaLabel({ operation, tokens: [token] })}
groupEditAriaLabel={i18nStrings.groupEditAriaLabel({ operation: query.operation, tokens: [token] })}
onChangeGroupOperation={() => {}}
hasGroups={false}
popoverSize={enableTokenGroups ? 'content' : 'large'}
Expand Down

0 comments on commit f195031

Please sign in to comment.