mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:37:11 -04:00 
			
		
		
		
	feat: rtl (#17860)
This commit is contained in:
		
							parent
							
								
									85ac0512a6
								
							
						
					
					
						commit
						e6c575c33e
					
				
							
								
								
									
										8
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -11,7 +11,7 @@ | ||||
|       "dependencies": { | ||||
|         "@formatjs/icu-messageformat-parser": "^2.9.8", | ||||
|         "@immich/sdk": "file:../open-api/typescript-sdk", | ||||
|         "@immich/ui": "^0.17.3", | ||||
|         "@immich/ui": "^0.18.1", | ||||
|         "@mapbox/mapbox-gl-rtl-text": "0.2.3", | ||||
|         "@mdi/js": "^7.4.47", | ||||
|         "@photo-sphere-viewer/core": "^5.11.5", | ||||
| @ -1320,9 +1320,9 @@ | ||||
|       "link": true | ||||
|     }, | ||||
|     "node_modules/@immich/ui": { | ||||
|       "version": "0.17.4", | ||||
|       "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.17.4.tgz", | ||||
|       "integrity": "sha512-a6M7Fxno5fwY5A0kxdluS8r+A4L6xZhSTKMW8c8hoFhQHvbBTHAsGFKQF3GOEQLOlUuvsS2Lt7dMevBlAPgo/A==", | ||||
|       "version": "0.18.1", | ||||
|       "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.18.1.tgz", | ||||
|       "integrity": "sha512-XWWO6OTfH3MektyxCn0hWefZyOGyWwwx/2zHinuShpxTHSyfveJ4mOkFP8DkyMz0dnvJ1EfdkPBMkld3y5R/Hw==", | ||||
|       "license": "GNU Affero General Public License version 3", | ||||
|       "dependencies": { | ||||
|         "@mdi/js": "^7.4.47", | ||||
|  | ||||
| @ -27,7 +27,7 @@ | ||||
|   "dependencies": { | ||||
|     "@formatjs/icu-messageformat-parser": "^2.9.8", | ||||
|     "@immich/sdk": "file:../open-api/typescript-sdk", | ||||
|     "@immich/ui": "^0.17.3", | ||||
|     "@immich/ui": "^0.18.1", | ||||
|     "@mapbox/mapbox-gl-rtl-text": "0.2.3", | ||||
|     "@mdi/js": "^7.4.47", | ||||
|     "@photo-sphere-viewer/core": "^5.11.5", | ||||
|  | ||||
| @ -51,7 +51,7 @@ | ||||
|   let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused); | ||||
|   let multipleButtons = $derived(allText || refreshText); | ||||
| 
 | ||||
|   const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6'; | ||||
|   const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pe-4 ps-6'; | ||||
| </script> | ||||
| 
 | ||||
| <div | ||||
| @ -110,7 +110,7 @@ | ||||
| 
 | ||||
|       <div class="mt-2 flex w-full max-w-md flex-col sm:flex-row"> | ||||
|         <div | ||||
|           class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-l-lg sm:rounded-r-none" | ||||
|           class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-s-lg sm:rounded-e-none" | ||||
|         > | ||||
|           <p>{$t('active')}</p> | ||||
|           <p class="text-2xl"> | ||||
| @ -119,7 +119,7 @@ | ||||
|         </div> | ||||
| 
 | ||||
|         <div | ||||
|           class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-l-none sm:rounded-r-lg" | ||||
|           class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-s-none sm:rounded-e-lg" | ||||
|         > | ||||
|           <p class="text-2xl"> | ||||
|             {waitingCount.toLocaleString($locale)} | ||||
|  | ||||
| @ -79,7 +79,7 @@ | ||||
|             <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span | ||||
|               class="text-immich-primary dark:text-immich-dark-primary">{statsUsage}</span | ||||
|             > | ||||
|             <span class="my-auto ml-2 text-center text-base font-light text-gray-400">{statsUsageUnit}</span> | ||||
|             <span class="my-auto ms-2 text-center text-base font-light text-gray-400">{statsUsageUnit}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| @ -88,7 +88,7 @@ | ||||
| 
 | ||||
|   <div> | ||||
|     <p class="text-sm dark:text-immich-dark-fg">{$t('user_usage_detail').toUpperCase()}</p> | ||||
|     <table class="mt-5 w-full text-left"> | ||||
|     <table class="mt-5 w-full text-start"> | ||||
|       <thead | ||||
|         class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary" | ||||
|       > | ||||
|  | ||||
| @ -31,7 +31,7 @@ | ||||
|       class="text-immich-primary dark:text-immich-dark-primary">{value}</span | ||||
|     > | ||||
|     {#if unit} | ||||
|       <span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span> | ||||
|       <span class="absolute -top-5 end-2 text-base font-light text-gray-400">{unit}</span> | ||||
|     {/if} | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @ -76,13 +76,13 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}> | ||||
|       <div class="ml-4 mt-4 flex flex-col"> | ||||
|       <div class="ms-4 mt-4 flex flex-col"> | ||||
|         <SettingAccordion | ||||
|           key="oauth" | ||||
|           title={$t('admin.oauth_settings')} | ||||
|           subtitle={$t('admin.oauth_settings_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <p class="text-sm dark:text-immich-dark-fg"> | ||||
|               <FormatMessage key="admin.oauth_settings_more_details"> | ||||
|                 {#snippet children({ message })} | ||||
| @ -243,8 +243,8 @@ | ||||
|           title={$t('admin.password_settings')} | ||||
|           subtitle={$t('admin.password_settings_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|             <div class="ml-4 mt-4 flex flex-col"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <div class="ms-4 mt-4 flex flex-col"> | ||||
|               <SettingSwitch | ||||
|                 title={$t('admin.password_enable_description')} | ||||
|                 {disabled} | ||||
|  | ||||
| @ -37,7 +37,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('admin.backup_database_enable_description')} | ||||
|           {disabled} | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <p class="text-sm dark:text-immich-dark-fg"> | ||||
|           <Icon path={mdiHelpCircleOutline} class="inline" size="15" /> | ||||
|           <FormatMessage key="admin.transcoding_codecs_learn_more"> | ||||
| @ -70,7 +70,7 @@ | ||||
|           title={$t('admin.transcoding_policy')} | ||||
|           subtitle={$t('admin.transcoding_policy_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSelect | ||||
|               label={$t('admin.transcoding_transcode_policy')} | ||||
|               {disabled} | ||||
| @ -159,7 +159,7 @@ | ||||
|           title={$t('admin.transcoding_encoding_options')} | ||||
|           subtitle={$t('admin.transcoding_encoding_options_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSelect | ||||
|               label={$t('admin.transcoding_video_codec')} | ||||
|               {disabled} | ||||
| @ -302,7 +302,7 @@ | ||||
|           title={$t('admin.transcoding_hardware_acceleration')} | ||||
|           subtitle={$t('admin.transcoding_hardware_acceleration_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSelect | ||||
|               label={$t('admin.transcoding_acceleration_api')} | ||||
|               {disabled} | ||||
| @ -376,7 +376,7 @@ | ||||
|           title={$t('advanced')} | ||||
|           subtitle={$t('admin.transcoding_advanced_options_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingInputField | ||||
|               inputType={SettingInputFieldType.NUMBER} | ||||
|               label={$t('admin.transcoding_max_b_frames')} | ||||
| @ -407,7 +407,7 @@ | ||||
|         </SettingAccordion> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingButtonsRow | ||||
|           onReset={(options) => onReset({ ...options, configKeys: ['ffmpeg'] })} | ||||
|           onSave={() => onSave({ ffmpeg: config.ffmpeg })} | ||||
|  | ||||
| @ -40,7 +40,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4"> | ||||
|       <div class="ms-4 mt-4"> | ||||
|         <SettingAccordion | ||||
|           key="thumbnail-settings" | ||||
|           title={$t('admin.image_thumbnail_title')} | ||||
| @ -195,7 +195,7 @@ | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4 mt-4"> | ||||
|       <div class="ms-4 mt-4"> | ||||
|         <SettingButtonsRow | ||||
|           onReset={(options) => onReset({ ...options, configKeys: ['image'] })} | ||||
|           onSave={() => onSave({ image: config.image })} | ||||
|  | ||||
| @ -47,7 +47,7 @@ | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       {#each jobNames as jobName (jobName)} | ||||
|         <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|         <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|           {#if isSystemConfigJobDto(jobName)} | ||||
|             <SettingInputField | ||||
|               inputType={SettingInputFieldType.NUMBER} | ||||
| @ -71,7 +71,7 @@ | ||||
|         </div> | ||||
|       {/each} | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingButtonsRow | ||||
|           onReset={(options) => onReset({ ...options, configKeys: ['job'] })} | ||||
|           onSave={() => onSave({ job: config.job })} | ||||
|  | ||||
| @ -47,14 +47,14 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingAccordion | ||||
|           key="library-watching" | ||||
|           title={$t('admin.library_watching_settings')} | ||||
|           subtitle={$t('admin.library_watching_settings_description')} | ||||
|           isOpen={openByDefault} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSwitch | ||||
|               title={$t('admin.library_watching_enable_description')} | ||||
|               {disabled} | ||||
| @ -69,7 +69,7 @@ | ||||
|           subtitle={$t('admin.library_scanning_description')} | ||||
|           isOpen={openByDefault} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSwitch | ||||
|               title={$t('admin.library_scanning_enable_description')} | ||||
|               {disabled} | ||||
|  | ||||
| @ -27,7 +27,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('admin.logging_enable_description')} | ||||
|           {disabled} | ||||
|  | ||||
| @ -51,7 +51,7 @@ | ||||
|               {#if config.machineLearning.urls.length > 1} | ||||
|                 <CircleIconButton | ||||
|                   size="24" | ||||
|                   class="ml-2" | ||||
|                   class="ms-2" | ||||
|                   padding="2" | ||||
|                   color="red" | ||||
|                   title="" | ||||
| @ -88,7 +88,7 @@ | ||||
|         title={$t('admin.machine_learning_smart_search')} | ||||
|         subtitle={$t('admin.machine_learning_smart_search_description')} | ||||
|       > | ||||
|         <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|         <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|           <SettingSwitch | ||||
|             title={$t('admin.machine_learning_smart_search_enabled')} | ||||
|             subtitle={$t('admin.machine_learning_smart_search_enabled_description')} | ||||
| @ -124,7 +124,7 @@ | ||||
|         title={$t('admin.machine_learning_duplicate_detection')} | ||||
|         subtitle={$t('admin.machine_learning_duplicate_detection_setting_description')} | ||||
|       > | ||||
|         <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|         <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|           <SettingSwitch | ||||
|             title={$t('admin.machine_learning_duplicate_detection_enabled')} | ||||
|             subtitle={$t('admin.machine_learning_duplicate_detection_enabled_description')} | ||||
| @ -154,7 +154,7 @@ | ||||
|         title={$t('admin.machine_learning_facial_recognition')} | ||||
|         subtitle={$t('admin.machine_learning_facial_recognition_description')} | ||||
|       > | ||||
|         <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|         <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|           <SettingSwitch | ||||
|             title={$t('admin.machine_learning_facial_recognition_setting')} | ||||
|             subtitle={$t('admin.machine_learning_facial_recognition_setting_description')} | ||||
|  | ||||
| @ -32,7 +32,7 @@ | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="flex flex-col gap-4"> | ||||
|         <SettingAccordion key="map" title={$t('admin.map_settings')} subtitle={$t('admin.map_settings_description')}> | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSwitch | ||||
|               title={$t('admin.map_enable_description')} | ||||
|               subtitle={$t('admin.map_implications')} | ||||
| @ -78,7 +78,7 @@ | ||||
|               </FormatMessage> | ||||
|             </p> | ||||
|           {/snippet} | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSwitch | ||||
|               title={$t('admin.map_reverse_geocoding_enable_description')} | ||||
|               {disabled} | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
| <div class="mt-2"> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit} class="mx-4 mt-4"> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('admin.metadata_faces_import_setting')} | ||||
|           subtitle={$t('admin.metadata_faces_import_setting_description')} | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4"> | ||||
|       <div class="ms-4 mt-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('admin.version_check_enabled_description')} | ||||
|           subtitle={$t('admin.version_check_implications')} | ||||
|  | ||||
| @ -80,7 +80,7 @@ | ||||
|     <form autocomplete="off" {onsubmit} class="mt-4"> | ||||
|       <div class="flex flex-col gap-4"> | ||||
|         <SettingAccordion key="email" title={$t('email')} subtitle={$t('admin.notification_email_setting_description')}> | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <SettingSwitch | ||||
|               title={$t('admin.notification_enable_email_notifications')} | ||||
|               {disabled} | ||||
|  | ||||
| @ -28,7 +28,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="mt-4 ml-4"> | ||||
|       <div class="mt-4 ms-4"> | ||||
|         <SettingInputField | ||||
|           inputType={SettingInputFieldType.TEXT} | ||||
|           label={$t('admin.server_external_domain_settings')} | ||||
| @ -52,7 +52,7 @@ | ||||
|           bind:checked={config.server.publicUsers} | ||||
|         /> | ||||
| 
 | ||||
|         <div class="ml-4"> | ||||
|         <div class="ms-4"> | ||||
|           <SettingButtonsRow | ||||
|             onReset={(options) => onReset({ ...options, configKeys: ['server'] })} | ||||
|             onSave={() => onSave({ server: config.server })} | ||||
|  | ||||
| @ -141,7 +141,7 @@ | ||||
|     </p> | ||||
|   </div> | ||||
|   {#await getTemplateOptions() then} | ||||
|     <div id="directory-path-builder" class="flex flex-col gap-4 {minified ? '' : 'ml-4 mt-4'}"> | ||||
|     <div id="directory-path-builder" class="flex flex-col gap-4 {minified ? '' : 'ms-4 mt-4'}"> | ||||
|       <SettingSwitch | ||||
|         title={$t('admin.storage_template_enable_description')} | ||||
|         {disabled} | ||||
|  | ||||
| @ -76,7 +76,7 @@ | ||||
|           title={$t('admin.template_email_settings')} | ||||
|           subtitle={$t('admin.template_settings_description')} | ||||
|         > | ||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|             <p class="text-sm dark:text-immich-dark-fg"> | ||||
|               <FormatMessage key="admin.template_email_if_empty"> | ||||
|                 {$t('admin.template_email_if_empty')} | ||||
| @ -102,7 +102,7 @@ | ||||
|                   onclick={() => getTemplate(templateName, config.templates.email[templateKey])} | ||||
|                   title={$t('admin.template_email_preview')} | ||||
|                 > | ||||
|                   <Icon path={mdiEyeOutline} class="mr-1" /> | ||||
|                   <Icon path={mdiEyeOutline} class="me-1" /> | ||||
|                   {$t('admin.template_email_preview')} | ||||
|                 </Button> | ||||
|               </div> | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingTextarea | ||||
|           {disabled} | ||||
|           label={$t('admin.theme_custom_css_settings')} | ||||
|  | ||||
| @ -28,7 +28,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingSwitch title={$t('admin.trash_enabled_description')} {disabled} bind:checked={config.trash.enabled} /> | ||||
| 
 | ||||
|         <hr /> | ||||
|  | ||||
| @ -24,7 +24,7 @@ | ||||
| <div> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingInputField | ||||
|           inputType={SettingInputFieldType.NUMBER} | ||||
|           min={1} | ||||
| @ -35,7 +35,7 @@ | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingButtonsRow | ||||
|           onReset={(options) => onReset({ ...options, configKeys: ['user'] })} | ||||
|           onSave={() => onSave({ user: config.user })} | ||||
|  | ||||
| @ -48,7 +48,7 @@ | ||||
|     <button | ||||
|       type="button" | ||||
|       onclick={() => toggleAlbumGroupCollapsing(group.id)} | ||||
|       class="w-full text-left mt-2 pt-2 pr-2 pb-2 rounded-md transition-colors cursor-pointer dark:text-immich-dark-fg hover:text-immich-primary dark:hover:text-immich-dark-primary hover:bg-immich-gray dark:hover:bg-immich-dark-gray" | ||||
|       class="w-full text-start mt-2 pt-2 pe-2 pb-2 rounded-md transition-colors cursor-pointer dark:text-immich-dark-fg hover:text-immich-primary dark:hover:text-immich-dark-primary hover:bg-immich-gray dark:hover:bg-immich-dark-gray" | ||||
|       aria-expanded={!isCollapsed} | ||||
|     > | ||||
|       <Icon | ||||
| @ -57,7 +57,7 @@ | ||||
|         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}" | ||||
|       /> | ||||
|       <span class="font-bold text-3xl text-black dark:text-white">{group.name}</span> | ||||
|       <span class="ml-1.5">({$t('albums_count', { values: { count: albums.length } })})</span> | ||||
|       <span class="ms-1.5">({$t('albums_count', { values: { count: albums.length } })})</span> | ||||
|     </button> | ||||
|     <hr class="dark:border-immich-dark-gray" /> | ||||
|   </div> | ||||
|  | ||||
| @ -40,7 +40,7 @@ | ||||
|   {#if onShowContextMenu} | ||||
|     <div | ||||
|       id="icon-{album.id}" | ||||
|       class="absolute right-6 top-6 z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100" | ||||
|       class="absolute end-6 top-6 z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100" | ||||
|       data-testid="context-button-parent" | ||||
|     > | ||||
|       <CircleIconButton | ||||
|  | ||||
| @ -117,7 +117,7 @@ | ||||
|       <!-- ALBUM DESCRIPTION --> | ||||
|       {#if album.description} | ||||
|         <p | ||||
|           class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-left font-medium text-base text-black dark:text-gray-300" | ||||
|           class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-start font-medium text-base text-black dark:text-gray-300" | ||||
|         > | ||||
|           {album.description} | ||||
|         </p> | ||||
|  | ||||
| @ -35,13 +35,13 @@ | ||||
|   onclick={() => goto(`${AppRoute.ALBUMS}/${album.id}`)} | ||||
|   {oncontextmenu} | ||||
| > | ||||
|   <td class="text-md text-ellipsis text-left w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center"> | ||||
|   <td class="text-md text-ellipsis text-start w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center"> | ||||
|     {album.albumName} | ||||
|     {#if album.shared} | ||||
|       <Icon | ||||
|         path={mdiShareVariantOutline} | ||||
|         size="16" | ||||
|         class="inline ml-1 opacity-70" | ||||
|         class="inline ms-1 opacity-70" | ||||
|         title={album.ownerId === $user.id | ||||
|           ? $t('shared_by_you') | ||||
|           : $t('shared_by_user', { values: { user: album.owner.name } })} | ||||
|  | ||||
| @ -24,7 +24,7 @@ | ||||
|   let { groupedAlbums, albumGroupOption = AlbumGroupBy.None, onShowContextMenu }: Props = $props(); | ||||
| </script> | ||||
| 
 | ||||
| <table class="mt-2 w-full text-left"> | ||||
| <table class="mt-2 w-full text-start"> | ||||
|   <thead | ||||
|     class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary" | ||||
|   > | ||||
| @ -48,18 +48,18 @@ | ||||
|         class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg" | ||||
|       > | ||||
|         <tr | ||||
|           class="flex w-full place-items-center p-2 md:pl-5 md:pr-5 md:pt-3 md:pb-3" | ||||
|           class="flex w-full place-items-center p-2 md:ps-5 md:pe-5 md:pt-3 md:pb-3" | ||||
|           onclick={() => toggleAlbumGroupCollapsing(albumGroup.id)} | ||||
|           aria-expanded={!isCollapsed} | ||||
|         > | ||||
|           <td class="text-md text-left -mb-1"> | ||||
|           <td class="text-md text-start -mb-1"> | ||||
|             <Icon | ||||
|               path={mdiChevronRight} | ||||
|               size="20" | ||||
|               class="inline-block -mt-2 transition-all duration-[250ms] {iconRotation}" | ||||
|             /> | ||||
|             <span class="font-bold text-2xl">{albumGroup.name}</span> | ||||
|             <span class="ml-1.5"> | ||||
|             <span class="ms-1.5"> | ||||
|               ({$t('albums_count', { values: { count: albumGroup.albums.length } })}) | ||||
|             </span> | ||||
|           </td> | ||||
|  | ||||
| @ -94,7 +94,7 @@ | ||||
|                 </div> | ||||
| 
 | ||||
|                 <!-- <UserAvatar {user} size="md" /> --> | ||||
|                 <div class="text-left flex-grow"> | ||||
|                 <div class="text-start flex-grow"> | ||||
|                   <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||
|                     {user.name} | ||||
|                   </p> | ||||
| @ -136,7 +136,7 @@ | ||||
|                   class="flex w-full place-items-center gap-4 p-4" | ||||
|                 > | ||||
|                   <UserAvatar {user} size="md" /> | ||||
|                   <div class="text-left flex-grow"> | ||||
|                   <div class="text-start flex-grow"> | ||||
|                     <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||
|                       {user.name} | ||||
|                     </p> | ||||
|  | ||||
| @ -186,7 +186,7 @@ | ||||
|       > | ||||
|         {#each reactions as reaction, index (reaction.id)} | ||||
|           {#if reaction.type === ReactionType.Comment} | ||||
|             <div class="flex dark:bg-gray-800 bg-gray-200 py-3 pl-3 mt-3 rounded-lg gap-4 justify-start"> | ||||
|             <div class="flex dark:bg-gray-800 bg-gray-200 py-3 ps-3 mt-3 rounded-lg gap-4 justify-start"> | ||||
|               <div class="flex items-center"> | ||||
|                 <UserAvatar user={reaction.user} size="sm" /> | ||||
|               </div> | ||||
| @ -202,7 +202,7 @@ | ||||
|                 </a> | ||||
|               {/if} | ||||
|               {#if reaction.user.id === user.id || albumOwnerId === user.id} | ||||
|                 <div class="mr-4"> | ||||
|                 <div class="me-4"> | ||||
|                   <ButtonContextMenu | ||||
|                     icon={mdiDotsVertical} | ||||
|                     title={$t('comment_options')} | ||||
| @ -231,7 +231,7 @@ | ||||
|             {/if} | ||||
|           {:else if reaction.type === ReactionType.Like} | ||||
|             <div class="relative"> | ||||
|               <div class="flex py-3 pl-3 mt-3 gap-4 items-center text-sm"> | ||||
|               <div class="flex py-3 ps-3 mt-3 gap-4 items-center text-sm"> | ||||
|                 <div class="text-red-600"><Icon path={mdiHeart} size={20} /></div> | ||||
| 
 | ||||
|                 <div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}> | ||||
| @ -255,7 +255,7 @@ | ||||
|                   </a> | ||||
|                 {/if} | ||||
|                 {#if reaction.user.id === user.id || albumOwnerId === user.id} | ||||
|                   <div class="mr-4"> | ||||
|                   <div class="me-4"> | ||||
|                     <ButtonContextMenu | ||||
|                       icon={mdiDotsVertical} | ||||
|                       title={$t('reaction_options')} | ||||
| @ -307,17 +307,17 @@ | ||||
|               }} | ||||
|               class="h-[18px] {disabled | ||||
|                 ? 'cursor-not-allowed' | ||||
|                 : ''} w-full max-h-56 pr-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200" | ||||
|                 : ''} w-full max-h-56 pe-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200" | ||||
|             ></textarea> | ||||
|           </div> | ||||
|           {#if isSendingMessage} | ||||
|             <div class="flex items-end place-items-center pb-2 ml-0"> | ||||
|             <div class="flex items-end place-items-center pb-2 ms-0"> | ||||
|               <div class="flex w-full place-items-center"> | ||||
|                 <LoadingSpinner /> | ||||
|               </div> | ||||
|             </div> | ||||
|           {:else if message} | ||||
|             <div class="flex items-end w-fit ml-0"> | ||||
|             <div class="flex items-end w-fit ms-0"> | ||||
|               <CircleIconButton | ||||
|                 title={$t('send_message')} | ||||
|                 size="15" | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
|   type="button" | ||||
|   onclick={onAlbumClick} | ||||
|   use:scrollIntoViewIfSelected | ||||
|   class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl" | ||||
|   class="flex w-full gap-4 px-6 py-2 text-start transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl" | ||||
|   class:bg-gray-200={selected} | ||||
|   class:dark:bg-gray-700={selected} | ||||
| > | ||||
|  | ||||
| @ -422,7 +422,7 @@ | ||||
| 
 | ||||
| <section | ||||
|   id="immich-asset-viewer" | ||||
|   class="fixed left-0 top-0 z-[1001] grid size-full grid-cols-4 grid-rows-[64px_1fr] overflow-hidden bg-black" | ||||
|   class="fixed start-0 top-0 z-[1001] grid size-full grid-cols-4 grid-rows-[64px_1fr] overflow-hidden bg-black" | ||||
|   use:focusTrap | ||||
| > | ||||
|   <!-- Top navigation bar --> | ||||
| @ -547,7 +547,7 @@ | ||||
|           /> | ||||
|         {/if} | ||||
|         {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || numberOfComments > 0)} | ||||
|           <div class="z-[9999] absolute bottom-0 right-0 mb-20 mr-8"> | ||||
|           <div class="z-[9999] absolute bottom-0 end-0 mb-20 me-8"> | ||||
|             <ActivityStatus | ||||
|               disabled={!album?.isActivityEnabled} | ||||
|               {isLiked} | ||||
| @ -571,7 +571,7 @@ | ||||
|     <div | ||||
|       transition:fly={{ duration: 150 }} | ||||
|       id="detail-panel" | ||||
|       class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       translate="yes" | ||||
|     > | ||||
|       <DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} /> | ||||
| @ -582,7 +582,7 @@ | ||||
|     <div | ||||
|       transition:fly={{ duration: 150 }} | ||||
|       id="editor-panel" | ||||
|       class="z-[1002] row-start-1 row-span-4 w-[400px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       class="z-[1002] row-start-1 row-span-4 w-[400px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       translate="yes" | ||||
|     > | ||||
|       <EditorPanel {asset} onUpdateSelectedType={handleUpdateSelectedEditType} onClose={closeEditor} /> | ||||
| @ -631,7 +631,7 @@ | ||||
|     <div | ||||
|       transition:fly={{ duration: 150 }} | ||||
|       id="activity-panel" | ||||
|       class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg" | ||||
|       translate="yes" | ||||
|     > | ||||
|       <ActivityViewer | ||||
|  | ||||
| @ -33,7 +33,7 @@ | ||||
| {#if asset.exifInfo?.country} | ||||
|   <button | ||||
|     type="button" | ||||
|     class="flex w-full text-left justify-between place-items-start gap-4 py-4" | ||||
|     class="flex w-full text-start justify-between place-items-start gap-4 py-4" | ||||
|     onclick={() => (isOwner ? (isShowChangeLocation = true) : null)} | ||||
|     title={isOwner ? $t('edit_location') : ''} | ||||
|     class:hover:dark:text-immich-dark-primary={isOwner} | ||||
| @ -68,7 +68,7 @@ | ||||
| {:else if !asset.exifInfo?.city && isOwner} | ||||
|   <button | ||||
|     type="button" | ||||
|     class="flex w-full text-left justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary" | ||||
|     class="flex w-full text-start justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary" | ||||
|     onclick={() => (isShowChangeLocation = true)} | ||||
|     title={$t('add_location')} | ||||
|   > | ||||
|  | ||||
| @ -50,7 +50,7 @@ | ||||
|       {#each tags as tag (tag.id)} | ||||
|         <div class="flex group transition-all"> | ||||
|           <a | ||||
|             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" | ||||
|             href={encodeURI(`${AppRoute.TAGS}/?path=${tag.value}`)} | ||||
|           > | ||||
|             <p class="text-sm"> | ||||
| @ -60,7 +60,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(tag.id)} | ||||
|           > | ||||
|  | ||||
| @ -296,7 +296,7 @@ | ||||
|     {#if dateTime} | ||||
|       <button | ||||
|         type="button" | ||||
|         class="flex w-full text-left justify-between place-items-start gap-4 py-4" | ||||
|         class="flex w-full text-start justify-between place-items-start gap-4 py-4" | ||||
|         onclick={() => (isOwner ? (isShowChangeDate = true) : null)} | ||||
|         title={isOwner ? $t('edit_date') : ''} | ||||
|         class:hover:dark:text-immich-dark-primary={isOwner} | ||||
|  | ||||
| @ -16,14 +16,14 @@ | ||||
| {#if downloadStore.isDownloading} | ||||
|   <div | ||||
|     transition:fly={{ x: -100, duration: 350 }} | ||||
|     class="fixed bottom-10 left-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border bg-immich-bg p-4 text-sm shadow-sm" | ||||
|     class="fixed bottom-10 start-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border bg-immich-bg p-4 text-sm shadow-sm" | ||||
|   > | ||||
|     <p class="mb-2 text-xs text-gray-500">{$t('downloading').toUpperCase()}</p> | ||||
|     <div class="my-2 mb-2 flex max-h-[200px] flex-col overflow-y-auto text-sm"> | ||||
|       {#each Object.keys(downloadStore.assets) as downloadKey (downloadKey)} | ||||
|         {@const download = downloadStore.assets[downloadKey]} | ||||
|         <div class="mb-2 flex place-items-center" transition:slide> | ||||
|           <div class="w-full pr-10"> | ||||
|           <div class="w-full pe-10"> | ||||
|             <div class="flex place-items-center justify-between gap-2 text-xs font-medium"> | ||||
|               <p class="truncate">■ {downloadKey}</p> | ||||
|               {#if download.total} | ||||
| @ -41,7 +41,7 @@ | ||||
|               </p> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="absolute right-2"> | ||||
|           <div class="absolute end-2"> | ||||
|             <CircleIconButton | ||||
|               title={$t('close')} | ||||
|               onclick={() => abort(downloadKey, download)} | ||||
|  | ||||
| @ -308,13 +308,13 @@ | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div class="absolute left-0 top-0"> | ||||
|   <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 left-0"></canvas> | ||||
| <div class="absolute start-0 top-0"> | ||||
|   <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 start-0"></canvas> | ||||
| 
 | ||||
|   <div | ||||
|     id="face-selector" | ||||
|     bind:this={faceSelectorEl} | ||||
|     class="absolute top-[calc(50%-250px)] left-[calc(50%-125px)] max-w-[250px] w-[250px] bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800" | ||||
|     class="absolute top-[calc(50%-250px)] start-[calc(50%-125px)] max-w-[250px] w-[250px] bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800" | ||||
|   > | ||||
|     <p class="text-center text-sm">Select a person to tag</p> | ||||
| 
 | ||||
| @ -329,7 +329,7 @@ | ||||
|             <button | ||||
|               onclick={() => tagFace(person)} | ||||
|               type="button" | ||||
|               class="w-full flex place-items-center gap-2 rounded-lg pl-1 pr-4 py-2 hover:bg-immich-primary/25" | ||||
|               class="w-full flex place-items-center gap-2 rounded-lg ps-1 pe-4 py-2 hover:bg-immich-primary/25" | ||||
|             > | ||||
|               <ImageThumbnail | ||||
|                 curve | ||||
|  | ||||
| @ -213,7 +213,7 @@ | ||||
|         <img | ||||
|           src={assetFileUrl} | ||||
|           alt={$getAltText(asset)} | ||||
|           class="absolute top-0 left-0 -z-10 object-cover h-full w-full blur-lg" | ||||
|           class="absolute top-0 start-0 -z-10 object-cover h-full w-full blur-lg" | ||||
|           draggable="false" | ||||
|         /> | ||||
|       {/if} | ||||
|  | ||||
| @ -101,7 +101,7 @@ | ||||
| {/if} | ||||
| 
 | ||||
| {#if hidden} | ||||
|   <div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"> | ||||
|   <div class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"> | ||||
|     <Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} /> | ||||
|   </div> | ||||
| {/if} | ||||
|  | ||||
| @ -332,20 +332,20 @@ | ||||
| 
 | ||||
|         <!-- Favorite asset star --> | ||||
|         {#if !isSharedLink() && asset.isFavorite} | ||||
|           <div class="absolute bottom-2 left-2 z-10"> | ||||
|           <div class="absolute bottom-2 start-2 z-10"> | ||||
|             <Icon path={mdiHeart} size="24" class="text-white" /> | ||||
|           </div> | ||||
|         {/if} | ||||
| 
 | ||||
|         {#if !isSharedLink() && showArchiveIcon && asset.isArchived} | ||||
|           <div class={['absolute left-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}> | ||||
|           <div class={['absolute start-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}> | ||||
|             <Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" /> | ||||
|           </div> | ||||
|         {/if} | ||||
| 
 | ||||
|         {#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR} | ||||
|           <div class="absolute right-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white"> | ||||
|             <span class="pr-2 pt-2"> | ||||
|           <div class="absolute end-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white"> | ||||
|             <span class="pe-2 pt-2"> | ||||
|               <Icon path={mdiRotate360} size="24" /> | ||||
|             </span> | ||||
|           </div> | ||||
| @ -356,10 +356,10 @@ | ||||
|           <div | ||||
|             class={[ | ||||
|               'absolute z-10 flex place-items-center gap-1 text-xs font-medium text-white', | ||||
|               asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 right-0' : 'top-7 right-1', | ||||
|               asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 end-0' : 'top-7 end-1', | ||||
|             ]} | ||||
|           > | ||||
|             <span class="pr-2 pt-2 flex place-items-center gap-1"> | ||||
|             <span class="pe-2 pt-2 flex place-items-center gap-1"> | ||||
|               <p>{asset.stack.assetCount.toLocaleString($locale)}</p> | ||||
|               <Icon path={mdiCameraBurst} size="24" /> | ||||
|             </span> | ||||
|  | ||||
| @ -55,7 +55,7 @@ | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div class="absolute right-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white"> | ||||
| <div class="absolute end-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white"> | ||||
|   {#if showTime} | ||||
|     <span class="pt-2"> | ||||
|       {#if remainingSeconds < 60} | ||||
| @ -69,7 +69,7 @@ | ||||
|   {/if} | ||||
| 
 | ||||
|   <!-- svelte-ignore a11y_no_static_element_interactions --> | ||||
|   <span class="pr-2 pt-2" onmouseenter={onMouseEnter} onmouseleave={onMouseLeave}> | ||||
|   <span class="pe-2 pt-2" onmouseenter={onMouseEnter} onmouseleave={onMouseLeave}> | ||||
|     {#if enablePlayback} | ||||
|       {#if loading} | ||||
|         <LoadingSpinner /> | ||||
|  | ||||
| @ -79,7 +79,7 @@ | ||||
|   }; | ||||
| 
 | ||||
|   const sizeClasses: Record<Size, string> = { | ||||
|     tiny: 'p-0 ml-2 mr-0 align-top', | ||||
|     tiny: 'p-0 ms-2 me-0 align-top', | ||||
|     icon: 'p-2.5', | ||||
|     link: 'p-2 font-medium', | ||||
|     sm: 'px-4 py-2 text-sm font-medium', | ||||
|  | ||||
| @ -50,7 +50,7 @@ | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div class="absolute z-50 top-2 left-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}"> | ||||
| <div class="absolute z-50 top-2 start-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}"> | ||||
|   <Button | ||||
|     size="sm" | ||||
|     rounded="none" | ||||
|  | ||||
| @ -82,10 +82,10 @@ | ||||
|   const getAlignClass = (position: 'bottom-left' | 'bottom-right') => { | ||||
|     switch (position) { | ||||
|       case 'bottom-left': { | ||||
|         return 'left-0'; | ||||
|         return 'start-0'; | ||||
|       } | ||||
|       case 'bottom-right': { | ||||
|         return 'right-0'; | ||||
|         return 'end-0'; | ||||
|       } | ||||
| 
 | ||||
|       default: { | ||||
|  | ||||
| @ -35,7 +35,7 @@ | ||||
|     : 'rounded-lg'}  bg-gray-100 p-2 dark:bg-gray-700 border border-gray-200 dark:border-immich-dark-gray" | ||||
| > | ||||
|   <ImageThumbnail circle shadow url={thumbnailData} altText={person.name} widthStyle="2rem" heightStyle="2rem" /> | ||||
|   <form class="ml-4 flex w-full justify-between gap-16" autocomplete="off" {onsubmit}> | ||||
|   <form class="ms-4 flex w-full justify-between gap-16" autocomplete="off" {onsubmit}> | ||||
|     <SearchPeople | ||||
|       bind:searchName={name} | ||||
|       bind:searchedPeopleLocal={suggestedPeople} | ||||
|  | ||||
| @ -44,7 +44,7 @@ | ||||
|   </div> | ||||
| 
 | ||||
|   <div | ||||
|     class="absolute left-0 top-0 h-full w-full bg-immich-primary/30 opacity-0" | ||||
|     class="absolute start-0 top-0 h-full w-full bg-immich-primary/30 opacity-0" | ||||
|     class:hover:opacity-100={selectable} | ||||
|     class:rounded-full={circle} | ||||
|     class:rounded-lg={!circle} | ||||
| @ -52,7 +52,7 @@ | ||||
| 
 | ||||
|   {#if selected} | ||||
|     <div | ||||
|       class="absolute left-0 top-0 h-full w-full bg-blue-500/80" | ||||
|       class="absolute start-0 top-0 h-full w-full bg-blue-500/80" | ||||
|       class:rounded-full={circle} | ||||
|       class:rounded-lg={!circle} | ||||
|     ></div> | ||||
| @ -60,7 +60,7 @@ | ||||
| 
 | ||||
|   {#if person.name} | ||||
|     <span | ||||
|       class="w-100 text-white-shadow absolute bottom-2 left-0 w-full text-ellipsis px-1 text-center font-medium text-white hover:cursor-pointer" | ||||
|       class="w-100 text-white-shadow absolute bottom-2 start-0 w-full text-ellipsis px-1 text-center font-medium text-white hover:cursor-pointer" | ||||
|     > | ||||
|       {person.name} | ||||
|     </span> | ||||
|  | ||||
| @ -117,12 +117,12 @@ | ||||
|   <div class="flex items-center"> | ||||
|     <CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} /> | ||||
|     <div class="flex gap-2 items-center"> | ||||
|       <p id={titleId} class="ml-2">{$t('show_and_hide_people')}</p> | ||||
|       <p id={titleId} class="ms-2">{$t('show_and_hide_people')}</p> | ||||
|       <p class="text-sm text-gray-400 dark:text-gray-600">({totalPeopleCount.toLocaleString($locale)})</p> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="flex items-center justify-end"> | ||||
|     <div class="flex items-center md:mr-4"> | ||||
|     <div class="flex items-center md:me-4"> | ||||
|       <CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} onclick={handleResetVisibility} /> | ||||
|       <CircleIconButton title={toggleButton.label} icon={toggleButton.icon} onclick={handleToggleVisibility} /> | ||||
|     </div> | ||||
| @ -154,7 +154,7 @@ | ||||
|           hiddenIconClass="text-white group-hover:text-black transition-colors" | ||||
|         /> | ||||
|         {#if person.name} | ||||
|           <span class="absolute bottom-2 left-0 w-full select-text px-1 text-center font-medium text-white"> | ||||
|           <span class="absolute bottom-2 start-0 w-full select-text px-1 text-center font-medium text-white"> | ||||
|             {person.name} | ||||
|           </span> | ||||
|         {/if} | ||||
|  | ||||
| @ -99,7 +99,7 @@ | ||||
| 
 | ||||
| <section | ||||
|   transition:fly={{ y: 500, duration: 100, easing: quintOut }} | ||||
|   class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg" | ||||
|   class="absolute start-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg" | ||||
| > | ||||
|   <ControlAppBar onClose={onBack}> | ||||
|     {#snippet leading()} | ||||
| @ -113,7 +113,7 @@ | ||||
|     {#snippet trailing()} | ||||
|       <Button size="sm" disabled={!hasSelection} onclick={handleMerge}> | ||||
|         <Icon path={mdiMerge} size={18} /> | ||||
|         <span class="ml-2">{$t('merge')}</span></Button | ||||
|         <span class="ms-2">{$t('merge')}</span></Button | ||||
|       > | ||||
|     {/snippet} | ||||
|   </ControlAppBar> | ||||
|  | ||||
| @ -54,7 +54,7 @@ | ||||
|         circle | ||||
|       /> | ||||
|       {#if person.isFavorite} | ||||
|         <div class="absolute top-4 left-4"> | ||||
|         <div class="absolute top-4 start-4"> | ||||
|           <Icon path={mdiHeart} size="24" class="text-white" /> | ||||
|         </div> | ||||
|       {/if} | ||||
| @ -62,7 +62,7 @@ | ||||
|   </a> | ||||
| 
 | ||||
|   {#if showVerticalDots} | ||||
|     <div class="absolute top-2 right-2"> | ||||
|     <div class="absolute top-2 end-2"> | ||||
|       <ButtonContextMenu | ||||
|         buttonClass="icon-white-drop-shadow focus:opacity-100 {showVerticalDots ? 'opacity-100' : 'opacity-0'}" | ||||
|         color="opaque" | ||||
|  | ||||
| @ -227,7 +227,7 @@ | ||||
|             <div | ||||
|               role="button" | ||||
|               tabindex={index} | ||||
|               class="absolute left-0 top-0 h-[90px] w-[90px] cursor-default" | ||||
|               class="absolute start-0 top-0 h-[90px] w-[90px] cursor-default" | ||||
|               onfocus={() => ($boundingBoxesArray = [peopleWithFaces[index]])} | ||||
|               onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])} | ||||
|               onmouseleave={() => ($boundingBoxesArray = [])} | ||||
| @ -303,7 +303,7 @@ | ||||
|                 </p> | ||||
|               {/if} | ||||
| 
 | ||||
|               <div class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full"> | ||||
|               <div class="absolute -end-[5px] -top-[5px] h-[20px] w-[20px] rounded-full"> | ||||
|                 {#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]} | ||||
|                   <CircleIconButton | ||||
|                     color="primary" | ||||
| @ -311,7 +311,7 @@ | ||||
|                     title={$t('reset')} | ||||
|                     size="18" | ||||
|                     padding="1" | ||||
|                     class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     onclick={() => handleReset(face.id)} | ||||
|                   /> | ||||
|                 {:else} | ||||
| @ -321,29 +321,29 @@ | ||||
|                     title={$t('select_new_face')} | ||||
|                     size="18" | ||||
|                     padding="1" | ||||
|                     class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     onclick={() => handleFacePicker(face)} | ||||
|                   /> | ||||
|                 {/if} | ||||
|               </div> | ||||
|               <div class="absolute right-[25px] -top-[5px] h-[20px] w-[20px] rounded-full"> | ||||
|               <div class="absolute end-[25px] -top-[5px] h-[20px] w-[20px] rounded-full"> | ||||
|                 {#if !selectedPersonToCreate[face.id] && !selectedPersonToReassign[face.id] && !face.person} | ||||
|                   <div | ||||
|                     class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                   > | ||||
|                     <Icon color="primary" path={mdiAccountOff} ariaHidden size="18" /> | ||||
|                   </div> | ||||
|                 {/if} | ||||
|               </div> | ||||
|               {#if face.person != null} | ||||
|                 <div class="absolute -right-[5px] top-[25px] h-[20px] w-[20px] rounded-full"> | ||||
|                 <div class="absolute -end-[5px] top-[25px] h-[20px] w-[20px] rounded-full"> | ||||
|                   <CircleIconButton | ||||
|                     color="red" | ||||
|                     icon={mdiTrashCan} | ||||
|                     title={$t('delete_face')} | ||||
|                     size="18" | ||||
|                     padding="1" | ||||
|                     class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform" | ||||
|                     onclick={() => deleteAssetFace(face)} | ||||
|                   /> | ||||
|                 </div> | ||||
|  | ||||
| @ -120,7 +120,7 @@ | ||||
| 
 | ||||
| <section | ||||
|   transition:fly={{ y: 500, duration: 100, easing: quintOut }} | ||||
|   class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg" | ||||
|   class="absolute start-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg" | ||||
| > | ||||
|   <ControlAppBar {onClose}> | ||||
|     {#snippet leading()} | ||||
| @ -140,7 +140,7 @@ | ||||
|           {:else} | ||||
|             <LoadingSpinner /> | ||||
|           {/if} | ||||
|           <span class="ml-2"> {$t('create_new_person')}</span></Button | ||||
|           <span class="ms-2"> {$t('create_new_person')}</span></Button | ||||
|         > | ||||
|         <Button | ||||
|           size="sm" | ||||
| @ -155,7 +155,7 @@ | ||||
|           {:else} | ||||
|             <LoadingSpinner /> | ||||
|           {/if} | ||||
|           <span class="ml-2"> {$t('reassign')}</span></Button | ||||
|           <span class="ms-2"> {$t('reassign')}</span></Button | ||||
|         > | ||||
|       </div> | ||||
|     {/snippet} | ||||
|  | ||||
| @ -173,7 +173,7 @@ | ||||
| {/if} | ||||
| 
 | ||||
| <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4"> | ||||
|   <table class="text-left"> | ||||
|   <table class="text-start"> | ||||
|     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> | ||||
|       {#each validatedPaths as validatedPath, listIndex (validatedPath.importPath)} | ||||
|         <tr | ||||
| @ -183,7 +183,7 @@ | ||||
|               : 'bg-immich-bg dark:bg-immich-dark-gray/50' | ||||
|           }`} | ||||
|         > | ||||
|           <td class="w-1/8 text-ellipsis pl-8 text-sm"> | ||||
|           <td class="w-1/8 text-ellipsis ps-8 text-sm"> | ||||
|             {#if validatedPath.isValid} | ||||
|               <Icon | ||||
|                 path={mdiCheckCircleOutline} | ||||
|  | ||||
| @ -123,7 +123,7 @@ | ||||
| {/if} | ||||
| 
 | ||||
| <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4"> | ||||
|   <table class="w-full text-left"> | ||||
|   <table class="w-full text-start"> | ||||
|     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> | ||||
|       {#each exclusionPatterns as exclusionPattern, listIndex (exclusionPattern)} | ||||
|         <tr | ||||
|  | ||||
| @ -72,7 +72,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} | ||||
| @ -81,7 +81,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)} | ||||
|           > | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
|       alt="Immich logo" | ||||
|     /> | ||||
|     <div | ||||
|       class="w-full h-[99%] absolute left-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20" | ||||
|       class="w-full h-[99%] absolute start-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20" | ||||
|     ></div> | ||||
|   </div> | ||||
| 
 | ||||
|  | ||||
| @ -360,8 +360,8 @@ | ||||
| 
 | ||||
|         {#each current.memory.assets as asset, index (asset.id)} | ||||
|           <a class="relative w-full py-2" href={asHref(asset)} aria-label={$t('view')}> | ||||
|             <span class="absolute left-0 h-[2px] w-full bg-gray-500"></span> | ||||
|             <span class="absolute left-0 h-[2px] bg-white" style:width={`${toProgressPercentage(index)}%`}></span> | ||||
|             <span class="absolute start-0 h-[2px] w-full bg-gray-500"></span> | ||||
|             <span class="absolute start-0 h-[2px] bg-white" style:width={`${toProgressPercentage(index)}%`}></span> | ||||
|           </a> | ||||
|         {/each} | ||||
| 
 | ||||
| @ -380,7 +380,7 @@ | ||||
| 
 | ||||
|     {#if galleryInView} | ||||
|       <div | ||||
|         class="fixed top-20 z-30 left-1/2 -translate-x-1/2 transition-opacity" | ||||
|         class="fixed top-20 z-30 start-1/2 -translate-x-1/2 transition-opacity" | ||||
|         class:opacity-0={!galleryInView} | ||||
|         class:opacity-100={galleryInView} | ||||
|       > | ||||
| @ -396,7 +396,7 @@ | ||||
|     <!-- Viewer --> | ||||
|     <section class="overflow-hidden pt-32 md:pt-20" bind:clientHeight={viewerHeight}> | ||||
|       <div | ||||
|         class="ml-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden" | ||||
|         class="ms-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden" | ||||
|       > | ||||
|         <!-- PREVIOUS MEMORY --> | ||||
|         <div class="h-1/2 w-[20vw] rounded-2xl {current.previousMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}"> | ||||
| @ -424,7 +424,7 @@ | ||||
|             {/if} | ||||
| 
 | ||||
|             {#if current.previousMemory} | ||||
|               <div class="absolute bottom-4 right-4 text-left text-white"> | ||||
|               <div class="absolute bottom-4 end-4 text-start text-white"> | ||||
|                 <p class="text-xs font-semibold text-gray-200">{$t('previous').toUpperCase()}</p> | ||||
|                 <p class="text-xl">{$memoryLaneTitle(current.previousMemory)}</p> | ||||
|               </div> | ||||
| @ -465,7 +465,7 @@ | ||||
|             {/key} | ||||
| 
 | ||||
|             <div | ||||
|               class="absolute bottom-0 right-0 p-2 transition-all flex h-full justify-between flex-col items-end gap-2" | ||||
|               class="absolute bottom-0 end-0 p-2 transition-all flex h-full justify-between flex-col items-end gap-2" | ||||
|               class:opacity-0={galleryInView} | ||||
|               class:opacity-100={!galleryInView} | ||||
|             > | ||||
| @ -521,7 +521,7 @@ | ||||
|             </div> | ||||
|             <!-- CONTROL BUTTONS --> | ||||
|             {#if current.previous} | ||||
|               <div class="absolute top-1/2 left-0 ml-4"> | ||||
|               <div class="absolute top-1/2 start-0 ms-4"> | ||||
|                 <CircleIconButton | ||||
|                   title={$t('previous_memory')} | ||||
|                   icon={mdiChevronLeft} | ||||
| @ -532,7 +532,7 @@ | ||||
|             {/if} | ||||
| 
 | ||||
|             {#if current.next} | ||||
|               <div class="absolute top-1/2 right-0 mr-4"> | ||||
|               <div class="absolute top-1/2 end-0 me-4"> | ||||
|                 <CircleIconButton | ||||
|                   title={$t('next_memory')} | ||||
|                   icon={mdiChevronRight} | ||||
| @ -542,7 +542,7 @@ | ||||
|               </div> | ||||
|             {/if} | ||||
| 
 | ||||
|             <div class="absolute left-8 top-4 text-sm font-medium text-white"> | ||||
|             <div class="absolute start-8 top-4 text-sm font-medium text-white"> | ||||
|               <p> | ||||
|                 {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL, { | ||||
|                   locale: $locale, | ||||
| @ -582,7 +582,7 @@ | ||||
|             {/if} | ||||
| 
 | ||||
|             {#if current.nextMemory} | ||||
|               <div class="absolute bottom-4 left-4 text-left text-white"> | ||||
|               <div class="absolute bottom-4 start-4 text-start text-white"> | ||||
|                 <p class="text-xs font-semibold text-gray-200">{$t('up_next').toUpperCase()}</p> | ||||
|                 <p class="text-xl">{$memoryLaneTitle(current.nextMemory)}</p> | ||||
|               </div> | ||||
|  | ||||
| @ -149,7 +149,7 @@ | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       <span class="w-full truncate first-letter:capitalize ml-2.5" title={getDateLocaleString(dateGroup.date)}> | ||||
|       <span class="w-full truncate first-letter:capitalize ms-2.5" title={getDateLocaleString(dateGroup.date)}> | ||||
|         {dateGroup.groupTitle} | ||||
|       </span> | ||||
|     </div> | ||||
|  | ||||
| @ -743,7 +743,7 @@ | ||||
| <!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar --> | ||||
| <section | ||||
|   id="asset-grid" | ||||
|   class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ml-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'} | ||||
|   tabindex="-1" | ||||
|   bind:clientHeight={assetStore.viewportHeight} | ||||
|  | ||||
| @ -44,9 +44,9 @@ | ||||
|     onscroll={onScroll} | ||||
|   > | ||||
|     {#if canScrollLeft || canScrollRight} | ||||
|       <div class="sticky left-0 z-20"> | ||||
|       <div class="sticky start-0 z-20"> | ||||
|         {#if canScrollLeft} | ||||
|           <div class="absolute left-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}> | ||||
|           <div class="absolute start-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}> | ||||
|             <button | ||||
|               type="button" | ||||
|               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100" | ||||
| @ -59,7 +59,7 @@ | ||||
|           </div> | ||||
|         {/if} | ||||
|         {#if canScrollRight} | ||||
|           <div class="absolute right-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}> | ||||
|           <div class="absolute end-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}> | ||||
|             <button | ||||
|               type="button" | ||||
|               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100" | ||||
| @ -76,7 +76,7 @@ | ||||
|     <div class="inline-block" use:resizeObserver={({ width }) => (innerWidth = width)}> | ||||
|       {#each memoryStore.memories as memory (memory.id)} | ||||
|         <a | ||||
|           class="memory-card relative mr-2 md:mr-4 last:mr-0 inline-block aspect-[3/4] md:aspect-[4/3] max-md:h-[150px] xl:aspect-video h-[215px] rounded-xl" | ||||
|           class="memory-card relative me-2 md:me-4 last:me-0 inline-block aspect-[3/4] md:aspect-[4/3] max-md:h-[150px] xl:aspect-video h-[215px] rounded-xl" | ||||
|           href="{AppRoute.MEMORY}?{QueryParameter.ID}={memory.assets[0].id}" | ||||
|         > | ||||
|           <img | ||||
| @ -85,11 +85,11 @@ | ||||
|             alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })} | ||||
|             draggable="false" | ||||
|           /> | ||||
|           <p class="absolute bottom-2 left-4 z-10 text-lg text-white max-md:text-sm"> | ||||
|           <p class="absolute bottom-2 start-4 z-10 text-lg text-white max-md:text-sm"> | ||||
|             {$memoryLaneTitle(memory)} | ||||
|           </p> | ||||
|           <div | ||||
|             class="absolute left-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20" | ||||
|             class="absolute start-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20" | ||||
|           ></div> | ||||
|         </a> | ||||
|       {/each} | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     {title} | ||||
|   </div> | ||||
|   <div | ||||
|     class="animate-pulse absolute h-full ml-[10px] mr-[10px]" | ||||
|     class="animate-pulse absolute h-full ms-[10px] me-[10px]" | ||||
|     style:width="calc(100% - 20px)" | ||||
|     data-skeleton="true" | ||||
|   ></div> | ||||
|  | ||||
| @ -25,7 +25,7 @@ | ||||
|     <button | ||||
|       type="button" | ||||
|       onclick={() => togglePlacesGroupCollapsing(group.id)} | ||||
|       class="w-fit mt-2 pt-2 pr-2 mb-2 dark:text-immich-dark-fg" | ||||
|       class="w-fit mt-2 pt-2 pe-2 mb-2 dark:text-immich-dark-fg" | ||||
|       aria-expanded={!isCollapsed} | ||||
|     > | ||||
|       <Icon | ||||
| @ -34,7 +34,7 @@ | ||||
|         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}" | ||||
|       /> | ||||
|       <span class="font-bold text-3xl text-black dark:text-white">{group.name}</span> | ||||
|       <span class="ml-1.5">({$t('places_count', { values: { count: places.length } })})</span> | ||||
|       <span class="ms-1.5">({$t('places_count', { values: { count: places.length } })})</span> | ||||
|     </button> | ||||
|     <hr class="dark:border-immich-dark-gray" /> | ||||
|   </div> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -96,8 +96,8 @@ | ||||
| 
 | ||||
| <section class="my-4"> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ml-4"> | ||||
|     <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('theme_selection')} | ||||
|           subtitle={$t('theme_selection_description')} | ||||
| @ -106,7 +106,7 @@ | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingCombobox | ||||
|           comboboxPlaceholder={$t('language')} | ||||
|           selectedOption={langOptions.find(({ value }) => value === closestLanguage) || defaultLangOption} | ||||
| @ -117,7 +117,7 @@ | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('default_locale')} | ||||
|           subtitle={$t('default_locale_description')} | ||||
| @ -128,7 +128,7 @@ | ||||
|         </SettingSwitch> | ||||
|       </div> | ||||
|       {#if $locale !== undefined} | ||||
|         <div class="ml-4"> | ||||
|         <div class="ms-4"> | ||||
|           <SettingCombobox | ||||
|             comboboxPlaceholder={$t('searching_locales')} | ||||
|             {selectedOption} | ||||
| @ -140,7 +140,7 @@ | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('display_original_photos')} | ||||
|           subtitle={$t('display_original_photos_setting_description')} | ||||
| @ -148,7 +148,7 @@ | ||||
|           onToggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)} | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('video_hover_setting')} | ||||
|           subtitle={$t('video_hover_setting_description')} | ||||
| @ -156,7 +156,7 @@ | ||||
|           onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)} | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('loop_videos')} | ||||
|           subtitle={$t('loop_videos_description')} | ||||
| @ -165,7 +165,7 @@ | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="ml-4"> | ||||
|       <div class="ms-4"> | ||||
|         <SettingSwitch | ||||
|           title={$t('permanent_deletion_warning')} | ||||
|           subtitle={$t('permanent_deletion_warning_setting_description')} | ||||
|  | ||||
| @ -44,7 +44,7 @@ | ||||
| <section class="my-4"> | ||||
|   <div in:fade={{ duration: 500 }}> | ||||
|     <form autocomplete="off" {onsubmit}> | ||||
|       <div class="ml-4 mt-4 flex flex-col gap-4"> | ||||
|       <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||
|         <SettingInputField | ||||
|           inputType={SettingInputFieldType.PASSWORD} | ||||
|           label={$t('password')} | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user