diff --git a/messages/en.json b/messages/en.json index fb43805..bb7cca1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -104,6 +104,12 @@ "filename_format": "File name format", "filename_description": "This will determine the name of the file on download, not including the file extension. You can put these following templates in the format, which will be replaced with the relevant information: %name% for the original file name, %extension% for the original file extension, and %date% for a date string of when the file was converted.", "placeholder": "VERT_%name%", + "default_format": "Default conversion format", + "default_format_description": "This will change the default format selected when you upload a file of this file type.", + "default_format_image": "Images", + "default_format_video": "Videos", + "default_format_audio": "Audio", + "default_format_document": "Documents", "metadata": "File metadata", "metadata_description": "This changes whether any metadata (EXIF, song info, etc.) on the original file is preserved in converted files.", "keep": "Keep", diff --git a/src/lib/components/functional/FormatDropdown.svelte b/src/lib/components/functional/FormatDropdown.svelte index c9525da..69027ac 100644 --- a/src/lib/components/functional/FormatDropdown.svelte +++ b/src/lib/components/functional/FormatDropdown.svelte @@ -31,6 +31,7 @@ let searchQuery = $state(""); let dropdownMenu: HTMLElement | undefined = $state(); let rootCategory: string | null = null; + let dropdownPosition = $state<"left" | "center" | "right">("center"); // initialize current category $effect(() => { @@ -86,13 +87,15 @@ // if no query, return formats for current category if (!searchQuery) { + let formats = currentCategory + ? categories[currentCategory].formats.filter((format) => + shouldInclude(format, currentCategory!), + ) + : []; + return { categories: availableCategories, - formats: currentCategory - ? categories[currentCategory].formats.filter((format) => - shouldInclude(format, currentCategory!), - ) - : [], + formats, }; } const searchLower = normalize(searchQuery); @@ -213,6 +216,34 @@ const clickDropdown = () => { open = !open; if (!open) return; + + // keep within viewport + if (dropdown) { + const rect = dropdown.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + + let dropdownWidth: number; + if (dropdownSize === "large") { + dropdownWidth = rect.width * 3.2; + } else if (dropdownSize === "default") { + dropdownWidth = rect.width * 2.5; + } else { + dropdownWidth = rect.width * 1.5; + } + + const centerX = rect.left + rect.width / 2; + const leftEdge = centerX - dropdownWidth / 2; + const rightEdge = centerX + dropdownWidth / 2; + + if (leftEdge < 0) { + dropdownPosition = "left"; + } else if (rightEdge > viewportWidth) { + dropdownPosition = "right"; + } else { + dropdownPosition = "center"; + } + } + setTimeout(() => { if (!dropdownMenu) return; const searchInput = dropdownMenu.querySelector( @@ -232,23 +263,20 @@ } }; - window.addEventListener("click", handleClickOutside); - return () => window.removeEventListener("click", handleClickOutside); - }); + const handleResize = () => { + if (open) { + // recalculate dropdown position on resize + clickDropdown(); + open = true; + } + }; - // initialize selected format if none chosen - $effect(() => { - if ( - !selected && - currentCategory && - categories[currentCategory]?.formats?.length > 0 - ) { - const from = files.files[0]?.from; - const firstDiff = categories[currentCategory].formats.find( - (f) => f !== from, - ); - selected = firstDiff || categories[currentCategory].formats[0]; - } + window.addEventListener("click", handleClickOutside); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("click", handleClickOutside); + window.removeEventListener("resize", handleResize); + }; }); @@ -258,7 +286,7 @@ >
{/key} {#if currentCategory} @@ -308,12 +336,17 @@ class={clsx( $isMobile ? "fixed inset-x-0 bottom-0 w-full z-[200] shadow-xl bg-panel-alt shadow-black/25 rounded-t-2xl overflow-hidden" - : "min-w-full shadow-xl bg-panel-alt shadow-black/25 absolute -translate-x-1/2 top-full mt-2 z-50 rounded-2xl overflow-hidden", + : "min-w-full shadow-xl bg-panel-alt shadow-black/25 absolute top-full mt-2 z-50 rounded-2xl overflow-hidden", !$isMobile && { "w-[320%]": dropdownSize === "large", "w-[250%]": dropdownSize === "default", "w-[150%]": dropdownSize === "small", }, + !$isMobile && { + "-translate-x-1/2 left-1/2": dropdownPosition === "center", + "left-0": dropdownPosition === "left", + "right-0": dropdownPosition === "right", + }, )} > diff --git a/src/lib/sections/settings/Conversion.svelte b/src/lib/sections/settings/Conversion.svelte index 258fbc1..091af9d 100644 --- a/src/lib/sections/settings/Conversion.svelte +++ b/src/lib/sections/settings/Conversion.svelte @@ -13,8 +13,10 @@ import Dropdown from "$lib/components/functional/Dropdown.svelte"; import FancyInput from "$lib/components/functional/FancyInput.svelte"; import { effects } from "$lib/store/index.svelte"; + import FormatDropdown from "$lib/components/functional/FormatDropdown.svelte"; + import { categories } from "$lib/converters"; - const { settings }: { settings: ISettings } = $props(); + const { settings = $bindable() }: { settings: ISettings } = $props();+ {m["settings.conversion.default_format"]()} +
++ {m["settings.conversion.default_format_description"]()} +
++ {m["settings.conversion.default_format_image"]()} +
++ {m["settings.conversion.default_format_audio"]()} +
++ {m["settings.conversion.default_format_video"]()} +
++ {m["settings.conversion.default_format_document"]()} +
+
diff --git a/src/lib/sections/settings/Privacy.svelte b/src/lib/sections/settings/Privacy.svelte
index 5748d5b..8df55dc 100644
--- a/src/lib/sections/settings/Privacy.svelte
+++ b/src/lib/sections/settings/Privacy.svelte
@@ -6,7 +6,7 @@
import { m } from "$lib/paraglide/messages";
import { link } from "$lib/store/index.svelte";
- const { settings }: { settings: ISettings } = $props();
+ const { settings = $bindable() }: { settings: ISettings } = $props();
{m["convert.errors.cant_convert"]()}
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_downloading"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_downloading"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
{m["convert.errors.cant_convert"]()}
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_error"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_error"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
{m["convert.errors.cant_convert"]()}
+ {m["convert.errors.cant_convert"]()}
+
- {m["convert.errors.worker_timeout"]({
- type: isAudio
- ? m["convert.errors.audio"]()
- : isVideo
- ? "Video"
- : isDocument
- ? m["convert.errors.doc"]()
- : m["convert.errors.image"]()
+ {m["convert.errors.worker_timeout"]({
+ type: isAudio
+ ? m["convert.errors.audio"]()
+ : isVideo
+ ? "Video"
+ : isDocument
+ ? m["convert.errors.doc"]()
+ : m["convert.errors.image"](),
})}
{m["convert.errors.cant_convert"]()}
+ {m["convert.errors.cant_convert"]()}
+
{m["convert.errors.vertd_not_found"]()}