mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat: rtl (#17860)
This commit is contained in:
parent
85ac0512a6
commit
e6c575c33e
130 changed files with 354 additions and 323 deletions
|
|
@ -144,7 +144,7 @@
|
|||
<!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
|
||||
<!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
|
||||
{#snippet promptSnippet()}
|
||||
<div class="flex flex-col text-left gap-2">
|
||||
<div class="flex flex-col text-start gap-2">
|
||||
<div class="flex flex-col">
|
||||
<label for="datetime">{$t('date_and_time')}</label>
|
||||
<DateInput class="immich-form-input" id="datetime" type="datetime-local" bind:value={selectedDate} />
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@
|
|||
: ''}"
|
||||
onclick={() => handleUseSuggested(place.latitude, place.longitude)}
|
||||
>
|
||||
<p class="ml-4 text-sm text-gray-700 dark:text-gray-100 truncate">
|
||||
<p class="ms-4 text-sm text-gray-700 dark:text-gray-100 truncate">
|
||||
{getLocation(place.name, place.admin1name, place.admin2name)}
|
||||
</p>
|
||||
</button>
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
{/await}
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm text-left mt-4">
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
|
||||
<CoordinatesInput
|
||||
lat={point ? point.lat : assetLat}
|
||||
lng={point ? point.lng : assetLng}
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@
|
|||
>
|
||||
<div>
|
||||
{#if isActive}
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3">
|
||||
<div class="dark:text-immich-dark-fg/75">
|
||||
<Icon path={mdiMagnify} ariaHidden={true} />
|
||||
</div>
|
||||
|
|
@ -273,11 +273,11 @@
|
|||
aria-expanded={isOpen}
|
||||
autocomplete="off"
|
||||
bind:this={input}
|
||||
class:!pl-8={isActive}
|
||||
class:!ps-8={isActive}
|
||||
class:!rounded-b-none={isOpen && dropdownDirection === 'bottom'}
|
||||
class:!rounded-t-none={isOpen && dropdownDirection === 'top'}
|
||||
class:cursor-pointer={!isActive}
|
||||
class="immich-form-input text-sm text-left w-full !pr-12 transition-all"
|
||||
class="immich-form-input text-sm w-full !pe-12 transition-all"
|
||||
id={inputId}
|
||||
onfocus={activate}
|
||||
oninput={onInput}
|
||||
|
|
@ -325,8 +325,8 @@
|
|||
/>
|
||||
|
||||
<div
|
||||
class="absolute right-0 top-0 h-full flex px-4 justify-center items-center content-between"
|
||||
class:pr-2={selectedOption}
|
||||
class="absolute end-0 top-0 h-full flex px-4 justify-center items-center content-between"
|
||||
class:pe-2={selectedOption}
|
||||
class:pointer-events-none={!selectedOption}
|
||||
>
|
||||
{#if selectedOption}
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
role="listbox"
|
||||
id={listboxId}
|
||||
transition:fly={{ duration: 250 }}
|
||||
class="fixed text-left text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900 z-[10000]"
|
||||
class="fixed text-start text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900 z-[10000]"
|
||||
class:rounded-b-xl={dropdownDirection === 'bottom'}
|
||||
class:rounded-t-xl={dropdownDirection === 'top'}
|
||||
class:shadow={dropdownDirection === 'bottom'}
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
role="option"
|
||||
aria-selected={selectedIndex === 0}
|
||||
aria-disabled={true}
|
||||
class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700"
|
||||
class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700"
|
||||
id={`${listboxId}-${0}`}
|
||||
onclick={closeDropdown}
|
||||
>
|
||||
|
|
@ -372,7 +372,7 @@
|
|||
<li
|
||||
aria-selected={index === selectedIndex}
|
||||
bind:this={optionRefs[index]}
|
||||
class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 break-words"
|
||||
class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 break-words"
|
||||
id={`${listboxId}-${index}`}
|
||||
onclick={() => handleSelect(option)}
|
||||
role="option"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
} from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
|
||||
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
|
||||
import { languageManager } from '$lib/stores/language-manager.svelte';
|
||||
import {
|
||||
getContextMenuPositionFromBoundingRect,
|
||||
getContextMenuPositionFromEvent,
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
/**
|
||||
* The direction in which the context menu should open.
|
||||
*/
|
||||
// TODO change to start vs end
|
||||
direction?: 'left' | 'right';
|
||||
color?: Color;
|
||||
size?: string | undefined;
|
||||
|
|
@ -62,7 +64,15 @@
|
|||
const menuId = `context-menu-${id}`;
|
||||
|
||||
const openDropdown = (event: KeyboardEvent | MouseEvent) => {
|
||||
contextMenuPosition = getContextMenuPositionFromEvent(event, align);
|
||||
let layoutAlign = align;
|
||||
if (languageManager.rtl) {
|
||||
if (align.includes('left')) {
|
||||
layoutAlign = align.replace('left', 'right') as Align;
|
||||
} else if (align.includes('right')) {
|
||||
layoutAlign = align.replace('right', 'left') as Align;
|
||||
}
|
||||
}
|
||||
contextMenuPosition = getContextMenuPositionFromEvent(event, layoutAlign);
|
||||
isOpen = true;
|
||||
menuContainer?.focus();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { slide } from 'svelte/transition';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { languageManager } from '$lib/stores/language-manager.svelte';
|
||||
|
||||
interface Props {
|
||||
isVisible?: boolean;
|
||||
|
|
@ -41,12 +42,17 @@
|
|||
|
||||
$effect(() => {
|
||||
if (menuElement) {
|
||||
let layoutDirection = direction;
|
||||
if (languageManager.rtl) {
|
||||
layoutDirection = direction === 'left' ? 'right' : 'left';
|
||||
}
|
||||
|
||||
const rect = menuElement.getBoundingClientRect();
|
||||
const directionWidth = direction === 'left' ? rect.width : 0;
|
||||
const directionWidth = layoutDirection === 'left' ? rect.width : 0;
|
||||
const menuHeight = Math.min(menuElement.clientHeight, height) || 0;
|
||||
|
||||
left = Math.min(window.innerWidth - rect.width, x - directionWidth);
|
||||
top = Math.min(window.innerHeight - menuHeight, y);
|
||||
left = Math.max(8, Math.min(window.innerWidth - rect.width, x - directionWidth));
|
||||
top = Math.max(8, Math.min(window.innerHeight - menuHeight, y));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -66,7 +72,7 @@
|
|||
aria-labelledby={ariaLabelledBy}
|
||||
bind:this={menuElement}
|
||||
class="{isVisible
|
||||
? 'max-h-dvh max-h-svh'
|
||||
? 'max-h-dvh'
|
||||
: 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
onclick={handleClick}
|
||||
onmouseover={() => ($selectedIdStore = id)}
|
||||
onmouseleave={() => ($selectedIdStore = undefined)}
|
||||
class="w-full p-4 text-left text-sm font-medium {textColor} focus:outline-none focus:ring-2 focus:ring-inset cursor-pointer border-gray-200 flex gap-2 items-center {isActive
|
||||
class="w-full p-4 text-start text-sm font-medium {textColor} focus:outline-none focus:ring-2 focus:ring-inset cursor-pointer border-gray-200 flex gap-2 items-center {isActive
|
||||
? activeColor
|
||||
: 'bg-slate-100'}"
|
||||
role="menuitem"
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
<div class="flex justify-between">
|
||||
{text}
|
||||
{#if shortcutLabel}
|
||||
<span class="text-gray-500 pl-4">
|
||||
<span class="text-gray-500 ps-4">
|
||||
{shortcutLabel}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
const elements = document.elementsFromPoint(event.x, event.y);
|
||||
|
||||
if (menuContainer && elements.includes(menuContainer)) {
|
||||
// User right-clicked on the context menu itself, we keep the context
|
||||
// User end-clicked on the context menu itself, we keep the context
|
||||
// menu as is
|
||||
return;
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
},
|
||||
]}
|
||||
>
|
||||
<section class="fixed left-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
|
||||
<section class="fixed start-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
|
||||
<ContextMenu
|
||||
{direction}
|
||||
{x}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
<div class="mr-4 flex place-items-center gap-1 justify-self-end">
|
||||
<div class="me-4 flex place-items-center gap-1 justify-self-end">
|
||||
{@render trailing?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<FullScreenModal title={$t('deduplication_info')} width="auto" {onClose}>
|
||||
<div class="text-sm dark:text-white">
|
||||
<p>{$t('deduplication_info_description')}</p>
|
||||
<ol class="ml-8 mt-2" style="list-style: decimal">
|
||||
<ol class="ms-8 mt-2" style="list-style: decimal">
|
||||
<li>{$t('deduplication_criteria_1')}</li>
|
||||
<li>{$t('deduplication_criteria_2')}</li>
|
||||
</ol>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
role="presentation"
|
||||
in:fade={{ duration: 100 }}
|
||||
out:fade={{ duration: 100 }}
|
||||
class="fixed left-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
|
||||
class="fixed start-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
|
||||
onkeydown={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
</script>
|
||||
|
||||
<a data-sveltekit-preload-data="hover" class="ml-4" href="/">
|
||||
<a data-sveltekit-preload-data="hover" class="ms-4" href="/">
|
||||
<ImmichLogo class="h-[50px] max-w-none md:w-auto md:max-w-full" noText={mobileDevice.maxMd} />
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
in:fade={{ duration: 100 }}
|
||||
out:fade={{ duration: 100 }}
|
||||
id="account-info-panel"
|
||||
class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||
class="absolute end-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||
use:focusTrap
|
||||
>
|
||||
<div
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
>
|
||||
<div class="relative">
|
||||
<UserAvatar user={$user} size="xl" />
|
||||
<div class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6">
|
||||
<div class="absolute z-10 bottom-0 end-0 rounded-full w-6 h-6">
|
||||
<CircleIconButton
|
||||
color="primary"
|
||||
icon={mdiPencil}
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@
|
|||
<ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4 lg:gap-8 pr-6">
|
||||
<div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block">
|
||||
<div class="flex justify-between gap-4 lg:gap-8 pe-6">
|
||||
<div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block">
|
||||
{#if $featureFlags.search}
|
||||
<SearchBar grayTheme={true} />
|
||||
{/if}
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex pl-2"
|
||||
class="flex ps-2"
|
||||
onmouseover={() => (shouldShowAccountInfo = true)}
|
||||
onfocus={() => (shouldShowAccountInfo = true)}
|
||||
onblur={() => (shouldShowAccountInfo = false)}
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
<div
|
||||
in:fade={{ delay: 500, duration: 150 }}
|
||||
out:fade={{ delay: 200, duration: 150 }}
|
||||
class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||
class="absolute -bottom-12 end-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||
>
|
||||
<p>{$user.name}</p>
|
||||
<p>{$user.email}</p>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</script>
|
||||
|
||||
{#if showing}
|
||||
<div class="absolute left-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
|
||||
<div class="absolute start-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
|
||||
<span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message">
|
||||
<p class="whitespace-pre-wrap ps-[28px] pe-[16px] text-sm" data-testid="message">
|
||||
{#if isComponentNotification(notification)}
|
||||
<notification.component.type {...notification.component.props} />
|
||||
{:else}
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
</p>
|
||||
|
||||
{#if notification.button}
|
||||
<p class="pl-[28px] mt-2.5 text-sm">
|
||||
<p class="ps-[28px] mt-2.5 text-sm">
|
||||
<button
|
||||
type="button"
|
||||
class="{buttonStyle[notification.type]} rounded px-3 pt-1.5 pb-1 transition-all duration-200"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<div role="status" aria-relevant="additions" aria-label={$t('notifications')}>
|
||||
{#if $notificationList.length > 0}
|
||||
<section transition:fade={{ duration: 250 }} id="notification-list" class="fixed right-5 top-[80px] z-[99999999]">
|
||||
<section transition:fade={{ duration: 250 }} id="notification-list" class="fixed end-5 top-[80px] z-[99999999]">
|
||||
{#each $notificationList as notification (notification.id)}
|
||||
<div animate:flip={{ duration: 250, easing: quintOut }}>
|
||||
<NotificationCard {notification} />
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<div class="relative w-full">
|
||||
<input
|
||||
{...rest}
|
||||
class="immich-form-input w-full !pr-12"
|
||||
class="immich-form-input w-full !pe-12"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
{required}
|
||||
value={password}
|
||||
|
|
|
|||
|
|
@ -88,5 +88,5 @@
|
|||
</script>
|
||||
|
||||
{#if !hidden}
|
||||
<span class="absolute left-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`}></span>
|
||||
<span class="absolute start-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`}></span>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@
|
|||
aria-valuemax={toScrollY(1)}
|
||||
aria-valuemin={toScrollY(0)}
|
||||
data-id="immich-scrubbable-scrollbar"
|
||||
class="absolute right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
||||
class="absolute end-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
||||
style:padding-top={PADDING_TOP + 'px'}
|
||||
style:padding-bottom={PADDING_BOTTOM + 'px'}
|
||||
style:width
|
||||
|
|
@ -464,7 +464,7 @@
|
|||
class={[
|
||||
{ 'border-b-2': isDragging },
|
||||
{ 'rounded-bl-md': !isDragging },
|
||||
'truncate opacity-85 pointer-events-none absolute right-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-immich-primary bg-immich-bg py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray dark:text-immich-dark-fg',
|
||||
'truncate opacity-85 pointer-events-none absolute end-0 z-[100] min-w-20 max-w-64 w-fit rounded-ss-md border-immich-primary bg-immich-bg py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray dark:text-immich-dark-fg',
|
||||
]}
|
||||
style:top="{hoverY + 2}px"
|
||||
>
|
||||
|
|
@ -474,7 +474,7 @@
|
|||
{#if usingMobileDevice && ((assetStore.scrolling && scrollHoverLabel) || isHover || isDragging)}
|
||||
<div
|
||||
id="time-label"
|
||||
class="rounded-l-full w-[32px] pl-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
|
||||
class="rounded-s-full w-[32px] ps-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
|
||||
style:top="{PADDING_TOP + (scrollY - 50 / 2)}px"
|
||||
style:height="50px"
|
||||
style:right="0"
|
||||
|
|
@ -482,8 +482,8 @@
|
|||
in:fade={{ duration: 200 }}
|
||||
out:fade={{ duration: 200 }}
|
||||
>
|
||||
<Icon path={mdiPlay} size="20" class="-rotate-90 relative top-[9px] -right-[2px]" />
|
||||
<Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -right-[2px]" />
|
||||
<Icon path={mdiPlay} size="20" class="-rotate-90 relative top-[9px] -end-[2px]" />
|
||||
<Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -end-[2px]" />
|
||||
{#if (assetStore.scrolling && scrollHoverLabel) || isHover || isDragging}
|
||||
<p
|
||||
transition:fade={{ duration: 200 }}
|
||||
|
|
@ -500,13 +500,13 @@
|
|||
<!-- Scroll Position Indicator Line -->
|
||||
{#if !usingMobileDevice && !isDragging}
|
||||
<div
|
||||
class="absolute right-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
|
||||
class="absolute end-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
|
||||
style:top="{scrollY + PADDING_TOP - 2}px"
|
||||
>
|
||||
{#if assetStore.scrolling && scrollHoverLabel && !isHover}
|
||||
<p
|
||||
transition:fade={{ duration: 200 }}
|
||||
class="truncate pointer-events-none absolute right-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
|
||||
class="truncate pointer-events-none absolute end-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
|
||||
>
|
||||
{scrollHoverLabel}
|
||||
</p>
|
||||
|
|
@ -521,7 +521,7 @@
|
|||
data-label={segments.at(0)?.dateFormatted}
|
||||
>
|
||||
{#if relativeTopOffset > 6}
|
||||
<div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||
<div class="absolute end-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Time Segment -->
|
||||
|
|
@ -535,12 +535,12 @@
|
|||
>
|
||||
{#if !usingMobileDevice}
|
||||
{#if segment.hasLabel}
|
||||
<div class="absolute right-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
|
||||
<div class="absolute end-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
|
||||
{segment.date.year}
|
||||
</div>
|
||||
{/if}
|
||||
{#if segment.hasDot}
|
||||
<div class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||
<div class="absolute end-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -261,15 +261,15 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
|
||||
<div class="absolute inset-y-0 {showClearIcon ? 'end-14' : 'end-2'} flex items-center ps-6 transition-all">
|
||||
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" />
|
||||
</div>
|
||||
|
||||
{#if isFocus}
|
||||
<div
|
||||
class="absolute inset-y-0 flex items-center"
|
||||
class:right-16={isFocus}
|
||||
class:right-28={isFocus && value.length > 0}
|
||||
class:end-16={isFocus}
|
||||
class:end-28={isFocus && value.length > 0}
|
||||
>
|
||||
<p
|
||||
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs z-10"
|
||||
|
|
@ -280,11 +280,11 @@
|
|||
{/if}
|
||||
|
||||
{#if showClearIcon}
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<div class="absolute inset-y-0 end-0 flex items-center pe-2">
|
||||
<CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-2">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-2">
|
||||
<CircleIconButton
|
||||
type="submit"
|
||||
disabled={showFilter}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@
|
|||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
id={getId(index)}
|
||||
class="relative flex w-full cursor-pointer gap-3 py-3 pl-5 hover:bg-gray-100 aria-selected:bg-gray-100 dark:aria-selected:bg-gray-500/30 dark:hover:bg-gray-500/30"
|
||||
class="relative flex w-full cursor-pointer gap-3 py-3 ps-5 hover:bg-gray-100 aria-selected:bg-gray-100 dark:aria-selected:bg-gray-500/30 dark:hover:bg-gray-500/30"
|
||||
onclick={() => handleSelect(savedSearchTerm)}
|
||||
role="option"
|
||||
tabindex="-1"
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
<Icon path={mdiMagnify} size="1.5em" ariaHidden={true} />
|
||||
{savedSearchTerm}
|
||||
</div>
|
||||
<div aria-hidden={true} class="absolute right-5 top-0 items-center justify-center py-3">
|
||||
<div aria-hidden={true} class="absolute end-5 top-0 items-center justify-center py-3">
|
||||
<CircleIconButton
|
||||
icon={mdiClose}
|
||||
title={$t('remove')}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
{#if tag}
|
||||
<div class="flex group transition-all">
|
||||
<span
|
||||
class="inline-block h-min whitespace-nowrap pl-3 pr-1 group-hover:pl-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary rounded-tl-full rounded-bl-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
>
|
||||
<p class="text-sm">
|
||||
{tag.value}
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
|
||||
<button
|
||||
type="button"
|
||||
class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-e-full place-items-center place-content-center pe-2 ps-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
title="Remove tag"
|
||||
onclick={() => handleRemove(tagId)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
type="button"
|
||||
aria-expanded={isOpen}
|
||||
{onclick}
|
||||
class="flex w-full place-items-center justify-between text-left"
|
||||
class="flex w-full place-items-center justify-between text-start"
|
||||
>
|
||||
<div>
|
||||
<div class="flex gap-2 place-items-center">
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
</button>
|
||||
|
||||
{#if isOpen}
|
||||
<ul transition:slide={{ duration: 150 }} class="mb-2 ml-4">
|
||||
<ul transition:slide={{ duration: 150 }} class="mb-2 ms-4">
|
||||
{@render children?.()}
|
||||
</ul>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
{#if inputType === SettingInputFieldType.COLOR}
|
||||
<input
|
||||
bind:this={input}
|
||||
class="immich-form-input w-full pb-2 rounded-none mr-1"
|
||||
class="immich-form-input w-full pb-2 rounded-none me-1"
|
||||
aria-describedby={description ? `${label}-desc` : undefined}
|
||||
aria-labelledby="{label}-label"
|
||||
id={label}
|
||||
|
|
|
|||
|
|
@ -65,12 +65,12 @@
|
|||
path={mdiChevronDown}
|
||||
size="1.2em"
|
||||
ariaHidden={true}
|
||||
class="pointer-events-none right-1 relative col-start-1 row-start-1 self-center justify-self-end {disabled
|
||||
class="pointer-events-none end-1 relative col-start-1 row-start-1 self-center justify-self-end {disabled
|
||||
? 'text-immich-bg'
|
||||
: 'text-immich-fg dark:text-immich-bg'}"
|
||||
/>
|
||||
<select
|
||||
class="immich-form-input w-full appearance-none row-start-1 col-start-1 !pr-6"
|
||||
class="immich-form-input w-full appearance-none row-start-1 col-start-1 !pe-6"
|
||||
{disabled}
|
||||
aria-describedby={desc ? `${name}-desc` : undefined}
|
||||
{name}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
</script>
|
||||
|
||||
<div class="flex place-items-center justify-between">
|
||||
<div class="mr-2">
|
||||
<div class="me-2">
|
||||
<div class="flex h-[26px] place-items-center gap-1">
|
||||
<label class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm" for={sliderId}>
|
||||
{title}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
||||
<div class="flex justify-self-end">
|
||||
{#each shortcut.key as key (key)}
|
||||
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||
<p class="me-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||
{key}
|
||||
</p>
|
||||
{/each}
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
<div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
|
||||
<div class="flex justify-self-end">
|
||||
{#each shortcut.key as key (key)}
|
||||
<p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||
<p class="me-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
|
||||
{key}
|
||||
</p>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
<LicenseModal onClose={() => (isOpen = false)} />
|
||||
{/if}
|
||||
|
||||
<div class="license-status pl-4 text-sm">
|
||||
<div class="license-status ps-4 text-sm">
|
||||
{#if $isPurchased && $preferences.purchase.showSupportBadge}
|
||||
<button
|
||||
onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)}
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
{#if showMessage}
|
||||
<dialog
|
||||
open
|
||||
class="hidden sidebar:block w-[500px] absolute bottom-[75px] left-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
|
||||
class="hidden sidebar:block w-[500px] absolute bottom-[75px] start-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
|
||||
transition:fade={{ duration: 150 }}
|
||||
onmouseover={() => (hoverMessage = true)}
|
||||
onmouseleave={() => (hoverMessage = false)}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<a
|
||||
href={'/albums/' + album.id}
|
||||
title={album.albumName}
|
||||
class="flex w-full place-items-center justify-between gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary pl-10 group-hover:sm:px-10 md:px-10"
|
||||
class="flex w-full place-items-center justify-between gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary ps-10 group-hover:sm:px-10 md:px-10"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
{/if}
|
||||
|
||||
<div
|
||||
class="text-sm flex md:flex pl-5 pr-1 place-items-center place-content-center justify-between min-w-52 overflow-hidden"
|
||||
class="text-sm flex md:flex ps-5 pe-1 place-items-center place-content-center justify-between min-w-52 overflow-hidden"
|
||||
>
|
||||
{#if $connected}
|
||||
<div class="flex gap-2 place-items-center place-content-center">
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<div class="relative">
|
||||
{#if hasDropdown}
|
||||
<span class="hidden md:block absolute left-1 z-50 h-full">
|
||||
<span class="hidden md:block absolute start-1 z-50 h-full">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={$t('recent-albums')}
|
||||
|
|
@ -59,12 +59,12 @@
|
|||
data-sveltekit-preload-data={preloadData ? 'hover' : 'off'}
|
||||
draggable="false"
|
||||
aria-current={isSelected ? 'page' : undefined}
|
||||
class="flex w-full place-items-center gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary
|
||||
class="flex w-full place-items-center gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary
|
||||
{isSelected
|
||||
? 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/10 dark:bg-immich-dark-primary/10 dark:text-immich-dark-primary'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex w-full place-items-center gap-4 pl-5 overflow-hidden truncate">
|
||||
<div class="flex w-full place-items-center gap-4 ps-5 overflow-hidden truncate">
|
||||
<Icon path={icon} size="1.5em" class="shrink-0" flipped={flippedLogo} ariaHidden />
|
||||
<span class="text-sm font-medium">{title}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
tabindex="-1"
|
||||
class="immich-scrollbar relative z-10 w-0 sidebar:w-[16rem] overflow-y-auto overflow-x-hidden bg-immich-bg pt-8 transition-all duration-200 dark:bg-immich-dark-bg"
|
||||
class:shadow-2xl={isExpanded}
|
||||
class:dark:border-r-immich-dark-gray={isExpanded}
|
||||
class:dark:border-e-immich-dark-gray={isExpanded}
|
||||
class:border-r={isExpanded}
|
||||
class:w-[min(100vw,16rem)]={sidebarStore.isOpen}
|
||||
data-testid="sidebar-parent"
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
use:clickOutside={{ onOutclick: closeSidebar, onEscape: closeSidebar }}
|
||||
use:focusTrap={{ active: isExpanded }}
|
||||
>
|
||||
<div class="pr-6 flex flex-col gap-1 h-max min-h-full">
|
||||
<div class="pe-6 flex flex-col gap-1 h-max min-h-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ml-4 rounded-lg text-sm min-w-52"
|
||||
class="storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ms-4 rounded-lg text-sm min-w-52"
|
||||
title={$t('storage_usage', {
|
||||
values: {
|
||||
used: getByteUnitString(usedBytes, $locale, 3),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
icon={mdiArrowUpLeft}
|
||||
title={$t('to_parent')}
|
||||
href={getLink(pathSegments.slice(0, -1).join('/'))}
|
||||
class="mr-2"
|
||||
class="me-2"
|
||||
padding="2"
|
||||
onclick={() => {}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
let { items, parent = '', active = '', icons, getLink, getColor = () => undefined }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ul class="list-none ml-2">
|
||||
<ul class="list-none ms-2">
|
||||
<!-- eslint-disable-next-line svelte/require-each-key -->
|
||||
{#each Object.entries(items).sort() as [path, tree]}
|
||||
{@const value = normalizeTreePath(`${parent}/${path}`)}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<a
|
||||
href={getLink(path)}
|
||||
title={value}
|
||||
class={`flex flex-grow place-items-center pl-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-immich-primary dark:text-immich-dark-primary' : 'dark:text-gray-200'}`}
|
||||
class={`flex flex-grow place-items-center ps-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-immich-primary dark:text-immich-dark-primary' : 'dark:text-gray-200'}`}
|
||||
data-sveltekit-keepfocus
|
||||
>
|
||||
<button type="button" {onclick} class={Object.values(tree).length === 0 ? 'invisible' : ''}>
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
size={20}
|
||||
/>
|
||||
</div>
|
||||
<span class="text-nowrap overflow-hidden text-ellipsis font-mono pl-1 pt-1 whitespace-pre-wrap">{value}</span>
|
||||
<span class="text-nowrap overflow-hidden text-ellipsis font-mono ps-1 pt-1 whitespace-pre-wrap">{value}</span>
|
||||
</a>
|
||||
|
||||
{#if isOpen}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
uploadAssetsStore.reset();
|
||||
}}
|
||||
class="fixed bottom-6 right-16 z-[10000]"
|
||||
class="fixed bottom-6 end-16 z-[10000]"
|
||||
>
|
||||
{#if showDetail}
|
||||
<div
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
type="button"
|
||||
in:scale={{ duration: 250, easing: quartInOut }}
|
||||
onclick={() => (showDetail = true)}
|
||||
class="absolute -left-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
|
||||
class="absolute -start-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
|
||||
>
|
||||
{$remainingUploads.toLocaleString($locale)}
|
||||
</button>
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
type="button"
|
||||
in:scale={{ duration: 250, easing: quartInOut }}
|
||||
onclick={() => (showDetail = true)}
|
||||
class="absolute -right-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
|
||||
class="absolute -end-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
|
||||
>
|
||||
{$stats.errors.toLocaleString($locale)}
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue