Skip to content

Commit

Permalink
Merge pull request #10451 from DestinyItemManager/search-stuff
Browse files Browse the repository at this point in the history
Loadout search tweaks
  • Loading branch information
bhollis authored May 21, 2024
2 parents fbd99f4 + 45a28d9 commit 2942c4f
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 9 deletions.
4 changes: 3 additions & 1 deletion config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@
"Name": "Shows loadouts whose name matches (exactname:) or partially matches (name:) the filter text. Search for entire phrases using quotes.",
"Notes": "Search for loadouts by their notes field.",
"PartialMatch": "Shows loadouts where their name or notes has a partial match to the filter text. Search for entire phrases using quotes.",
"Season": "Shows loadouts by which season of Destiny 2 they were last modified in."
"Season": "Shows loadouts by which season of Destiny 2 they were last modified in.",
"FashionOnly": "Shows loadouts that contain only fashion (shaders or ornaments).",
"ModsOnly": "Shows loadouts that only contain armor mods."
},
"Filter": {
"Adept": "\\(Adept\\)",
Expand Down
2 changes: 1 addition & 1 deletion src/app/search/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ function SearchBar(

const lastBlurQuery = useRef<string>();
const onBlur = () => {
if (!loadouts && valid && liveQuery && liveQuery !== lastBlurQuery.current) {
if (valid && liveQuery && liveQuery !== lastBlurQuery.current) {
// save this to the recent searches only on blur
// we use the ref to only fire if the query changed since the last blur
dispatch(searchUsed(liveQuery));
Expand Down
2 changes: 1 addition & 1 deletion src/app/search/SearchFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default forwardRef(function SearchFilter(
menu={menu}
loadouts={onLoadouts}
>
{extras}
{!onLoadouts && extras}
</SearchBar>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`buildSearchConfig generates a reasonable filter map: is filters 1`] = `
[
"fashiononly",
"modsonly",
]
`;

exports[`buildSearchConfig generates a reasonable filter map: key-value filters 1`] = `
[
"exactname",
"keyword",
"name",
"notes",
"season",
]
`;
40 changes: 40 additions & 0 deletions src/app/search/loadouts/loadout-search-filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { canonicalFilterFormats } from '../filter-types';
import { buildFiltersMap } from '../search-config';
import { allLoadoutFilters } from './loadout-search-filter';

describe('buildSearchConfig', () => {
const searchConfig = buildFiltersMap(2, allLoadoutFilters);

test('generates a reasonable filter map', () => {
expect(Object.keys(searchConfig.isFilters).sort()).toMatchSnapshot('is filters');
expect(Object.keys(searchConfig.kvFilters).sort()).toMatchSnapshot('key-value filters');
});

test('filter formats specify unambiguous formats ', () => {
/*
* We have a bunch of filter formats for which `keyword:value`
* with purely alphabetic values can be valid syntax. Filters should
* avoid specifying more than one of these.
* query and freeform filters are sort of the same thing,
* except queries are exhaustive and freeform aren't. Overloaded
* range filters can also accept single words as filter value,
* because `season:worthy` is actually `season:10` and we don't
* want these to be mistaken for queries or freeforms.
*/

for (const filter of searchConfig.allFilters) {
let formats = canonicalFilterFormats(filter.format);

if (formats.length < 1) {
throw new Error(`filter ${filter.keywords} has no formats`);
}

formats = formats.filter(
(f) => f === 'query' || f === 'freeform' || (f === 'range' && filter.overload),
);
if (formats.length > 1) {
throw new Error(`filter ${filter.keywords} specifies ambiguous formats ${formats}`);
}
}
});
});
14 changes: 11 additions & 3 deletions src/app/search/loadouts/loadout-search-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import { parseAndValidateQuery } from '../search-utils';
import { LoadoutFilterContext, LoadoutSuggestionsContext } from './loadout-filter-types';
import freeformFilters from './search-filters/freeform';
import overloadedRangeFilters from './search-filters/range-overload';
import simpleFilters from './search-filters/simple';

const allLoadoutFilters = [...freeformFilters, ...overloadedRangeFilters];
export const allLoadoutFilters = [...simpleFilters, ...freeformFilters, ...overloadedRangeFilters];

//
// Selectors
Expand Down Expand Up @@ -94,6 +95,13 @@ export const loadoutFilterFactorySelector = createSelector(
export const validateLoadoutQuerySelector = createSelector(
loadoutSearchConfigSelector,
loadoutFilterContextSelector,
(searchConfig, filterContext) => (query: string) =>
parseAndValidateQuery(query, searchConfig.filtersMap, filterContext),
(searchConfig, filterContext) => (query: string) => {
const result = parseAndValidateQuery(query, searchConfig.filtersMap, filterContext);
return {
...result,
// For now, loadout searches are not saveable
saveable: false,
saveInHistory: false,
};
},
);
2 changes: 1 addition & 1 deletion src/app/search/loadouts/search-filters/range-overload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const overloadedRangeFilters: FilterDefinition<
>[] = [
{
keywords: 'season',
description: tl('Filter.Season'),
description: tl('LoadoutFilter.Season'),
format: 'range',
destinyVersion: 2,
overload: Object.fromEntries(Object.entries(seasonTagToNumber).reverse()),
Expand Down
30 changes: 30 additions & 0 deletions src/app/search/loadouts/search-filters/simple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { tl } from 'app/i18next-t';
import { Loadout } from 'app/loadout-drawer/loadout-types';
import { isArmorModsOnly, isFashionOnly } from 'app/loadout-drawer/loadout-utils';
import { FilterDefinition } from 'app/search/filter-types';
import { LoadoutFilterContext, LoadoutSuggestionsContext } from '../loadout-filter-types';

// simple checks against check an attribute found on DimItem
const simpleFilters: FilterDefinition<Loadout, LoadoutFilterContext, LoadoutSuggestionsContext>[] =
[
{
keywords: 'fashiononly',
description: tl('LoadoutFilter.FashionOnly'),
destinyVersion: 2,
filter:
({ d2Definitions }) =>
(loadout) =>
isFashionOnly(d2Definitions!, loadout),
},
{
keywords: 'modsonly',
description: tl('LoadoutFilter.ModsOnly'),
destinyVersion: 2,
filter:
({ d2Definitions }) =>
(loadout) =>
isArmorModsOnly(d2Definitions!, loadout),
},
];

export default simpleFilters;
3 changes: 1 addition & 2 deletions src/app/search/search-filters/simple.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { tl } from 'app/i18next-t';
import { DimItem } from 'app/inventory/item-types';
import { isSunset } from 'app/utils/item-utils';
import { BucketHashes } from 'data/d2/generated-enums';
import { FilterDefinition } from '../filter-types';
Expand All @@ -10,7 +9,7 @@ const simpleFilters: FilterDefinition[] = [
keywords: 'armor2.0',
description: tl('Filter.Energy'),
destinyVersion: 2,
filter: () => (item: DimItem) => Boolean(item.energy) && item.bucket.inArmor,
filter: () => (item) => Boolean(item.energy) && item.bucket.inArmor,
},
{
keywords: 'weapon',
Expand Down

0 comments on commit 2942c4f

Please sign in to comment.