diff --git a/e2e/src/specs/web/album.e2e-spec.ts b/e2e/src/specs/web/album.e2e-spec.ts index 953c7d00ae1cd..a1c8cbfafa735 100644 --- a/e2e/src/specs/web/album.e2e-spec.ts +++ b/e2e/src/specs/web/album.e2e-spec.ts @@ -1,5 +1,5 @@ import { LoginResponseDto } from '@immich/sdk'; -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { utils } from 'src/utils'; test.describe('Album', () => { @@ -22,4 +22,35 @@ test.describe('Album', () => { await page.reload(); await page.getByRole('button', { name: 'Select photos' }).waitFor(); }); + + test('resizes album title font size based on text and viewport size', async ({ context, page }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.setViewportSize({ width: 1100, height: 1025 }); + + await page.goto('/albums'); + await page.getByRole('button', { name: 'Create album' }).click(); + + const input = page.getByPlaceholder('Add a title'); + await expect(input).toBeVisible(); + + const getFontSize = async () => Number.parseFloat(await input.evaluate((el) => getComputedStyle(el).fontSize)); + + await input.pressSequentially('A small title'); + await page.waitForTimeout(100); + const shortTitleFontSize = await getFontSize(); + + await input.clear(); + await input.pressSequentially( + 'A title that is quite large, maybe too large, stop please! A title that is quite large, maybe too large, stop please!', + ); + await page.waitForTimeout(100); + const longTitleFontSize = await getFontSize(); + + await page.setViewportSize({ width: 600, height: 1025 }); + await page.waitForTimeout(100); + const smallWindowFontSize = await getFontSize(); + + expect(shortTitleFontSize).toBeGreaterThan(longTitleFontSize); + expect(longTitleFontSize).toBeGreaterThan(smallWindowFontSize); + }); }); diff --git a/web/src/lib/components/album-page/album-title.svelte b/web/src/lib/components/album-page/album-title.svelte index 1ded91dcd40f2..5db30bfe1f79a 100644 --- a/web/src/lib/components/album-page/album-title.svelte +++ b/web/src/lib/components/album-page/album-title.svelte @@ -3,6 +3,7 @@ import { eventManager } from '$lib/managers/event-manager.svelte'; import { handleError } from '$lib/utils/handle-error'; import { updateAlbumInfo } from '@immich/sdk'; + import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; import { tv } from 'tailwind-variants'; @@ -16,6 +17,15 @@ let { id, albumName = $bindable(), isOwned, onUpdate }: Props = $props(); let newAlbumName = $derived(albumName); + let inputEl: HTMLInputElement; + let sizeIndex = $state(0); + + const sizeClasses = [ + 'text-2xl md:text-4xl lg:text-6xl', + 'text-xl md:text-2xl lg:text-4xl', + 'text-lg md:text-xl lg:text-2xl', + 'text-base md:text-lg lg:text-xl', + ]; const handleUpdateName = async () => { if (newAlbumName === albumName) { @@ -39,7 +49,7 @@ }; const styles = tv({ - base: 'w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90', + base: 'w-[99%] mb-2 border-b-2 border-transparent text-primary outline-none transition-all focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90', variants: { isOwned: { true: 'hover:border-gray-400', @@ -47,12 +57,49 @@ }, }, }); + + const updateTextSize = () => { + if (!inputEl) { + return; + } + + const availableWidth = inputEl.clientWidth; + const style = getComputedStyle(inputEl); + const span = document.createElement('span'); + + span.textContent = inputEl.value || inputEl.placeholder; + span.style.fontFamily = style.fontFamily; + span.style.fontWeight = style.fontWeight; + span.style.fontStyle = style.fontStyle; + span.style.letterSpacing = style.letterSpacing; + span.style.textTransform = style.textTransform; + + document.body.append(span); + sizeIndex = sizeClasses.length - 1; + + for (let i = 0; i < sizeClasses.length; i += 1) { + span.className = `${sizeClasses[i]} absolute invisible whitespace-nowrap pointer-events-none`; + + if (Math.ceil(span.getBoundingClientRect().width) <= availableWidth) { + sizeIndex = i; + break; + } + } + + span.remove(); + }; + + onMount(updateTextSize); + + e.currentTarget.blur() }} onblur={handleUpdateName} - class={styles({ isOwned })} + oninput={updateTextSize} + class={`${styles({ isOwned })} ${sizeClasses[sizeIndex]}`} type="text" bind:value={newAlbumName} disabled={!isOwned}