From 70862a5abfe8852e91eb86cac967413dd0c6fec8 Mon Sep 17 00:00:00 2001 From: JovannMC Date: Wed, 28 May 2025 22:07:47 +0300 Subject: [PATCH] fix: many search fixes help --- .../functional/FormatDropdown.svelte | 186 +++++++++++++++--- src/lib/converters/index.ts | 5 +- src/routes/convert/+page.svelte | 1 - 3 files changed, 158 insertions(+), 34 deletions(-) diff --git a/src/lib/components/functional/FormatDropdown.svelte b/src/lib/components/functional/FormatDropdown.svelte index 6ba572f..21815d2 100644 --- a/src/lib/components/functional/FormatDropdown.svelte +++ b/src/lib/components/functional/FormatDropdown.svelte @@ -22,6 +22,7 @@ let open = $state(false); let hover = $state(false); let dropdown = $state(); + let initialCategory = $state(); let currentCategory = $state(); let searchQuery = $state(""); let dropdownMenu: HTMLElement | undefined = $state(); @@ -35,6 +36,7 @@ ); currentCategory = foundCat || Object.keys(categories)[0] || null; + initialCategory = currentCategory; } else { // find category based on file types const fileFormats = files.files.map((f) => f.from); @@ -45,6 +47,7 @@ ); currentCategory = foundCat || Object.keys(categories)[0] || null; + initialCategory = currentCategory; } } }); @@ -59,35 +62,99 @@ categories[cat].canConvertTo?.includes(currentCategory || ""), ); }); + + const shouldShowFormat = (format: string, category: string): boolean => { + const currentFileExt = files.files[0]?.from; + if (!currentFileExt) return true; + + if (category === initialCategory) { + return true; + } else if ( + initialCategory && + categories[initialCategory].formats.includes(format) + ) { + return false; + } + + const formatInOtherCategories = Object.keys(categories) + .filter((cat) => cat !== category) + .some((cat) => categories[cat].formats.includes(format)); + + if (formatInOtherCategories) { + const nativeCategory = Object.keys(categories).find((cat) => + cat.toLowerCase().includes(format.slice(1)), + ); + + return category === nativeCategory; + } + + return true; + }; + const filteredData = $derived.by(() => { + // if no query, return formats for current category if (!searchQuery) { return { categories: availableCategories, formats: currentCategory - ? categories[currentCategory].formats + ? categories[currentCategory].formats.filter((format) => + shouldShowFormat(format, currentCategory || ""), + ) : [], }; } + const searchLower = searchQuery.toLowerCase(); - // filter categories that have matching formats + // find all categories that have formats matching the search query const matchingCategories = availableCategories.filter((cat) => - categories[cat].formats.some((format) => - format.toLowerCase().includes(searchQuery.toLowerCase()), + categories[cat].formats.some( + (format) => + format.toLowerCase().includes(searchLower) && + shouldShowFormat(format, cat), ), ); + if (matchingCategories.length === 0) { + return { + categories: availableCategories, + formats: [], + }; + } - // only show formats from the current category that match the search - const filteredFormats = - currentCategory && categories[currentCategory] - ? categories[currentCategory].formats.filter((format) => - format - .toLowerCase() - .includes(searchQuery.toLowerCase()), - ) - : []; + // find all matching formats across all categories + const allMatchingFormats = matchingCategories.flatMap((cat) => { + return categories[cat].formats + .filter( + (format) => + format.toLowerCase().includes(searchLower) && + shouldShowFormat(format, cat), + ) + .map((format) => ({ format, category: cat })); + }); + // if current category has no matches, switch to first category that does + const currentCategoryHasMatches = + currentCategory && + allMatchingFormats.some( + (item) => item.category === currentCategory, + ); + if (!currentCategoryHasMatches && matchingCategories.length > 0) { + const newCategory = matchingCategories[0]; + currentCategory = newCategory; + } + + // return formats only from the current category that match the search + const filteredFormats = currentCategory + ? categories[currentCategory].formats.filter( + (format) => + format.toLowerCase().includes(searchLower) && + shouldShowFormat(format, currentCategory || ""), + ) + : []; return { - categories: matchingCategories, + categories: + matchingCategories.length > 0 + ? matchingCategories + : availableCategories, formats: filteredFormats, }; }); @@ -95,6 +162,21 @@ const selectOption = (option: string) => { selected = option; open = false; + + // find the category of this option if it's not in the current category + if ( + currentCategory && + !categories[currentCategory].formats.includes(option) + ) { + const formatCategory = Object.keys(categories).find((cat) => + categories[cat].formats.includes(option), + ); + + if (formatCategory) { + currentCategory = formatCategory; + } + } + onselect?.(option); }; @@ -104,7 +186,34 @@ }; const handleSearch = (event: Event) => { - searchQuery = (event.target as HTMLInputElement).value; + const query = (event.target as HTMLInputElement).value; + searchQuery = query; + + // find which categories have matching formats & switch + if (query) { + const queryLower = query.toLowerCase(); + const categoriesWithMatches = availableCategories.filter((cat) => + categories[cat].formats.some( + (format) => + format.toLowerCase().includes(queryLower) && + shouldShowFormat(format, cat), + ), + ); + + if (categoriesWithMatches.length > 0) { + const currentHasMatches = + currentCategory && + categories[currentCategory].formats.some( + (format) => + format.toLowerCase().includes(queryLower) && + shouldShowFormat(format, currentCategory || ""), + ); + + if (!currentHasMatches) { + currentCategory = categoriesWithMatches[0]; + } + } + } }; onMount(() => { @@ -184,7 +293,6 @@ {#if open}
{}} /> + {#if searchQuery} + + {filteredData.formats.length} + {filteredData.formats.length === 1 + ? "result" + : "results"} + + {/if}
-
{#each filteredData.categories as category} {/each}
-
- {#each filteredData.formats as format} - - {/each} + {#if filteredData.formats.length > 0} + {#each filteredData.formats as format} + + {/each} + {:else} +
+ {searchQuery + ? "No formats match your search" + : "No formats available"} +
+ {/if}
{/if} diff --git a/src/lib/converters/index.ts b/src/lib/converters/index.ts index 4b57c65..43a78cb 100644 --- a/src/lib/converters/index.ts +++ b/src/lib/converters/index.ts @@ -23,8 +23,8 @@ export function getConverterByFormat(format: string) { export const categories: Categories = { image: { formats: [""], canConvertTo: [] }, - video: { formats: [""], canConvertTo: [] }, // add "audio" when "nullptr/experimental-audio-to-video" is implemented - audio: { formats: [""], canConvertTo: [] }, // add "video" when "nullptr/experimental-audio-to-video" is implemented + video: { formats: [""], canConvertTo: ["audio"] }, + audio: { formats: [""], canConvertTo: ["video"] }, docs: { formats: [""], canConvertTo: [] }, }; @@ -48,6 +48,7 @@ categories.docs.formats = .find((c) => c.name === "pandoc") ?.formatStrings((f) => f.toSupported) .filter((f) => f !== ".pdf") || []; + export const byNative = (format: string) => { return (a: Converter, b: Converter) => { const aFormat = a.supportedFormats.find((f) => f.name === format); diff --git a/src/routes/convert/+page.svelte b/src/routes/convert/+page.svelte index 90e37d2..e475b7d 100644 --- a/src/routes/convert/+page.svelte +++ b/src/routes/convert/+page.svelte @@ -113,7 +113,6 @@ {#snippet fileItem(file: VertFile, index: number)} - {@const availableConverters = file.findConverters()} {@const currentConverter = converters.find( (c) => c.formatStrings((f) => f.fromSupported).includes(file.from) &&