@@ -16,14 +16,18 @@ export type CollectionFilterState = {
1616 manaValueOperator : ComparisonOperator
1717 manaValue : string
1818 rarities : RarityFilter [ ]
19- set : string
19+ sets : string [ ]
2020 collectorOperator : ComparisonOperator
2121 collectorNumber : string
2222 language : string
2323 finish : FinishFilter
2424 allocation : AllocationFilter
2525 quantityOperator : ComparisonOperator
2626 quantity : string
27+ purchasePriceMin : string
28+ purchasePriceMax : string
29+ addedFrom : string
30+ addedTo : string
2731 priceOperator : ComparisonOperator
2832 priceUsd : string
2933 dateOperator : ComparisonOperator
@@ -50,14 +54,18 @@ export const EMPTY_COLLECTION_FILTERS: CollectionFilterState = {
5054 manaValueOperator : "=" ,
5155 manaValue : "" ,
5256 rarities : [ ] ,
53- set : "" ,
57+ sets : [ ] ,
5458 collectorOperator : "=" ,
5559 collectorNumber : "" ,
5660 language : "" ,
5761 finish : "any" ,
5862 allocation : "any" ,
5963 quantityOperator : ">=" ,
6064 quantity : "" ,
65+ purchasePriceMin : "" ,
66+ purchasePriceMax : "" ,
67+ addedFrom : "" ,
68+ addedTo : "" ,
6169 priceOperator : ">=" ,
6270 priceUsd : "" ,
6371 dateOperator : ">=" ,
@@ -75,12 +83,16 @@ export function buildCollectionFilterQuery(filters: CollectionFilterState) {
7583 colorPredicate ( "id" , filters . identityOperator , filters . identity ) ,
7684 comparisonPredicate ( "mv" , filters . manaValueOperator , filters . manaValue ) ,
7785 rarityPredicate ( filters . rarities ) ,
78- textPredicate ( "set" , filters . set ) ,
86+ setPredicate ( filters . sets ) ,
7987 comparisonPredicate ( "number" , filters . collectorOperator , filters . collectorNumber ) ,
8088 textPredicate ( "lang" , filters . language ) ,
8189 filters . finish === "any" ? "" : `is:${ filters . finish } ` ,
8290 filters . allocation === "any" ? "" : `is:${ filters . allocation } ` ,
8391 comparisonPredicate ( "qty" , filters . quantityOperator , filters . quantity ) ,
92+ comparisonPredicate ( "paid" , ">=" , filters . purchasePriceMin ) ,
93+ comparisonPredicate ( "paid" , "<=" , filters . purchasePriceMax ) ,
94+ comparisonPredicate ( "added" , ">=" , filters . addedFrom ) ,
95+ comparisonPredicate ( "added" , "<=" , filters . addedTo ) ,
8496 comparisonPredicate ( "usd" , filters . priceOperator , filters . priceUsd ) ,
8597 comparisonPredicate ( "date" , filters . dateOperator , filters . releasedDate ) ,
8698 comparisonPredicate ( "year" , filters . yearOperator , filters . releasedYear ) ,
@@ -106,12 +118,14 @@ export function countActiveCollectionFilters(filters: CollectionFilterState) {
106118 filters . identity . length ,
107119 filters . manaValue . trim ( ) ,
108120 filters . rarities . length ,
109- filters . set . trim ( ) ,
121+ filters . sets . length ,
110122 filters . collectorNumber . trim ( ) ,
111123 filters . language . trim ( ) ,
112124 filters . finish !== "any" ,
113125 filters . allocation !== "any" ,
114126 filters . quantity . trim ( ) ,
127+ filters . purchasePriceMin . trim ( ) || filters . purchasePriceMax . trim ( ) ,
128+ filters . addedFrom . trim ( ) || filters . addedTo . trim ( ) ,
115129 filters . priceUsd . trim ( ) ,
116130 filters . releasedDate . trim ( ) ,
117131 filters . releasedYear . trim ( ) ,
@@ -124,6 +138,7 @@ export function cloneCollectionFilters(filters: CollectionFilterState): Collecti
124138 colors : [ ...filters . colors ] ,
125139 identity : [ ...filters . identity ] ,
126140 rarities : [ ...filters . rarities ] ,
141+ sets : [ ...filters . sets ] ,
127142 }
128143}
129144
@@ -152,7 +167,7 @@ export function encodeCollectionFilters(filters: CollectionFilterState) {
152167 ? filters . manaValueOperator
153168 : undefined ,
154169 rarities : filters . rarities . length ? filters . rarities : undefined ,
155- set : trimmedValue ( filters . set ) ,
170+ sets : filters . sets . length ? filters . sets : undefined ,
156171 collectorNumber : trimmedValue ( filters . collectorNumber ) ,
157172 collectorOperator :
158173 filters . collectorNumber . trim ( ) &&
@@ -169,6 +184,10 @@ export function encodeCollectionFilters(filters: CollectionFilterState) {
169184 filters . quantityOperator !== EMPTY_COLLECTION_FILTERS . quantityOperator
170185 ? filters . quantityOperator
171186 : undefined ,
187+ purchasePriceMin : trimmedValue ( filters . purchasePriceMin ) ,
188+ purchasePriceMax : trimmedValue ( filters . purchasePriceMax ) ,
189+ addedFrom : trimmedValue ( filters . addedFrom ) ,
190+ addedTo : trimmedValue ( filters . addedTo ) ,
172191 priceUsd : trimmedValue ( filters . priceUsd ) ,
173192 priceOperator :
174193 filters . priceUsd . trim ( ) && filters . priceOperator !== EMPTY_COLLECTION_FILTERS . priceOperator
@@ -222,7 +241,10 @@ export function decodeCollectionFilters(value: unknown): CollectionFilterState {
222241 filters . manaValueOperator ,
223242 )
224243 filters . rarities = filteredValues ( decoded . rarities , RARITY_FILTERS )
225- filters . set = stringValue ( decoded . set )
244+ // Older saved filters stored a single `set` string.
245+ filters . sets = uniqueSetValues (
246+ Array . isArray ( decoded . sets ) ? decoded . sets : [ stringValue ( decoded . set ) ] ,
247+ )
226248 filters . collectorNumber = stringValue ( decoded . collectorNumber )
227249 filters . collectorOperator = operatorValue (
228250 decoded . collectorOperator ,
@@ -238,6 +260,10 @@ export function decodeCollectionFilters(value: unknown): CollectionFilterState {
238260 COMPARISON_OPERATORS ,
239261 filters . quantityOperator ,
240262 )
263+ filters . purchasePriceMin = stringValue ( decoded . purchasePriceMin )
264+ filters . purchasePriceMax = stringValue ( decoded . purchasePriceMax )
265+ filters . addedFrom = stringValue ( decoded . addedFrom )
266+ filters . addedTo = stringValue ( decoded . addedTo )
241267 filters . priceUsd = stringValue ( decoded . priceUsd )
242268 filters . priceOperator = operatorValue (
243269 decoded . priceOperator ,
@@ -304,6 +330,27 @@ function colorPredicate(field: "c" | "id", operator: ColorOperator, colors: Mana
304330 return `${ field } ${ operator } ${ colors . join ( "" ) } `
305331}
306332
333+ /** Trims, drops blanks, and removes case-insensitive duplicates, keeping the first spelling. */
334+ export function uniqueSetValues ( values : readonly unknown [ ] ) {
335+ const seen = new Set < string > ( )
336+ const sets : string [ ] = [ ]
337+ for ( const value of values ) {
338+ const set = stringValue ( value )
339+ const key = set . toLowerCase ( )
340+ if ( ! set || seen . has ( key ) ) continue
341+ seen . add ( key )
342+ sets . push ( set )
343+ }
344+ return sets
345+ }
346+
347+ function setPredicate ( sets : string [ ] ) {
348+ const terms = sets . map ( ( set ) => textPredicate ( "set" , set ) ) . filter ( Boolean )
349+ if ( ! terms . length ) return ""
350+
351+ return terms . length === 1 ? terms [ 0 ] : `(${ terms . join ( " or " ) } )`
352+ }
353+
307354function rarityPredicate ( rarities : RarityFilter [ ] ) {
308355 if ( ! rarities . length ) return ""
309356
0 commit comments