fix(web): timeline and asset viewer RTL support (#26513)

This commit is contained in:
Mees Frensel
2026-03-03 09:01:54 +01:00
committed by GitHub
parent 05010c3a84
commit a284e38890
8 changed files with 18 additions and 15 deletions
@@ -18,6 +18,7 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { languageManager } from '$lib/managers/language-manager.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getGlobalActions } from '$lib/services/app.service'; import { getGlobalActions } from '$lib/services/app.service';
import { getAssetActions, handleReplaceAsset } from '$lib/services/asset.service'; import { getAssetActions, handleReplaceAsset } from '$lib/services/asset.service';
@@ -36,6 +37,7 @@
import { ActionButton, CommandPaletteDefaultProvider, type ActionItem } from '@immich/ui'; import { ActionButton, CommandPaletteDefaultProvider, type ActionItem } from '@immich/ui';
import { import {
mdiArrowLeft, mdiArrowLeft,
mdiArrowRight,
mdiCompare, mdiCompare,
mdiDotsVertical, mdiDotsVertical,
mdiImageSearch, mdiImageSearch,
@@ -84,7 +86,7 @@
const Close: ActionItem = $derived({ const Close: ActionItem = $derived({
title: $t('go_back'), title: $t('go_back'),
type: $t('assets'), type: $t('assets'),
icon: mdiArrowLeft, icon: languageManager.rtl ? mdiArrowRight : mdiArrowLeft,
$if: () => !!onClose, $if: () => !!onClose,
onAction: () => onClose?.(), onAction: () => onClose?.(),
shortcuts: [{ key: 'Escape' }], shortcuts: [{ key: 'Escape' }],
@@ -134,7 +134,7 @@
></div> ></div>
{/if} {/if}
</Button> </Button>
<span class="text-sm text-white text-left">{ratio.label}</span> <span class="text-sm text-white">{ratio.label}</span>
</HStack> </HStack>
{/each} {/each}
</div> </div>
@@ -2,6 +2,7 @@
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut'; import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
import ProgressBar from '$lib/components/shared-components/progress-bar/progress-bar.svelte'; import ProgressBar from '$lib/components/shared-components/progress-bar/progress-bar.svelte';
import { ProgressBarStatus } from '$lib/constants'; import { ProgressBarStatus } from '$lib/constants';
import { languageManager } from '$lib/managers/language-manager.svelte';
import SlideshowSettingsModal from '$lib/modals/SlideshowSettingsModal.svelte'; import SlideshowSettingsModal from '$lib/modals/SlideshowSettingsModal.svelte';
import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store';
import { AssetTypeEnum } from '@immich/sdk'; import { AssetTypeEnum } from '@immich/sdk';
@@ -199,7 +200,7 @@
variant="ghost" variant="ghost"
shape="round" shape="round"
color="secondary" color="secondary"
icon={mdiChevronLeft} icon={languageManager.rtl ? mdiChevronRight : mdiChevronLeft}
onclick={onPrevious} onclick={onPrevious}
aria-label={$t('previous')} aria-label={$t('previous')}
/> />
@@ -207,7 +208,7 @@
variant="ghost" variant="ghost"
shape="round" shape="round"
color="secondary" color="secondary"
icon={mdiChevronRight} icon={languageManager.rtl ? mdiChevronLeft : mdiChevronRight}
onclick={onNext} onclick={onNext}
aria-label={$t('next')} aria-label={$t('next')}
/> />
@@ -47,7 +47,7 @@
data-asset-id={asset.id} data-asset-id={asset.id}
class="absolute" class="absolute"
style:top={position.top + 'px'} style:top={position.top + 'px'}
style:left={position.left + 'px'} style:inset-inline-start={position.left + 'px'}
style:width={position.width + 'px'} style:width={position.width + 'px'}
style:height={position.height + 'px'} style:height={position.height + 'px'}
out:scale|global={{ start: 0.1, duration: scaleDuration }} out:scale|global={{ start: 0.1, duration: scaleDuration }}
+2 -2
View File
@@ -54,7 +54,6 @@
</script> </script>
{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)} {#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
{@const absoluteWidth = dayGroup.left}
{@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)} {@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<section <section
@@ -64,7 +63,8 @@
]} ]}
data-group data-group
style:position="absolute" style:position="absolute"
style:transform={`translate3d(${absoluteWidth}px,${dayGroup.top}px,0)`} style:inset-inline-start={dayGroup.start + 'px'}
style:top={dayGroup.top + 'px'}
onmouseenter={() => (hoveredDayGroup = dayGroup.groupTitle)} onmouseenter={() => (hoveredDayGroup = dayGroup.groupTitle)}
onmouseleave={() => (hoveredDayGroup = null)} onmouseleave={() => (hoveredDayGroup = null)}
> >
@@ -617,7 +617,7 @@
<section <section
id="asset-grid" id="asset-grid"
class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ms-0': !isEmpty }]} class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ms-0': !isEmpty }]}
style:margin-right={(usingMobileDevice ? 0 : scrubberWidth) + 'px'} style:margin-inline-end={(usingMobileDevice ? 0 : scrubberWidth) + 'px'}
tabindex="-1" tabindex="-1"
bind:clientHeight={timelineManager.viewportHeight} bind:clientHeight={timelineManager.viewportHeight}
bind:clientWidth={timelineManager.viewportWidth} bind:clientWidth={timelineManager.viewportWidth}
@@ -21,7 +21,7 @@ export class DayGroup {
intersecting = $derived.by(() => this.viewerAssets.some((viewAsset) => viewAsset.intersecting)); intersecting = $derived.by(() => this.viewerAssets.some((viewAsset) => viewAsset.intersecting));
#top: number = $state(0); #top: number = $state(0);
#left: number = $state(0); #start: number = $state(0);
#row = $state(0); #row = $state(0);
#col = $state(0); #col = $state(0);
#deferredLayout = false; #deferredLayout = false;
@@ -41,12 +41,12 @@ export class DayGroup {
this.#top = value; this.#top = value;
} }
get left() { get start() {
return this.#left; return this.#start;
} }
set left(value: number) { set start(value: number) {
this.#left = value; this.#start = value;
} }
get row() { get row() {
@@ -39,7 +39,7 @@ export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthG
if (fitsInCurrentRow) { if (fitsInCurrentRow) {
dayGroup.row = dayGroupRow; dayGroup.row = dayGroupRow;
dayGroup.col = dayGroupCol++; dayGroup.col = dayGroupCol++;
dayGroup.left = cumulativeWidth; dayGroup.start = cumulativeWidth;
dayGroup.top = cumulativeHeight; dayGroup.top = cumulativeHeight;
cumulativeWidth += dayGroup.width + timelineManager.gap; cumulativeWidth += dayGroup.width + timelineManager.gap;
@@ -53,7 +53,7 @@ export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthG
// Position at start of new row // Position at start of new row
dayGroup.row = dayGroupRow; dayGroup.row = dayGroupRow;
dayGroup.col = dayGroupCol; dayGroup.col = dayGroupCol;
dayGroup.left = 0; dayGroup.start = 0;
dayGroup.top = cumulativeHeight; dayGroup.top = cumulativeHeight;
dayGroupCol++; dayGroupCol++;