@akinon/pz-similar-products 1.92.0-snapshot-ZERO-3457-20250627111231 → 1.93.0-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -2
- package/README.md +248 -3
- package/package.json +1 -1
- package/src/data/endpoints.ts +37 -9
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-similar-products.ts +175 -27
- package/src/hooks/use-text-search-feature.ts +41 -0
- package/src/types/index.ts +66 -0
- package/src/utils/index.ts +38 -3
- package/src/views/filters.tsx +713 -645
- package/src/views/header-image-search-feature.tsx +13 -1
- package/src/views/image-search-button.tsx +20 -18
- package/src/views/image-search.tsx +98 -86
- package/src/views/main.tsx +92 -7
- package/src/views/product-image-search-feature.tsx +17 -3
- package/src/views/results.tsx +7 -5
- package/src/views/search-button.tsx +12 -2
- package/src/views/search-modal.tsx +173 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import { Product } from '@akinon/next/types';
|
|
3
3
|
import {
|
|
4
4
|
useLazyGetSimilarProductsByUrlQuery,
|
|
@@ -13,17 +13,19 @@ import {
|
|
|
13
13
|
validateImageFromDataUrl,
|
|
14
14
|
type ImageValidationResult
|
|
15
15
|
} from '../utils/image-validation';
|
|
16
|
+
import { debounce } from '../utils';
|
|
16
17
|
|
|
17
18
|
type SearchResults = SimilarProductsListResponse;
|
|
18
19
|
|
|
19
20
|
interface SearchParams {
|
|
20
|
-
[key: string]: string;
|
|
21
|
+
[key: string]: string | string[];
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function useSimilarProducts(product: Product) {
|
|
24
25
|
const { t } = useLocalization();
|
|
25
26
|
const [currentImageUrl, setCurrentImageUrl] = useState('');
|
|
26
27
|
const [fileError, setFileError] = useState('');
|
|
28
|
+
const [searchText, setSearchText] = useState('');
|
|
27
29
|
const [searchResults, setSearchResults] = useState<SearchResults | null>(
|
|
28
30
|
null
|
|
29
31
|
);
|
|
@@ -33,6 +35,16 @@ export function useSimilarProducts(product: Product) {
|
|
|
33
35
|
const [isCropProcessing, setIsCropProcessing] = useState(false);
|
|
34
36
|
const [loadedPages, setLoadedPages] = useState<Set<number>>(new Set([1]));
|
|
35
37
|
const [allLoadedProducts, setAllLoadedProducts] = useState<Product[]>([]);
|
|
38
|
+
const [currentImageBase64, setCurrentImageBase64] = useState<string>('');
|
|
39
|
+
const [hasCroppedImage, setHasCroppedImage] = useState(false);
|
|
40
|
+
|
|
41
|
+
const searchTextRef = useRef<string>('');
|
|
42
|
+
const hasUploadedImageRef = useRef<boolean>(false);
|
|
43
|
+
|
|
44
|
+
const setSearchTextWithRef = useCallback((text: string) => {
|
|
45
|
+
setSearchText(text);
|
|
46
|
+
searchTextRef.current = text;
|
|
47
|
+
}, []);
|
|
36
48
|
|
|
37
49
|
const [fetchSimilarProductsByUrl, { isLoading: isUrlSearchLoading }] =
|
|
38
50
|
useLazyGetSimilarProductsByUrlQuery();
|
|
@@ -103,7 +115,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
103
115
|
String(choice.value)
|
|
104
116
|
);
|
|
105
117
|
const searchKey = String(facet.search_key || facet.key);
|
|
106
|
-
searchParams[searchKey] = values.
|
|
118
|
+
searchParams[searchKey] = values.length === 1 ? values[0] : values;
|
|
107
119
|
}
|
|
108
120
|
});
|
|
109
121
|
}
|
|
@@ -338,7 +350,9 @@ export function useSimilarProducts(product: Product) {
|
|
|
338
350
|
const productIds = extractProductIds(data);
|
|
339
351
|
|
|
340
352
|
if (productIds.length > 0) {
|
|
341
|
-
const
|
|
353
|
+
const searchParams = buildSearchParams(searchResults?.facets);
|
|
354
|
+
|
|
355
|
+
const result = await executeProductSearch(productIds, searchParams);
|
|
342
356
|
if (result) {
|
|
343
357
|
const resultWithCorrectSorterState = {
|
|
344
358
|
...result,
|
|
@@ -357,7 +371,11 @@ export function useSimilarProducts(product: Product) {
|
|
|
357
371
|
executeProductSearch,
|
|
358
372
|
updateSorterState,
|
|
359
373
|
updateResultsAndKey,
|
|
360
|
-
createEmptySearchResults
|
|
374
|
+
createEmptySearchResults,
|
|
375
|
+
searchText,
|
|
376
|
+
searchTextRef,
|
|
377
|
+
buildSearchParams,
|
|
378
|
+
searchResults
|
|
361
379
|
]
|
|
362
380
|
);
|
|
363
381
|
|
|
@@ -426,17 +444,24 @@ export function useSimilarProducts(product: Product) {
|
|
|
426
444
|
);
|
|
427
445
|
|
|
428
446
|
const fetchSimilarProductsByImageUrl = useCallback(
|
|
429
|
-
async (imageUrl: string) => {
|
|
447
|
+
async (imageUrl: string, overrideText?: string) => {
|
|
430
448
|
setFileError('');
|
|
449
|
+
|
|
431
450
|
try {
|
|
432
451
|
const productPk = product?.pk;
|
|
433
452
|
const excludedIds = productPk ? [productPk] : undefined;
|
|
434
453
|
|
|
435
|
-
const
|
|
454
|
+
const textToUse =
|
|
455
|
+
overrideText !== undefined ? overrideText : searchTextRef.current;
|
|
456
|
+
|
|
457
|
+
const requestParams = {
|
|
436
458
|
url: imageUrl,
|
|
437
459
|
limit: 20,
|
|
438
|
-
excluded_product_ids: excludedIds
|
|
439
|
-
|
|
460
|
+
excluded_product_ids: excludedIds,
|
|
461
|
+
text: textToUse || undefined
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const result = await fetchSimilarProductsByUrl(requestParams).unwrap();
|
|
440
465
|
|
|
441
466
|
await handleSearchResults(result);
|
|
442
467
|
return result;
|
|
@@ -450,6 +475,8 @@ export function useSimilarProducts(product: Product) {
|
|
|
450
475
|
[
|
|
451
476
|
fetchSimilarProductsByUrl,
|
|
452
477
|
product?.pk,
|
|
478
|
+
searchText,
|
|
479
|
+
searchTextRef,
|
|
453
480
|
handleSearchResults,
|
|
454
481
|
updateResultsAndKey,
|
|
455
482
|
createEmptySearchResults,
|
|
@@ -462,31 +489,62 @@ export function useSimilarProducts(product: Product) {
|
|
|
462
489
|
if (product?.productimage_set?.length > 0) {
|
|
463
490
|
const initialImageUrl = product.productimage_set[0].image;
|
|
464
491
|
setCurrentImageUrl(initialImageUrl);
|
|
492
|
+
setHasCroppedImage(false);
|
|
493
|
+
setCurrentImageBase64('');
|
|
494
|
+
setHasUploadedImage(false);
|
|
495
|
+
hasUploadedImageRef.current = false;
|
|
465
496
|
}
|
|
466
497
|
}, [product]);
|
|
467
498
|
|
|
468
499
|
const fetchSimilarProductsByBase64 = useCallback(
|
|
469
|
-
async (
|
|
500
|
+
async (
|
|
501
|
+
base64Image: string,
|
|
502
|
+
overrideText?: string,
|
|
503
|
+
forceExclude?: boolean
|
|
504
|
+
) => {
|
|
470
505
|
const base64Data = base64Image.startsWith('data:')
|
|
471
506
|
? base64Image.split(',')[1]
|
|
472
507
|
: base64Image;
|
|
473
508
|
|
|
509
|
+
const textToUse =
|
|
510
|
+
overrideText !== undefined ? overrideText : searchTextRef.current;
|
|
511
|
+
|
|
512
|
+
const shouldExclude =
|
|
513
|
+
forceExclude !== undefined ? forceExclude : !hasUploadedImage;
|
|
514
|
+
|
|
515
|
+
const requestData = {
|
|
516
|
+
image: base64Data,
|
|
517
|
+
limit: 20,
|
|
518
|
+
excluded_product_ids: shouldExclude
|
|
519
|
+
? product?.pk
|
|
520
|
+
? [product.pk]
|
|
521
|
+
: undefined
|
|
522
|
+
: undefined,
|
|
523
|
+
text: textToUse || undefined
|
|
524
|
+
};
|
|
525
|
+
|
|
474
526
|
return handleImageSearch(
|
|
475
|
-
async () =>
|
|
476
|
-
getSimilarProductsByImage({
|
|
477
|
-
image: base64Data,
|
|
478
|
-
limit: 20
|
|
479
|
-
}).unwrap(),
|
|
527
|
+
async () => getSimilarProductsByImage(requestData).unwrap(),
|
|
480
528
|
base64Image,
|
|
481
529
|
'Image search'
|
|
482
530
|
);
|
|
483
531
|
},
|
|
484
|
-
[
|
|
532
|
+
[
|
|
533
|
+
getSimilarProductsByImage,
|
|
534
|
+
searchText,
|
|
535
|
+
searchTextRef,
|
|
536
|
+
handleImageSearch,
|
|
537
|
+
hasUploadedImage,
|
|
538
|
+
product?.pk
|
|
539
|
+
]
|
|
485
540
|
);
|
|
486
541
|
|
|
487
542
|
const fetchSimilarProductsByImageCrop = useCallback(
|
|
488
|
-
async (dataString: string) => {
|
|
543
|
+
async (dataString: string, excludeCurrentProduct: boolean = true) => {
|
|
489
544
|
setFileError('');
|
|
545
|
+
|
|
546
|
+
setCurrentImageBase64(dataString);
|
|
547
|
+
setHasCroppedImage(true);
|
|
490
548
|
if (dataString.startsWith('data:application/x-cors-fallback;base64,')) {
|
|
491
549
|
try {
|
|
492
550
|
const fallbackDataEncoded = dataString.replace(
|
|
@@ -549,9 +607,12 @@ export function useSimilarProducts(product: Product) {
|
|
|
549
607
|
getSimilarProductsByImage({
|
|
550
608
|
image: base64Data,
|
|
551
609
|
limit: 20,
|
|
552
|
-
excluded_product_ids:
|
|
553
|
-
?
|
|
554
|
-
|
|
610
|
+
excluded_product_ids: excludeCurrentProduct
|
|
611
|
+
? product?.pk
|
|
612
|
+
? [product.pk]
|
|
613
|
+
: undefined
|
|
614
|
+
: undefined,
|
|
615
|
+
text: searchTextRef.current || undefined
|
|
555
616
|
}).unwrap(),
|
|
556
617
|
croppedBase64,
|
|
557
618
|
'Proxy crop search'
|
|
@@ -601,7 +662,12 @@ export function useSimilarProducts(product: Product) {
|
|
|
601
662
|
getSimilarProductsByImage({
|
|
602
663
|
image: base64Data,
|
|
603
664
|
limit: 20,
|
|
604
|
-
excluded_product_ids:
|
|
665
|
+
excluded_product_ids: excludeCurrentProduct
|
|
666
|
+
? product?.pk
|
|
667
|
+
? [product.pk]
|
|
668
|
+
: undefined
|
|
669
|
+
: undefined,
|
|
670
|
+
text: searchTextRef.current || undefined
|
|
605
671
|
}).unwrap(),
|
|
606
672
|
dataString,
|
|
607
673
|
'Image crop search'
|
|
@@ -609,11 +675,14 @@ export function useSimilarProducts(product: Product) {
|
|
|
609
675
|
},
|
|
610
676
|
[
|
|
611
677
|
getSimilarProductsByImage,
|
|
678
|
+
searchText,
|
|
679
|
+
searchTextRef,
|
|
612
680
|
handleImageSearch,
|
|
613
681
|
product,
|
|
614
682
|
fetchSimilarProductsByImageUrl,
|
|
615
683
|
setFileError,
|
|
616
|
-
t
|
|
684
|
+
t,
|
|
685
|
+
hasUploadedImage
|
|
617
686
|
]
|
|
618
687
|
);
|
|
619
688
|
|
|
@@ -624,6 +693,8 @@ export function useSimilarProducts(product: Product) {
|
|
|
624
693
|
const file = event.target.files?.[0];
|
|
625
694
|
if (!file) return;
|
|
626
695
|
|
|
696
|
+
setSearchTextWithRef('');
|
|
697
|
+
|
|
627
698
|
try {
|
|
628
699
|
let processedFile = file;
|
|
629
700
|
|
|
@@ -650,6 +721,9 @@ export function useSimilarProducts(product: Product) {
|
|
|
650
721
|
const dataUrl = e.target?.result as string;
|
|
651
722
|
setCurrentImageUrl(dataUrl);
|
|
652
723
|
setHasUploadedImage(true);
|
|
724
|
+
hasUploadedImageRef.current = true;
|
|
725
|
+
setHasCroppedImage(false);
|
|
726
|
+
setCurrentImageBase64('');
|
|
653
727
|
|
|
654
728
|
const metadataValidation = await validateImageFromDataUrl(dataUrl);
|
|
655
729
|
if (!metadataValidation.isValid) {
|
|
@@ -658,7 +732,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
658
732
|
);
|
|
659
733
|
return;
|
|
660
734
|
}
|
|
661
|
-
fetchSimilarProductsByBase64(dataUrl);
|
|
735
|
+
fetchSimilarProductsByBase64(dataUrl, undefined, false);
|
|
662
736
|
};
|
|
663
737
|
|
|
664
738
|
reader.onerror = () =>
|
|
@@ -679,6 +753,8 @@ export function useSimilarProducts(product: Product) {
|
|
|
679
753
|
|
|
680
754
|
try {
|
|
681
755
|
setCurrentImageUrl(base64Image);
|
|
756
|
+
setCurrentImageBase64(base64Image);
|
|
757
|
+
setHasCroppedImage(true);
|
|
682
758
|
|
|
683
759
|
const metadataValidation = await validateImageFromDataUrl(base64Image);
|
|
684
760
|
if (!metadataValidation.isValid) {
|
|
@@ -688,10 +764,14 @@ export function useSimilarProducts(product: Product) {
|
|
|
688
764
|
return;
|
|
689
765
|
}
|
|
690
766
|
|
|
691
|
-
if (
|
|
692
|
-
await fetchSimilarProductsByBase64(
|
|
767
|
+
if (hasUploadedImageRef.current) {
|
|
768
|
+
await fetchSimilarProductsByBase64(
|
|
769
|
+
base64Image,
|
|
770
|
+
searchTextRef.current,
|
|
771
|
+
false
|
|
772
|
+
);
|
|
693
773
|
} else {
|
|
694
|
-
await fetchSimilarProductsByImageCrop(base64Image);
|
|
774
|
+
await fetchSimilarProductsByImageCrop(base64Image, true);
|
|
695
775
|
}
|
|
696
776
|
} catch (error) {
|
|
697
777
|
setFileError(t('common.similar_products.errors.crop_processing_error'));
|
|
@@ -874,6 +954,19 @@ export function useSimilarProducts(product: Product) {
|
|
|
874
954
|
setFileError('');
|
|
875
955
|
}, []);
|
|
876
956
|
|
|
957
|
+
const clearResults = useCallback(() => {
|
|
958
|
+
setSearchResults(null);
|
|
959
|
+
setResultsKey(0);
|
|
960
|
+
setLastProductIds([]);
|
|
961
|
+
setAllLoadedProducts([]);
|
|
962
|
+
setLoadedPages(new Set([1]));
|
|
963
|
+
}, []);
|
|
964
|
+
|
|
965
|
+
const resetCropState = useCallback(() => {
|
|
966
|
+
setHasCroppedImage(false);
|
|
967
|
+
setCurrentImageBase64('');
|
|
968
|
+
}, []);
|
|
969
|
+
|
|
877
970
|
const clearFileInput = useCallback(
|
|
878
971
|
(fileInputRef: React.RefObject<HTMLInputElement>) => {
|
|
879
972
|
if (fileInputRef.current) {
|
|
@@ -883,6 +976,55 @@ export function useSimilarProducts(product: Product) {
|
|
|
883
976
|
[]
|
|
884
977
|
);
|
|
885
978
|
|
|
979
|
+
const handleTextSearch = useCallback(async () => {
|
|
980
|
+
const textToUse = searchTextRef.current || searchText;
|
|
981
|
+
|
|
982
|
+
if (hasCroppedImage && currentImageBase64) {
|
|
983
|
+
await fetchSimilarProductsByBase64(
|
|
984
|
+
currentImageBase64,
|
|
985
|
+
textToUse,
|
|
986
|
+
!hasUploadedImage
|
|
987
|
+
);
|
|
988
|
+
} else if (hasUploadedImage && currentImageUrl) {
|
|
989
|
+
await fetchSimilarProductsByBase64(currentImageUrl, textToUse, false);
|
|
990
|
+
} else if (currentImageUrl) {
|
|
991
|
+
await fetchSimilarProductsByImageUrl(currentImageUrl, textToUse);
|
|
992
|
+
}
|
|
993
|
+
}, [
|
|
994
|
+
searchText,
|
|
995
|
+
searchTextRef,
|
|
996
|
+
currentImageUrl,
|
|
997
|
+
currentImageBase64,
|
|
998
|
+
hasCroppedImage,
|
|
999
|
+
hasUploadedImage,
|
|
1000
|
+
fetchSimilarProductsByImageUrl,
|
|
1001
|
+
fetchSimilarProductsByBase64
|
|
1002
|
+
]);
|
|
1003
|
+
|
|
1004
|
+
const handleClearText = useCallback(async () => {
|
|
1005
|
+
setSearchTextWithRef('');
|
|
1006
|
+
|
|
1007
|
+
if (hasCroppedImage && currentImageBase64) {
|
|
1008
|
+
await fetchSimilarProductsByBase64(
|
|
1009
|
+
currentImageBase64,
|
|
1010
|
+
'',
|
|
1011
|
+
!hasUploadedImage
|
|
1012
|
+
);
|
|
1013
|
+
} else if (hasUploadedImage && currentImageUrl) {
|
|
1014
|
+
await fetchSimilarProductsByBase64(currentImageUrl, '', false);
|
|
1015
|
+
} else if (currentImageUrl) {
|
|
1016
|
+
await fetchSimilarProductsByImageUrl(currentImageUrl, '');
|
|
1017
|
+
}
|
|
1018
|
+
}, [
|
|
1019
|
+
hasCroppedImage,
|
|
1020
|
+
currentImageBase64,
|
|
1021
|
+
currentImageUrl,
|
|
1022
|
+
hasUploadedImage,
|
|
1023
|
+
fetchSimilarProductsByBase64,
|
|
1024
|
+
fetchSimilarProductsByImageUrl,
|
|
1025
|
+
setSearchTextWithRef
|
|
1026
|
+
]);
|
|
1027
|
+
|
|
886
1028
|
const handleLoadMore = async () => {
|
|
887
1029
|
if (!searchResults?.pagination || isLoading) return;
|
|
888
1030
|
|
|
@@ -923,6 +1065,8 @@ export function useSimilarProducts(product: Product) {
|
|
|
923
1065
|
setCurrentImageUrl,
|
|
924
1066
|
isLoading,
|
|
925
1067
|
fileError,
|
|
1068
|
+
searchText,
|
|
1069
|
+
setSearchText: setSearchTextWithRef,
|
|
926
1070
|
searchResults,
|
|
927
1071
|
resultsKey,
|
|
928
1072
|
hasUploadedImage,
|
|
@@ -940,6 +1084,10 @@ export function useSimilarProducts(product: Product) {
|
|
|
940
1084
|
loadedPages: Array.from(loadedPages),
|
|
941
1085
|
allLoadedProducts,
|
|
942
1086
|
clearError,
|
|
943
|
-
|
|
1087
|
+
clearResults,
|
|
1088
|
+
clearFileInput,
|
|
1089
|
+
handleTextSearch,
|
|
1090
|
+
handleClearText,
|
|
1091
|
+
resetCropState
|
|
944
1092
|
};
|
|
945
1093
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const TEXT_SEARCH_STORAGE_KEY = 'enable_image_search_text';
|
|
4
|
+
|
|
5
|
+
interface UseTextSearchFeatureProps {
|
|
6
|
+
enableTextSearch?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useTextSearchFeature(props?: UseTextSearchFeatureProps) {
|
|
10
|
+
const [isEnabled, setIsEnabled] = useState(false);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const envEnabled = process.env.NEXT_PUBLIC_ENABLE_TEXT_SEARCH === 'true';
|
|
15
|
+
|
|
16
|
+
if (envEnabled) {
|
|
17
|
+
setIsEnabled(true);
|
|
18
|
+
setIsLoading(false);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const localStorageValue = localStorage.getItem(TEXT_SEARCH_STORAGE_KEY);
|
|
24
|
+
|
|
25
|
+
if (localStorageValue === 'true') {
|
|
26
|
+
setIsEnabled(true);
|
|
27
|
+
} else {
|
|
28
|
+
setIsEnabled(!!props?.enableTextSearch);
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
setIsEnabled(false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setIsLoading(false);
|
|
35
|
+
}, [props?.enableTextSearch]);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
isEnabled,
|
|
39
|
+
isLoading
|
|
40
|
+
};
|
|
41
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -58,6 +58,10 @@ export interface SimilarProductsModalProps {
|
|
|
58
58
|
showResetButton?: boolean;
|
|
59
59
|
settings?: any;
|
|
60
60
|
className?: string;
|
|
61
|
+
searchText?: string;
|
|
62
|
+
setSearchText?: (text: string) => void;
|
|
63
|
+
handleTextSearch?: () => void;
|
|
64
|
+
handleClearText?: () => void;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export interface FilterSidebarProps {
|
|
@@ -67,6 +71,8 @@ export interface FilterSidebarProps {
|
|
|
67
71
|
isLoading: boolean;
|
|
68
72
|
handleFacetChange: (facetKey: string, choiceValue: string | number) => void;
|
|
69
73
|
removeFacetFilter: (facetKey: string, choiceValue: string | number) => void;
|
|
74
|
+
searchText?: string;
|
|
75
|
+
setSearchText?: (text: string) => void;
|
|
70
76
|
currentImageUrl: string;
|
|
71
77
|
isCropping: boolean;
|
|
72
78
|
imageRef: React.RefObject<HTMLImageElement>;
|
|
@@ -126,6 +132,8 @@ export interface CustomRendererProps {
|
|
|
126
132
|
onSortChange: (value: string) => void;
|
|
127
133
|
onFilterMenuToggle: () => void;
|
|
128
134
|
isLoading: boolean;
|
|
135
|
+
searchText?: string;
|
|
136
|
+
setSearchText?: (text: string) => void;
|
|
129
137
|
}) => React.ReactNode;
|
|
130
138
|
renderSortDropdown?: (props: {
|
|
131
139
|
sorters: SortOption[];
|
|
@@ -141,6 +149,17 @@ export interface CustomRendererProps {
|
|
|
141
149
|
onClick: () => void;
|
|
142
150
|
isLoading: boolean;
|
|
143
151
|
}) => React.ReactNode;
|
|
152
|
+
renderModalSearchInput?: (props: {
|
|
153
|
+
searchText: string;
|
|
154
|
+
setSearchText: (text: string) => void;
|
|
155
|
+
isLoading: boolean;
|
|
156
|
+
placeholder: string;
|
|
157
|
+
onSearch?: () => void;
|
|
158
|
+
}) => React.ReactNode;
|
|
159
|
+
renderSearchIcon?: (props: {
|
|
160
|
+
disabled: boolean;
|
|
161
|
+
onClick?: () => void;
|
|
162
|
+
}) => React.ReactNode;
|
|
144
163
|
renderEmptyState?: () => React.ReactNode;
|
|
145
164
|
};
|
|
146
165
|
filterSidebar?: {
|
|
@@ -219,6 +238,12 @@ export interface CustomRendererProps {
|
|
|
219
238
|
onClearAll: () => void;
|
|
220
239
|
isLoading: boolean;
|
|
221
240
|
}) => React.ReactNode;
|
|
241
|
+
renderTextSearch?: (props: {
|
|
242
|
+
searchText: string;
|
|
243
|
+
setSearchText: (text: string) => void;
|
|
244
|
+
isLoading: boolean;
|
|
245
|
+
placeholder: string;
|
|
246
|
+
}) => React.ReactNode;
|
|
222
247
|
};
|
|
223
248
|
resultsGrid?: {
|
|
224
249
|
renderGrid?: (props: ResultsGridProps) => React.ReactNode;
|
|
@@ -309,6 +334,7 @@ export interface SimilarProductsSettings {
|
|
|
309
334
|
resultsPerPage?: number;
|
|
310
335
|
enableCropping?: boolean;
|
|
311
336
|
enableFileUpload?: boolean;
|
|
337
|
+
enableTextSearch?: boolean;
|
|
312
338
|
paginationType?: 'pagination' | 'load-more' | 'infinite-scroll';
|
|
313
339
|
loadMoreText?: string;
|
|
314
340
|
loadMoreStyle?: 'button' | 'auto';
|
|
@@ -329,10 +355,22 @@ export interface SimilarProductsSettings {
|
|
|
329
355
|
controlsContainer?: string;
|
|
330
356
|
controlsInner?: string;
|
|
331
357
|
controlsLeft?: string;
|
|
358
|
+
controlsCenter?: string;
|
|
332
359
|
controlsRight?: string;
|
|
333
360
|
itemCount?: string;
|
|
334
361
|
sortDropdown?: string;
|
|
335
362
|
filterToggleButton?: string;
|
|
363
|
+
modalSearchInput?: string;
|
|
364
|
+
modalSearchContainer?: string;
|
|
365
|
+
modalSearchButton?: string;
|
|
366
|
+
modalSearchIcon?: string;
|
|
367
|
+
modalSearchClearButton?: string;
|
|
368
|
+
modalSearchClearIcon?: string;
|
|
369
|
+
searchButtonIcon?: string;
|
|
370
|
+
modalCloseIcon?: string;
|
|
371
|
+
filterIcon?: string;
|
|
372
|
+
filterRemoveIcon?: string;
|
|
373
|
+
imageSearchButtonIcon?: string;
|
|
336
374
|
|
|
337
375
|
filterSidebarMobileHeader?: string;
|
|
338
376
|
filterSidebarMobileTitle?: string;
|
|
@@ -357,6 +395,19 @@ export interface SimilarProductsSettings {
|
|
|
357
395
|
errorMessage?: string;
|
|
358
396
|
cropControls?: string;
|
|
359
397
|
|
|
398
|
+
// Detailed crop styling
|
|
399
|
+
cropComponent?: string;
|
|
400
|
+
cropImage?: string;
|
|
401
|
+
cropImageActive?: string;
|
|
402
|
+
cropOverlay?: string;
|
|
403
|
+
cropSelection?: string;
|
|
404
|
+
cropSelectionBorder?: string;
|
|
405
|
+
cropImageNonCropping?: string;
|
|
406
|
+
cropImageContainer?: string;
|
|
407
|
+
cropImageWrapper?: string;
|
|
408
|
+
cropOverlayBackground?: string;
|
|
409
|
+
cropSelectionHighlight?: string;
|
|
410
|
+
|
|
360
411
|
resultsContainer?: string;
|
|
361
412
|
gridContainer?: string;
|
|
362
413
|
productItem?: string;
|
|
@@ -395,10 +446,25 @@ export interface SimilarProductsSettings {
|
|
|
395
446
|
imageSearchTipsTitle?: string;
|
|
396
447
|
imageSearchTipsList?: string;
|
|
397
448
|
imageSearchTipsItem?: string;
|
|
449
|
+
imageSearchModalOverlay?: string;
|
|
398
450
|
|
|
399
451
|
mobileActiveFilters?: string;
|
|
400
452
|
mobileActiveFilterTag?: string;
|
|
401
453
|
mobileClearAllButton?: string;
|
|
454
|
+
filterSidebarMobileOverlay?: string;
|
|
455
|
+
|
|
456
|
+
textSearchContainer?: string;
|
|
457
|
+
textSearchLabel?: string;
|
|
458
|
+
textSearchInput?: string;
|
|
459
|
+
};
|
|
460
|
+
iconNames?: {
|
|
461
|
+
searchButton?: string;
|
|
462
|
+
modalClose?: string;
|
|
463
|
+
filter?: string;
|
|
464
|
+
filterRemove?: string;
|
|
465
|
+
modalSearch?: string;
|
|
466
|
+
modalSearchClear?: string;
|
|
467
|
+
imageSearchButton?: string;
|
|
402
468
|
};
|
|
403
469
|
customRenderers?: {
|
|
404
470
|
Modal?: React.ComponentType<SimilarProductsModalProps>;
|
package/src/utils/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const defaultSettings: Required<SimilarProductsSettings> = {
|
|
|
7
7
|
resultsPerPage: 20,
|
|
8
8
|
enableCropping: true,
|
|
9
9
|
enableFileUpload: true,
|
|
10
|
+
enableTextSearch: false,
|
|
10
11
|
customStyles: {
|
|
11
12
|
modal: '',
|
|
12
13
|
filterSidebar: '',
|
|
@@ -15,7 +16,31 @@ export const defaultSettings: Required<SimilarProductsSettings> = {
|
|
|
15
16
|
productItem: '',
|
|
16
17
|
pagination: '',
|
|
17
18
|
filterGroup: '',
|
|
18
|
-
imageSection: ''
|
|
19
|
+
imageSection: '',
|
|
20
|
+
textSearchContainer: '',
|
|
21
|
+
textSearchLabel: '',
|
|
22
|
+
textSearchInput: '',
|
|
23
|
+
controlsCenter: '',
|
|
24
|
+
modalSearchInput: '',
|
|
25
|
+
modalSearchContainer: '',
|
|
26
|
+
modalSearchButton: '',
|
|
27
|
+
modalSearchIcon: '',
|
|
28
|
+
modalSearchClearButton: '',
|
|
29
|
+
modalSearchClearIcon: '',
|
|
30
|
+
searchButtonIcon: '',
|
|
31
|
+
modalCloseIcon: '',
|
|
32
|
+
filterIcon: '',
|
|
33
|
+
filterRemoveIcon: '',
|
|
34
|
+
imageSearchButtonIcon: ''
|
|
35
|
+
},
|
|
36
|
+
iconNames: {
|
|
37
|
+
searchButton: 'search',
|
|
38
|
+
modalClose: 'close',
|
|
39
|
+
filter: 'filter',
|
|
40
|
+
filterRemove: 'close',
|
|
41
|
+
modalSearch: 'search',
|
|
42
|
+
modalSearchClear: 'close',
|
|
43
|
+
imageSearchButton: 'search'
|
|
19
44
|
},
|
|
20
45
|
customRenderers: {
|
|
21
46
|
render: {}
|
|
@@ -43,6 +68,10 @@ export function mergeSettings(
|
|
|
43
68
|
...defaultSettings.customStyles,
|
|
44
69
|
...userSettings.customStyles
|
|
45
70
|
},
|
|
71
|
+
iconNames: {
|
|
72
|
+
...defaultSettings.iconNames,
|
|
73
|
+
...userSettings.iconNames
|
|
74
|
+
},
|
|
46
75
|
customRenderers: {
|
|
47
76
|
...defaultSettings.customRenderers,
|
|
48
77
|
...userSettings.customRenderers,
|
|
@@ -100,13 +129,19 @@ export function validateImageFile(
|
|
|
100
129
|
export function debounce<T extends (...args: any[]) => any>(
|
|
101
130
|
func: T,
|
|
102
131
|
wait: number
|
|
103
|
-
): (...args: Parameters<T>) => void {
|
|
132
|
+
): ((...args: Parameters<T>) => void) & { cancel: () => void } {
|
|
104
133
|
let timeout: NodeJS.Timeout;
|
|
105
134
|
|
|
106
|
-
|
|
135
|
+
const debounced = (...args: Parameters<T>) => {
|
|
107
136
|
clearTimeout(timeout);
|
|
108
137
|
timeout = setTimeout(() => func(...args), wait);
|
|
109
138
|
};
|
|
139
|
+
|
|
140
|
+
debounced.cancel = () => {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return debounced as ((...args: Parameters<T>) => void) & { cancel: () => void };
|
|
110
145
|
}
|
|
111
146
|
|
|
112
147
|
export function dataURLToBlob(dataURL: string): Blob {
|