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": { |       "dependencies": { | ||||||
|         "@formatjs/icu-messageformat-parser": "^2.9.8", |         "@formatjs/icu-messageformat-parser": "^2.9.8", | ||||||
|         "@immich/sdk": "file:../open-api/typescript-sdk", |         "@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", |         "@mapbox/mapbox-gl-rtl-text": "0.2.3", | ||||||
|         "@mdi/js": "^7.4.47", |         "@mdi/js": "^7.4.47", | ||||||
|         "@photo-sphere-viewer/core": "^5.11.5", |         "@photo-sphere-viewer/core": "^5.11.5", | ||||||
| @ -1320,9 +1320,9 @@ | |||||||
|       "link": true |       "link": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/@immich/ui": { |     "node_modules/@immich/ui": { | ||||||
|       "version": "0.17.4", |       "version": "0.18.1", | ||||||
|       "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.17.4.tgz", |       "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.18.1.tgz", | ||||||
|       "integrity": "sha512-a6M7Fxno5fwY5A0kxdluS8r+A4L6xZhSTKMW8c8hoFhQHvbBTHAsGFKQF3GOEQLOlUuvsS2Lt7dMevBlAPgo/A==", |       "integrity": "sha512-XWWO6OTfH3MektyxCn0hWefZyOGyWwwx/2zHinuShpxTHSyfveJ4mOkFP8DkyMz0dnvJ1EfdkPBMkld3y5R/Hw==", | ||||||
|       "license": "GNU Affero General Public License version 3", |       "license": "GNU Affero General Public License version 3", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@mdi/js": "^7.4.47", |         "@mdi/js": "^7.4.47", | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@formatjs/icu-messageformat-parser": "^2.9.8", |     "@formatjs/icu-messageformat-parser": "^2.9.8", | ||||||
|     "@immich/sdk": "file:../open-api/typescript-sdk", |     "@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", |     "@mapbox/mapbox-gl-rtl-text": "0.2.3", | ||||||
|     "@mdi/js": "^7.4.47", |     "@mdi/js": "^7.4.47", | ||||||
|     "@photo-sphere-viewer/core": "^5.11.5", |     "@photo-sphere-viewer/core": "^5.11.5", | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ | |||||||
|   let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused); |   let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused); | ||||||
|   let multipleButtons = $derived(allText || refreshText); |   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> | </script> | ||||||
| 
 | 
 | ||||||
| <div | <div | ||||||
| @ -110,7 +110,7 @@ | |||||||
| 
 | 
 | ||||||
|       <div class="mt-2 flex w-full max-w-md flex-col sm:flex-row"> |       <div class="mt-2 flex w-full max-w-md flex-col sm:flex-row"> | ||||||
|         <div |         <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>{$t('active')}</p> | ||||||
|           <p class="text-2xl"> |           <p class="text-2xl"> | ||||||
| @ -119,7 +119,7 @@ | |||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <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"> |           <p class="text-2xl"> | ||||||
|             {waitingCount.toLocaleString($locale)} |             {waitingCount.toLocaleString($locale)} | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ | |||||||
|             <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span |             <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span | ||||||
|               class="text-immich-primary dark:text-immich-dark-primary">{statsUsage}</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> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @ -88,7 +88,7 @@ | |||||||
| 
 | 
 | ||||||
|   <div> |   <div> | ||||||
|     <p class="text-sm dark:text-immich-dark-fg">{$t('user_usage_detail').toUpperCase()}</p> |     <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 |       <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" |         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 |       class="text-immich-primary dark:text-immich-dark-primary">{value}</span | ||||||
|     > |     > | ||||||
|     {#if unit} |     {#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} |     {/if} | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -76,13 +76,13 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}> |     <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 |         <SettingAccordion | ||||||
|           key="oauth" |           key="oauth" | ||||||
|           title={$t('admin.oauth_settings')} |           title={$t('admin.oauth_settings')} | ||||||
|           subtitle={$t('admin.oauth_settings_description')} |           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"> |             <p class="text-sm dark:text-immich-dark-fg"> | ||||||
|               <FormatMessage key="admin.oauth_settings_more_details"> |               <FormatMessage key="admin.oauth_settings_more_details"> | ||||||
|                 {#snippet children({ message })} |                 {#snippet children({ message })} | ||||||
| @ -243,8 +243,8 @@ | |||||||
|           title={$t('admin.password_settings')} |           title={$t('admin.password_settings')} | ||||||
|           subtitle={$t('admin.password_settings_description')} |           subtitle={$t('admin.password_settings_description')} | ||||||
|         > |         > | ||||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> |           <div class="ms-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"> | ||||||
|               <SettingSwitch |               <SettingSwitch | ||||||
|                 title={$t('admin.password_enable_description')} |                 title={$t('admin.password_enable_description')} | ||||||
|                 {disabled} |                 {disabled} | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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 |         <SettingSwitch | ||||||
|           title={$t('admin.backup_database_enable_description')} |           title={$t('admin.backup_database_enable_description')} | ||||||
|           {disabled} |           {disabled} | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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"> |         <p class="text-sm dark:text-immich-dark-fg"> | ||||||
|           <Icon path={mdiHelpCircleOutline} class="inline" size="15" /> |           <Icon path={mdiHelpCircleOutline} class="inline" size="15" /> | ||||||
|           <FormatMessage key="admin.transcoding_codecs_learn_more"> |           <FormatMessage key="admin.transcoding_codecs_learn_more"> | ||||||
| @ -70,7 +70,7 @@ | |||||||
|           title={$t('admin.transcoding_policy')} |           title={$t('admin.transcoding_policy')} | ||||||
|           subtitle={$t('admin.transcoding_policy_description')} |           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 |             <SettingSelect | ||||||
|               label={$t('admin.transcoding_transcode_policy')} |               label={$t('admin.transcoding_transcode_policy')} | ||||||
|               {disabled} |               {disabled} | ||||||
| @ -159,7 +159,7 @@ | |||||||
|           title={$t('admin.transcoding_encoding_options')} |           title={$t('admin.transcoding_encoding_options')} | ||||||
|           subtitle={$t('admin.transcoding_encoding_options_description')} |           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 |             <SettingSelect | ||||||
|               label={$t('admin.transcoding_video_codec')} |               label={$t('admin.transcoding_video_codec')} | ||||||
|               {disabled} |               {disabled} | ||||||
| @ -302,7 +302,7 @@ | |||||||
|           title={$t('admin.transcoding_hardware_acceleration')} |           title={$t('admin.transcoding_hardware_acceleration')} | ||||||
|           subtitle={$t('admin.transcoding_hardware_acceleration_description')} |           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 |             <SettingSelect | ||||||
|               label={$t('admin.transcoding_acceleration_api')} |               label={$t('admin.transcoding_acceleration_api')} | ||||||
|               {disabled} |               {disabled} | ||||||
| @ -376,7 +376,7 @@ | |||||||
|           title={$t('advanced')} |           title={$t('advanced')} | ||||||
|           subtitle={$t('admin.transcoding_advanced_options_description')} |           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 |             <SettingInputField | ||||||
|               inputType={SettingInputFieldType.NUMBER} |               inputType={SettingInputFieldType.NUMBER} | ||||||
|               label={$t('admin.transcoding_max_b_frames')} |               label={$t('admin.transcoding_max_b_frames')} | ||||||
| @ -407,7 +407,7 @@ | |||||||
|         </SettingAccordion> |         </SettingAccordion> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingButtonsRow |         <SettingButtonsRow | ||||||
|           onReset={(options) => onReset({ ...options, configKeys: ['ffmpeg'] })} |           onReset={(options) => onReset({ ...options, configKeys: ['ffmpeg'] })} | ||||||
|           onSave={() => onSave({ ffmpeg: config.ffmpeg })} |           onSave={() => onSave({ ffmpeg: config.ffmpeg })} | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <form autocomplete="off" {onsubmit}> | ||||||
|       <div class="ml-4 mt-4"> |       <div class="ms-4 mt-4"> | ||||||
|         <SettingAccordion |         <SettingAccordion | ||||||
|           key="thumbnail-settings" |           key="thumbnail-settings" | ||||||
|           title={$t('admin.image_thumbnail_title')} |           title={$t('admin.image_thumbnail_title')} | ||||||
| @ -195,7 +195,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4 mt-4"> |       <div class="ms-4 mt-4"> | ||||||
|         <SettingButtonsRow |         <SettingButtonsRow | ||||||
|           onReset={(options) => onReset({ ...options, configKeys: ['image'] })} |           onReset={(options) => onReset({ ...options, configKeys: ['image'] })} | ||||||
|           onSave={() => onSave({ image: config.image })} |           onSave={() => onSave({ image: config.image })} | ||||||
|  | |||||||
| @ -47,7 +47,7 @@ | |||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <form autocomplete="off" {onsubmit}> | ||||||
|       {#each jobNames as jobName (jobName)} |       {#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)} |           {#if isSystemConfigJobDto(jobName)} | ||||||
|             <SettingInputField |             <SettingInputField | ||||||
|               inputType={SettingInputFieldType.NUMBER} |               inputType={SettingInputFieldType.NUMBER} | ||||||
| @ -71,7 +71,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       {/each} |       {/each} | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingButtonsRow |         <SettingButtonsRow | ||||||
|           onReset={(options) => onReset({ ...options, configKeys: ['job'] })} |           onReset={(options) => onReset({ ...options, configKeys: ['job'] })} | ||||||
|           onSave={() => onSave({ job: config.job })} |           onSave={() => onSave({ job: config.job })} | ||||||
|  | |||||||
| @ -47,14 +47,14 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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 |         <SettingAccordion | ||||||
|           key="library-watching" |           key="library-watching" | ||||||
|           title={$t('admin.library_watching_settings')} |           title={$t('admin.library_watching_settings')} | ||||||
|           subtitle={$t('admin.library_watching_settings_description')} |           subtitle={$t('admin.library_watching_settings_description')} | ||||||
|           isOpen={openByDefault} |           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 |             <SettingSwitch | ||||||
|               title={$t('admin.library_watching_enable_description')} |               title={$t('admin.library_watching_enable_description')} | ||||||
|               {disabled} |               {disabled} | ||||||
| @ -69,7 +69,7 @@ | |||||||
|           subtitle={$t('admin.library_scanning_description')} |           subtitle={$t('admin.library_scanning_description')} | ||||||
|           isOpen={openByDefault} |           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 |             <SettingSwitch | ||||||
|               title={$t('admin.library_scanning_enable_description')} |               title={$t('admin.library_scanning_enable_description')} | ||||||
|               {disabled} |               {disabled} | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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 |         <SettingSwitch | ||||||
|           title={$t('admin.logging_enable_description')} |           title={$t('admin.logging_enable_description')} | ||||||
|           {disabled} |           {disabled} | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ | |||||||
|               {#if config.machineLearning.urls.length > 1} |               {#if config.machineLearning.urls.length > 1} | ||||||
|                 <CircleIconButton |                 <CircleIconButton | ||||||
|                   size="24" |                   size="24" | ||||||
|                   class="ml-2" |                   class="ms-2" | ||||||
|                   padding="2" |                   padding="2" | ||||||
|                   color="red" |                   color="red" | ||||||
|                   title="" |                   title="" | ||||||
| @ -88,7 +88,7 @@ | |||||||
|         title={$t('admin.machine_learning_smart_search')} |         title={$t('admin.machine_learning_smart_search')} | ||||||
|         subtitle={$t('admin.machine_learning_smart_search_description')} |         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 |           <SettingSwitch | ||||||
|             title={$t('admin.machine_learning_smart_search_enabled')} |             title={$t('admin.machine_learning_smart_search_enabled')} | ||||||
|             subtitle={$t('admin.machine_learning_smart_search_enabled_description')} |             subtitle={$t('admin.machine_learning_smart_search_enabled_description')} | ||||||
| @ -124,7 +124,7 @@ | |||||||
|         title={$t('admin.machine_learning_duplicate_detection')} |         title={$t('admin.machine_learning_duplicate_detection')} | ||||||
|         subtitle={$t('admin.machine_learning_duplicate_detection_setting_description')} |         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 |           <SettingSwitch | ||||||
|             title={$t('admin.machine_learning_duplicate_detection_enabled')} |             title={$t('admin.machine_learning_duplicate_detection_enabled')} | ||||||
|             subtitle={$t('admin.machine_learning_duplicate_detection_enabled_description')} |             subtitle={$t('admin.machine_learning_duplicate_detection_enabled_description')} | ||||||
| @ -154,7 +154,7 @@ | |||||||
|         title={$t('admin.machine_learning_facial_recognition')} |         title={$t('admin.machine_learning_facial_recognition')} | ||||||
|         subtitle={$t('admin.machine_learning_facial_recognition_description')} |         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 |           <SettingSwitch | ||||||
|             title={$t('admin.machine_learning_facial_recognition_setting')} |             title={$t('admin.machine_learning_facial_recognition_setting')} | ||||||
|             subtitle={$t('admin.machine_learning_facial_recognition_setting_description')} |             subtitle={$t('admin.machine_learning_facial_recognition_setting_description')} | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ | |||||||
|     <form autocomplete="off" {onsubmit}> |     <form autocomplete="off" {onsubmit}> | ||||||
|       <div class="flex flex-col gap-4"> |       <div class="flex flex-col gap-4"> | ||||||
|         <SettingAccordion key="map" title={$t('admin.map_settings')} subtitle={$t('admin.map_settings_description')}> |         <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 |             <SettingSwitch | ||||||
|               title={$t('admin.map_enable_description')} |               title={$t('admin.map_enable_description')} | ||||||
|               subtitle={$t('admin.map_implications')} |               subtitle={$t('admin.map_implications')} | ||||||
| @ -78,7 +78,7 @@ | |||||||
|               </FormatMessage> |               </FormatMessage> | ||||||
|             </p> |             </p> | ||||||
|           {/snippet} |           {/snippet} | ||||||
|           <div class="ml-4 mt-4 flex flex-col gap-4"> |           <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||||
|             <SettingSwitch |             <SettingSwitch | ||||||
|               title={$t('admin.map_reverse_geocoding_enable_description')} |               title={$t('admin.map_reverse_geocoding_enable_description')} | ||||||
|               {disabled} |               {disabled} | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
| <div class="mt-2"> | <div class="mt-2"> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit} class="mx-4 mt-4"> |     <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 |         <SettingSwitch | ||||||
|           title={$t('admin.metadata_faces_import_setting')} |           title={$t('admin.metadata_faces_import_setting')} | ||||||
|           subtitle={$t('admin.metadata_faces_import_setting_description')} |           subtitle={$t('admin.metadata_faces_import_setting_description')} | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <form autocomplete="off" {onsubmit}> | ||||||
|       <div class="ml-4 mt-4"> |       <div class="ms-4 mt-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('admin.version_check_enabled_description')} |           title={$t('admin.version_check_enabled_description')} | ||||||
|           subtitle={$t('admin.version_check_implications')} |           subtitle={$t('admin.version_check_implications')} | ||||||
|  | |||||||
| @ -80,7 +80,7 @@ | |||||||
|     <form autocomplete="off" {onsubmit} class="mt-4"> |     <form autocomplete="off" {onsubmit} class="mt-4"> | ||||||
|       <div class="flex flex-col gap-4"> |       <div class="flex flex-col gap-4"> | ||||||
|         <SettingAccordion key="email" title={$t('email')} subtitle={$t('admin.notification_email_setting_description')}> |         <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 |             <SettingSwitch | ||||||
|               title={$t('admin.notification_enable_email_notifications')} |               title={$t('admin.notification_enable_email_notifications')} | ||||||
|               {disabled} |               {disabled} | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <form autocomplete="off" {onsubmit}> | ||||||
|       <div class="mt-4 ml-4"> |       <div class="mt-4 ms-4"> | ||||||
|         <SettingInputField |         <SettingInputField | ||||||
|           inputType={SettingInputFieldType.TEXT} |           inputType={SettingInputFieldType.TEXT} | ||||||
|           label={$t('admin.server_external_domain_settings')} |           label={$t('admin.server_external_domain_settings')} | ||||||
| @ -52,7 +52,7 @@ | |||||||
|           bind:checked={config.server.publicUsers} |           bind:checked={config.server.publicUsers} | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <div class="ml-4"> |         <div class="ms-4"> | ||||||
|           <SettingButtonsRow |           <SettingButtonsRow | ||||||
|             onReset={(options) => onReset({ ...options, configKeys: ['server'] })} |             onReset={(options) => onReset({ ...options, configKeys: ['server'] })} | ||||||
|             onSave={() => onSave({ server: config.server })} |             onSave={() => onSave({ server: config.server })} | ||||||
|  | |||||||
| @ -141,7 +141,7 @@ | |||||||
|     </p> |     </p> | ||||||
|   </div> |   </div> | ||||||
|   {#await getTemplateOptions() then} |   {#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 |       <SettingSwitch | ||||||
|         title={$t('admin.storage_template_enable_description')} |         title={$t('admin.storage_template_enable_description')} | ||||||
|         {disabled} |         {disabled} | ||||||
|  | |||||||
| @ -76,7 +76,7 @@ | |||||||
|           title={$t('admin.template_email_settings')} |           title={$t('admin.template_email_settings')} | ||||||
|           subtitle={$t('admin.template_settings_description')} |           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"> |             <p class="text-sm dark:text-immich-dark-fg"> | ||||||
|               <FormatMessage key="admin.template_email_if_empty"> |               <FormatMessage key="admin.template_email_if_empty"> | ||||||
|                 {$t('admin.template_email_if_empty')} |                 {$t('admin.template_email_if_empty')} | ||||||
| @ -102,7 +102,7 @@ | |||||||
|                   onclick={() => getTemplate(templateName, config.templates.email[templateKey])} |                   onclick={() => getTemplate(templateName, config.templates.email[templateKey])} | ||||||
|                   title={$t('admin.template_email_preview')} |                   title={$t('admin.template_email_preview')} | ||||||
|                 > |                 > | ||||||
|                   <Icon path={mdiEyeOutline} class="mr-1" /> |                   <Icon path={mdiEyeOutline} class="me-1" /> | ||||||
|                   {$t('admin.template_email_preview')} |                   {$t('admin.template_email_preview')} | ||||||
|                 </Button> |                 </Button> | ||||||
|               </div> |               </div> | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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 |         <SettingTextarea | ||||||
|           {disabled} |           {disabled} | ||||||
|           label={$t('admin.theme_custom_css_settings')} |           label={$t('admin.theme_custom_css_settings')} | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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} /> |         <SettingSwitch title={$t('admin.trash_enabled_description')} {disabled} bind:checked={config.trash.enabled} /> | ||||||
| 
 | 
 | ||||||
|         <hr /> |         <hr /> | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ | |||||||
| <div> | <div> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}> |     <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 |         <SettingInputField | ||||||
|           inputType={SettingInputFieldType.NUMBER} |           inputType={SettingInputFieldType.NUMBER} | ||||||
|           min={1} |           min={1} | ||||||
| @ -35,7 +35,7 @@ | |||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingButtonsRow |         <SettingButtonsRow | ||||||
|           onReset={(options) => onReset({ ...options, configKeys: ['user'] })} |           onReset={(options) => onReset({ ...options, configKeys: ['user'] })} | ||||||
|           onSave={() => onSave({ user: config.user })} |           onSave={() => onSave({ user: config.user })} | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ | |||||||
|     <button |     <button | ||||||
|       type="button" |       type="button" | ||||||
|       onclick={() => toggleAlbumGroupCollapsing(group.id)} |       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} |       aria-expanded={!isCollapsed} | ||||||
|     > |     > | ||||||
|       <Icon |       <Icon | ||||||
| @ -57,7 +57,7 @@ | |||||||
|         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}" |         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="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> |     </button> | ||||||
|     <hr class="dark:border-immich-dark-gray" /> |     <hr class="dark:border-immich-dark-gray" /> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ | |||||||
|   {#if onShowContextMenu} |   {#if onShowContextMenu} | ||||||
|     <div |     <div | ||||||
|       id="icon-{album.id}" |       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" |       data-testid="context-button-parent" | ||||||
|     > |     > | ||||||
|       <CircleIconButton |       <CircleIconButton | ||||||
|  | |||||||
| @ -117,7 +117,7 @@ | |||||||
|       <!-- ALBUM DESCRIPTION --> |       <!-- ALBUM DESCRIPTION --> | ||||||
|       {#if album.description} |       {#if album.description} | ||||||
|         <p |         <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} |           {album.description} | ||||||
|         </p> |         </p> | ||||||
|  | |||||||
| @ -35,13 +35,13 @@ | |||||||
|   onclick={() => goto(`${AppRoute.ALBUMS}/${album.id}`)} |   onclick={() => goto(`${AppRoute.ALBUMS}/${album.id}`)} | ||||||
|   {oncontextmenu} |   {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} |     {album.albumName} | ||||||
|     {#if album.shared} |     {#if album.shared} | ||||||
|       <Icon |       <Icon | ||||||
|         path={mdiShareVariantOutline} |         path={mdiShareVariantOutline} | ||||||
|         size="16" |         size="16" | ||||||
|         class="inline ml-1 opacity-70" |         class="inline ms-1 opacity-70" | ||||||
|         title={album.ownerId === $user.id |         title={album.ownerId === $user.id | ||||||
|           ? $t('shared_by_you') |           ? $t('shared_by_you') | ||||||
|           : $t('shared_by_user', { values: { user: album.owner.name } })} |           : $t('shared_by_user', { values: { user: album.owner.name } })} | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ | |||||||
|   let { groupedAlbums, albumGroupOption = AlbumGroupBy.None, onShowContextMenu }: Props = $props(); |   let { groupedAlbums, albumGroupOption = AlbumGroupBy.None, onShowContextMenu }: Props = $props(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <table class="mt-2 w-full text-left"> | <table class="mt-2 w-full text-start"> | ||||||
|   <thead |   <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" |     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" |         class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg" | ||||||
|       > |       > | ||||||
|         <tr |         <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)} |           onclick={() => toggleAlbumGroupCollapsing(albumGroup.id)} | ||||||
|           aria-expanded={!isCollapsed} |           aria-expanded={!isCollapsed} | ||||||
|         > |         > | ||||||
|           <td class="text-md text-left -mb-1"> |           <td class="text-md text-start -mb-1"> | ||||||
|             <Icon |             <Icon | ||||||
|               path={mdiChevronRight} |               path={mdiChevronRight} | ||||||
|               size="20" |               size="20" | ||||||
|               class="inline-block -mt-2 transition-all duration-[250ms] {iconRotation}" |               class="inline-block -mt-2 transition-all duration-[250ms] {iconRotation}" | ||||||
|             /> |             /> | ||||||
|             <span class="font-bold text-2xl">{albumGroup.name}</span> |             <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 } })}) |               ({$t('albums_count', { values: { count: albumGroup.albums.length } })}) | ||||||
|             </span> |             </span> | ||||||
|           </td> |           </td> | ||||||
|  | |||||||
| @ -94,7 +94,7 @@ | |||||||
|                 </div> |                 </div> | ||||||
| 
 | 
 | ||||||
|                 <!-- <UserAvatar {user} size="md" /> --> |                 <!-- <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"> |                   <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||||
|                     {user.name} |                     {user.name} | ||||||
|                   </p> |                   </p> | ||||||
| @ -136,7 +136,7 @@ | |||||||
|                   class="flex w-full place-items-center gap-4 p-4" |                   class="flex w-full place-items-center gap-4 p-4" | ||||||
|                 > |                 > | ||||||
|                   <UserAvatar {user} size="md" /> |                   <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"> |                     <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||||
|                       {user.name} |                       {user.name} | ||||||
|                     </p> |                     </p> | ||||||
|  | |||||||
| @ -186,7 +186,7 @@ | |||||||
|       > |       > | ||||||
|         {#each reactions as reaction, index (reaction.id)} |         {#each reactions as reaction, index (reaction.id)} | ||||||
|           {#if reaction.type === ReactionType.Comment} |           {#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"> |               <div class="flex items-center"> | ||||||
|                 <UserAvatar user={reaction.user} size="sm" /> |                 <UserAvatar user={reaction.user} size="sm" /> | ||||||
|               </div> |               </div> | ||||||
| @ -202,7 +202,7 @@ | |||||||
|                 </a> |                 </a> | ||||||
|               {/if} |               {/if} | ||||||
|               {#if reaction.user.id === user.id || albumOwnerId === user.id} |               {#if reaction.user.id === user.id || albumOwnerId === user.id} | ||||||
|                 <div class="mr-4"> |                 <div class="me-4"> | ||||||
|                   <ButtonContextMenu |                   <ButtonContextMenu | ||||||
|                     icon={mdiDotsVertical} |                     icon={mdiDotsVertical} | ||||||
|                     title={$t('comment_options')} |                     title={$t('comment_options')} | ||||||
| @ -231,7 +231,7 @@ | |||||||
|             {/if} |             {/if} | ||||||
|           {:else if reaction.type === ReactionType.Like} |           {:else if reaction.type === ReactionType.Like} | ||||||
|             <div class="relative"> |             <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="text-red-600"><Icon path={mdiHeart} size={20} /></div> | ||||||
| 
 | 
 | ||||||
|                 <div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}> |                 <div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}> | ||||||
| @ -255,7 +255,7 @@ | |||||||
|                   </a> |                   </a> | ||||||
|                 {/if} |                 {/if} | ||||||
|                 {#if reaction.user.id === user.id || albumOwnerId === user.id} |                 {#if reaction.user.id === user.id || albumOwnerId === user.id} | ||||||
|                   <div class="mr-4"> |                   <div class="me-4"> | ||||||
|                     <ButtonContextMenu |                     <ButtonContextMenu | ||||||
|                       icon={mdiDotsVertical} |                       icon={mdiDotsVertical} | ||||||
|                       title={$t('reaction_options')} |                       title={$t('reaction_options')} | ||||||
| @ -307,17 +307,17 @@ | |||||||
|               }} |               }} | ||||||
|               class="h-[18px] {disabled |               class="h-[18px] {disabled | ||||||
|                 ? 'cursor-not-allowed' |                 ? '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> |             ></textarea> | ||||||
|           </div> |           </div> | ||||||
|           {#if isSendingMessage} |           {#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"> |               <div class="flex w-full place-items-center"> | ||||||
|                 <LoadingSpinner /> |                 <LoadingSpinner /> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           {:else if message} |           {:else if message} | ||||||
|             <div class="flex items-end w-fit ml-0"> |             <div class="flex items-end w-fit ms-0"> | ||||||
|               <CircleIconButton |               <CircleIconButton | ||||||
|                 title={$t('send_message')} |                 title={$t('send_message')} | ||||||
|                 size="15" |                 size="15" | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ | |||||||
|   type="button" |   type="button" | ||||||
|   onclick={onAlbumClick} |   onclick={onAlbumClick} | ||||||
|   use:scrollIntoViewIfSelected |   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:bg-gray-200={selected} | ||||||
|   class:dark:bg-gray-700={selected} |   class:dark:bg-gray-700={selected} | ||||||
| > | > | ||||||
|  | |||||||
| @ -422,7 +422,7 @@ | |||||||
| 
 | 
 | ||||||
| <section | <section | ||||||
|   id="immich-asset-viewer" |   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 |   use:focusTrap | ||||||
| > | > | ||||||
|   <!-- Top navigation bar --> |   <!-- Top navigation bar --> | ||||||
| @ -547,7 +547,7 @@ | |||||||
|           /> |           /> | ||||||
|         {/if} |         {/if} | ||||||
|         {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || numberOfComments > 0)} |         {#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 |             <ActivityStatus | ||||||
|               disabled={!album?.isActivityEnabled} |               disabled={!album?.isActivityEnabled} | ||||||
|               {isLiked} |               {isLiked} | ||||||
| @ -571,7 +571,7 @@ | |||||||
|     <div |     <div | ||||||
|       transition:fly={{ duration: 150 }} |       transition:fly={{ duration: 150 }} | ||||||
|       id="detail-panel" |       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" |       translate="yes" | ||||||
|     > |     > | ||||||
|       <DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} /> |       <DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} /> | ||||||
| @ -582,7 +582,7 @@ | |||||||
|     <div |     <div | ||||||
|       transition:fly={{ duration: 150 }} |       transition:fly={{ duration: 150 }} | ||||||
|       id="editor-panel" |       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" |       translate="yes" | ||||||
|     > |     > | ||||||
|       <EditorPanel {asset} onUpdateSelectedType={handleUpdateSelectedEditType} onClose={closeEditor} /> |       <EditorPanel {asset} onUpdateSelectedType={handleUpdateSelectedEditType} onClose={closeEditor} /> | ||||||
| @ -631,7 +631,7 @@ | |||||||
|     <div |     <div | ||||||
|       transition:fly={{ duration: 150 }} |       transition:fly={{ duration: 150 }} | ||||||
|       id="activity-panel" |       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" |       translate="yes" | ||||||
|     > |     > | ||||||
|       <ActivityViewer |       <ActivityViewer | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ | |||||||
| {#if asset.exifInfo?.country} | {#if asset.exifInfo?.country} | ||||||
|   <button |   <button | ||||||
|     type="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)} |     onclick={() => (isOwner ? (isShowChangeLocation = true) : null)} | ||||||
|     title={isOwner ? $t('edit_location') : ''} |     title={isOwner ? $t('edit_location') : ''} | ||||||
|     class:hover:dark:text-immich-dark-primary={isOwner} |     class:hover:dark:text-immich-dark-primary={isOwner} | ||||||
| @ -68,7 +68,7 @@ | |||||||
| {:else if !asset.exifInfo?.city && isOwner} | {:else if !asset.exifInfo?.city && isOwner} | ||||||
|   <button |   <button | ||||||
|     type="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)} |     onclick={() => (isShowChangeLocation = true)} | ||||||
|     title={$t('add_location')} |     title={$t('add_location')} | ||||||
|   > |   > | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ | |||||||
|       {#each tags as tag (tag.id)} |       {#each tags as tag (tag.id)} | ||||||
|         <div class="flex group transition-all"> |         <div class="flex group transition-all"> | ||||||
|           <a |           <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}`)} |             href={encodeURI(`${AppRoute.TAGS}/?path=${tag.value}`)} | ||||||
|           > |           > | ||||||
|             <p class="text-sm"> |             <p class="text-sm"> | ||||||
| @ -60,7 +60,7 @@ | |||||||
| 
 | 
 | ||||||
|           <button |           <button | ||||||
|             type="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" |             title="Remove tag" | ||||||
|             onclick={() => handleRemove(tag.id)} |             onclick={() => handleRemove(tag.id)} | ||||||
|           > |           > | ||||||
|  | |||||||
| @ -296,7 +296,7 @@ | |||||||
|     {#if dateTime} |     {#if dateTime} | ||||||
|       <button |       <button | ||||||
|         type="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)} |         onclick={() => (isOwner ? (isShowChangeDate = true) : null)} | ||||||
|         title={isOwner ? $t('edit_date') : ''} |         title={isOwner ? $t('edit_date') : ''} | ||||||
|         class:hover:dark:text-immich-dark-primary={isOwner} |         class:hover:dark:text-immich-dark-primary={isOwner} | ||||||
|  | |||||||
| @ -16,14 +16,14 @@ | |||||||
| {#if downloadStore.isDownloading} | {#if downloadStore.isDownloading} | ||||||
|   <div |   <div | ||||||
|     transition:fly={{ x: -100, duration: 350 }} |     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> |     <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"> |     <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)} |       {#each Object.keys(downloadStore.assets) as downloadKey (downloadKey)} | ||||||
|         {@const download = downloadStore.assets[downloadKey]} |         {@const download = downloadStore.assets[downloadKey]} | ||||||
|         <div class="mb-2 flex place-items-center" transition:slide> |         <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"> |             <div class="flex place-items-center justify-between gap-2 text-xs font-medium"> | ||||||
|               <p class="truncate">■ {downloadKey}</p> |               <p class="truncate">■ {downloadKey}</p> | ||||||
|               {#if download.total} |               {#if download.total} | ||||||
| @ -41,7 +41,7 @@ | |||||||
|               </p> |               </p> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="absolute right-2"> |           <div class="absolute end-2"> | ||||||
|             <CircleIconButton |             <CircleIconButton | ||||||
|               title={$t('close')} |               title={$t('close')} | ||||||
|               onclick={() => abort(downloadKey, download)} |               onclick={() => abort(downloadKey, download)} | ||||||
|  | |||||||
| @ -308,13 +308,13 @@ | |||||||
|   }; |   }; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="absolute left-0 top-0"> | <div class="absolute start-0 top-0"> | ||||||
|   <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 left-0"></canvas> |   <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 start-0"></canvas> | ||||||
| 
 | 
 | ||||||
|   <div |   <div | ||||||
|     id="face-selector" |     id="face-selector" | ||||||
|     bind:this={faceSelectorEl} |     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> |     <p class="text-center text-sm">Select a person to tag</p> | ||||||
| 
 | 
 | ||||||
| @ -329,7 +329,7 @@ | |||||||
|             <button |             <button | ||||||
|               onclick={() => tagFace(person)} |               onclick={() => tagFace(person)} | ||||||
|               type="button" |               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 |               <ImageThumbnail | ||||||
|                 curve |                 curve | ||||||
|  | |||||||
| @ -213,7 +213,7 @@ | |||||||
|         <img |         <img | ||||||
|           src={assetFileUrl} |           src={assetFileUrl} | ||||||
|           alt={$getAltText(asset)} |           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" |           draggable="false" | ||||||
|         /> |         /> | ||||||
|       {/if} |       {/if} | ||||||
|  | |||||||
| @ -101,7 +101,7 @@ | |||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| {#if hidden} | {#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} /> |     <Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} /> | ||||||
|   </div> |   </div> | ||||||
| {/if} | {/if} | ||||||
|  | |||||||
| @ -332,20 +332,20 @@ | |||||||
| 
 | 
 | ||||||
|         <!-- Favorite asset star --> |         <!-- Favorite asset star --> | ||||||
|         {#if !isSharedLink() && asset.isFavorite} |         {#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" /> |             <Icon path={mdiHeart} size="24" class="text-white" /> | ||||||
|           </div> |           </div> | ||||||
|         {/if} |         {/if} | ||||||
| 
 | 
 | ||||||
|         {#if !isSharedLink() && showArchiveIcon && asset.isArchived} |         {#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" /> |             <Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" /> | ||||||
|           </div> |           </div> | ||||||
|         {/if} |         {/if} | ||||||
| 
 | 
 | ||||||
|         {#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR} |         {#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"> |           <div class="absolute end-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white"> | ||||||
|             <span class="pr-2 pt-2"> |             <span class="pe-2 pt-2"> | ||||||
|               <Icon path={mdiRotate360} size="24" /> |               <Icon path={mdiRotate360} size="24" /> | ||||||
|             </span> |             </span> | ||||||
|           </div> |           </div> | ||||||
| @ -356,10 +356,10 @@ | |||||||
|           <div |           <div | ||||||
|             class={[ |             class={[ | ||||||
|               'absolute z-10 flex place-items-center gap-1 text-xs font-medium text-white', |               '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> |               <p>{asset.stack.assetCount.toLocaleString($locale)}</p> | ||||||
|               <Icon path={mdiCameraBurst} size="24" /> |               <Icon path={mdiCameraBurst} size="24" /> | ||||||
|             </span> |             </span> | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ | |||||||
|   }; |   }; | ||||||
| </script> | </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} |   {#if showTime} | ||||||
|     <span class="pt-2"> |     <span class="pt-2"> | ||||||
|       {#if remainingSeconds < 60} |       {#if remainingSeconds < 60} | ||||||
| @ -69,7 +69,7 @@ | |||||||
|   {/if} |   {/if} | ||||||
| 
 | 
 | ||||||
|   <!-- svelte-ignore a11y_no_static_element_interactions --> |   <!-- 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 enablePlayback} | ||||||
|       {#if loading} |       {#if loading} | ||||||
|         <LoadingSpinner /> |         <LoadingSpinner /> | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const sizeClasses: Record<Size, string> = { |   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', |     icon: 'p-2.5', | ||||||
|     link: 'p-2 font-medium', |     link: 'p-2 font-medium', | ||||||
|     sm: 'px-4 py-2 text-sm font-medium', |     sm: 'px-4 py-2 text-sm font-medium', | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ | |||||||
|   }; |   }; | ||||||
| </script> | </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 |   <Button | ||||||
|     size="sm" |     size="sm" | ||||||
|     rounded="none" |     rounded="none" | ||||||
|  | |||||||
| @ -82,10 +82,10 @@ | |||||||
|   const getAlignClass = (position: 'bottom-left' | 'bottom-right') => { |   const getAlignClass = (position: 'bottom-left' | 'bottom-right') => { | ||||||
|     switch (position) { |     switch (position) { | ||||||
|       case 'bottom-left': { |       case 'bottom-left': { | ||||||
|         return 'left-0'; |         return 'start-0'; | ||||||
|       } |       } | ||||||
|       case 'bottom-right': { |       case 'bottom-right': { | ||||||
|         return 'right-0'; |         return 'end-0'; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       default: { |       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" |     : '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" /> |   <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 |     <SearchPeople | ||||||
|       bind:searchName={name} |       bind:searchName={name} | ||||||
|       bind:searchedPeopleLocal={suggestedPeople} |       bind:searchedPeopleLocal={suggestedPeople} | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ | |||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <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:hover:opacity-100={selectable} | ||||||
|     class:rounded-full={circle} |     class:rounded-full={circle} | ||||||
|     class:rounded-lg={!circle} |     class:rounded-lg={!circle} | ||||||
| @ -52,7 +52,7 @@ | |||||||
| 
 | 
 | ||||||
|   {#if selected} |   {#if selected} | ||||||
|     <div |     <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-full={circle} | ||||||
|       class:rounded-lg={!circle} |       class:rounded-lg={!circle} | ||||||
|     ></div> |     ></div> | ||||||
| @ -60,7 +60,7 @@ | |||||||
| 
 | 
 | ||||||
|   {#if person.name} |   {#if person.name} | ||||||
|     <span |     <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} |       {person.name} | ||||||
|     </span> |     </span> | ||||||
|  | |||||||
| @ -117,12 +117,12 @@ | |||||||
|   <div class="flex items-center"> |   <div class="flex items-center"> | ||||||
|     <CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} /> |     <CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} /> | ||||||
|     <div class="flex gap-2 items-center"> |     <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> |       <p class="text-sm text-gray-400 dark:text-gray-600">({totalPeopleCount.toLocaleString($locale)})</p> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   <div class="flex items-center justify-end"> |   <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={$t('reset_people_visibility')} icon={mdiRestart} onclick={handleResetVisibility} /> | ||||||
|       <CircleIconButton title={toggleButton.label} icon={toggleButton.icon} onclick={handleToggleVisibility} /> |       <CircleIconButton title={toggleButton.label} icon={toggleButton.icon} onclick={handleToggleVisibility} /> | ||||||
|     </div> |     </div> | ||||||
| @ -154,7 +154,7 @@ | |||||||
|           hiddenIconClass="text-white group-hover:text-black transition-colors" |           hiddenIconClass="text-white group-hover:text-black transition-colors" | ||||||
|         /> |         /> | ||||||
|         {#if person.name} |         {#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} |             {person.name} | ||||||
|           </span> |           </span> | ||||||
|         {/if} |         {/if} | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ | |||||||
| 
 | 
 | ||||||
| <section | <section | ||||||
|   transition:fly={{ y: 500, duration: 100, easing: quintOut }} |   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}> |   <ControlAppBar onClose={onBack}> | ||||||
|     {#snippet leading()} |     {#snippet leading()} | ||||||
| @ -113,7 +113,7 @@ | |||||||
|     {#snippet trailing()} |     {#snippet trailing()} | ||||||
|       <Button size="sm" disabled={!hasSelection} onclick={handleMerge}> |       <Button size="sm" disabled={!hasSelection} onclick={handleMerge}> | ||||||
|         <Icon path={mdiMerge} size={18} /> |         <Icon path={mdiMerge} size={18} /> | ||||||
|         <span class="ml-2">{$t('merge')}</span></Button |         <span class="ms-2">{$t('merge')}</span></Button | ||||||
|       > |       > | ||||||
|     {/snippet} |     {/snippet} | ||||||
|   </ControlAppBar> |   </ControlAppBar> | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ | |||||||
|         circle |         circle | ||||||
|       /> |       /> | ||||||
|       {#if person.isFavorite} |       {#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" /> |           <Icon path={mdiHeart} size="24" class="text-white" /> | ||||||
|         </div> |         </div> | ||||||
|       {/if} |       {/if} | ||||||
| @ -62,7 +62,7 @@ | |||||||
|   </a> |   </a> | ||||||
| 
 | 
 | ||||||
|   {#if showVerticalDots} |   {#if showVerticalDots} | ||||||
|     <div class="absolute top-2 right-2"> |     <div class="absolute top-2 end-2"> | ||||||
|       <ButtonContextMenu |       <ButtonContextMenu | ||||||
|         buttonClass="icon-white-drop-shadow focus:opacity-100 {showVerticalDots ? 'opacity-100' : 'opacity-0'}" |         buttonClass="icon-white-drop-shadow focus:opacity-100 {showVerticalDots ? 'opacity-100' : 'opacity-0'}" | ||||||
|         color="opaque" |         color="opaque" | ||||||
|  | |||||||
| @ -227,7 +227,7 @@ | |||||||
|             <div |             <div | ||||||
|               role="button" |               role="button" | ||||||
|               tabindex={index} |               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]])} |               onfocus={() => ($boundingBoxesArray = [peopleWithFaces[index]])} | ||||||
|               onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])} |               onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])} | ||||||
|               onmouseleave={() => ($boundingBoxesArray = [])} |               onmouseleave={() => ($boundingBoxesArray = [])} | ||||||
| @ -303,7 +303,7 @@ | |||||||
|                 </p> |                 </p> | ||||||
|               {/if} |               {/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]} |                 {#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]} | ||||||
|                   <CircleIconButton |                   <CircleIconButton | ||||||
|                     color="primary" |                     color="primary" | ||||||
| @ -311,7 +311,7 @@ | |||||||
|                     title={$t('reset')} |                     title={$t('reset')} | ||||||
|                     size="18" |                     size="18" | ||||||
|                     padding="1" |                     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)} |                     onclick={() => handleReset(face.id)} | ||||||
|                   /> |                   /> | ||||||
|                 {:else} |                 {:else} | ||||||
| @ -321,29 +321,29 @@ | |||||||
|                     title={$t('select_new_face')} |                     title={$t('select_new_face')} | ||||||
|                     size="18" |                     size="18" | ||||||
|                     padding="1" |                     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)} |                     onclick={() => handleFacePicker(face)} | ||||||
|                   /> |                   /> | ||||||
|                 {/if} |                 {/if} | ||||||
|               </div> |               </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} |                 {#if !selectedPersonToCreate[face.id] && !selectedPersonToReassign[face.id] && !face.person} | ||||||
|                   <div |                   <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" /> |                     <Icon color="primary" path={mdiAccountOff} ariaHidden size="18" /> | ||||||
|                   </div> |                   </div> | ||||||
|                 {/if} |                 {/if} | ||||||
|               </div> |               </div> | ||||||
|               {#if face.person != null} |               {#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 |                   <CircleIconButton | ||||||
|                     color="red" |                     color="red" | ||||||
|                     icon={mdiTrashCan} |                     icon={mdiTrashCan} | ||||||
|                     title={$t('delete_face')} |                     title={$t('delete_face')} | ||||||
|                     size="18" |                     size="18" | ||||||
|                     padding="1" |                     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)} |                     onclick={() => deleteAssetFace(face)} | ||||||
|                   /> |                   /> | ||||||
|                 </div> |                 </div> | ||||||
|  | |||||||
| @ -120,7 +120,7 @@ | |||||||
| 
 | 
 | ||||||
| <section | <section | ||||||
|   transition:fly={{ y: 500, duration: 100, easing: quintOut }} |   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}> |   <ControlAppBar {onClose}> | ||||||
|     {#snippet leading()} |     {#snippet leading()} | ||||||
| @ -140,7 +140,7 @@ | |||||||
|           {:else} |           {:else} | ||||||
|             <LoadingSpinner /> |             <LoadingSpinner /> | ||||||
|           {/if} |           {/if} | ||||||
|           <span class="ml-2"> {$t('create_new_person')}</span></Button |           <span class="ms-2"> {$t('create_new_person')}</span></Button | ||||||
|         > |         > | ||||||
|         <Button |         <Button | ||||||
|           size="sm" |           size="sm" | ||||||
| @ -155,7 +155,7 @@ | |||||||
|           {:else} |           {:else} | ||||||
|             <LoadingSpinner /> |             <LoadingSpinner /> | ||||||
|           {/if} |           {/if} | ||||||
|           <span class="ml-2"> {$t('reassign')}</span></Button |           <span class="ms-2"> {$t('reassign')}</span></Button | ||||||
|         > |         > | ||||||
|       </div> |       </div> | ||||||
|     {/snippet} |     {/snippet} | ||||||
|  | |||||||
| @ -173,7 +173,7 @@ | |||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4"> | <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"> |     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> | ||||||
|       {#each validatedPaths as validatedPath, listIndex (validatedPath.importPath)} |       {#each validatedPaths as validatedPath, listIndex (validatedPath.importPath)} | ||||||
|         <tr |         <tr | ||||||
| @ -183,7 +183,7 @@ | |||||||
|               : 'bg-immich-bg dark:bg-immich-dark-gray/50' |               : '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} |             {#if validatedPath.isValid} | ||||||
|               <Icon |               <Icon | ||||||
|                 path={mdiCheckCircleOutline} |                 path={mdiCheckCircleOutline} | ||||||
|  | |||||||
| @ -123,7 +123,7 @@ | |||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4"> | <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"> |     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> | ||||||
|       {#each exclusionPatterns as exclusionPattern, listIndex (exclusionPattern)} |       {#each exclusionPatterns as exclusionPattern, listIndex (exclusionPattern)} | ||||||
|         <tr |         <tr | ||||||
|  | |||||||
| @ -72,7 +72,7 @@ | |||||||
|       {#if tag} |       {#if tag} | ||||||
|         <div class="flex group transition-all"> |         <div class="flex group transition-all"> | ||||||
|           <span |           <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"> |             <p class="text-sm"> | ||||||
|               {tag.value} |               {tag.value} | ||||||
| @ -81,7 +81,7 @@ | |||||||
| 
 | 
 | ||||||
|           <button |           <button | ||||||
|             type="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" |             title="Remove tag" | ||||||
|             onclick={() => handleRemove(tagId)} |             onclick={() => handleRemove(tagId)} | ||||||
|           > |           > | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
|       alt="Immich logo" |       alt="Immich logo" | ||||||
|     /> |     /> | ||||||
|     <div |     <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> | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -360,8 +360,8 @@ | |||||||
| 
 | 
 | ||||||
|         {#each current.memory.assets as asset, index (asset.id)} |         {#each current.memory.assets as asset, index (asset.id)} | ||||||
|           <a class="relative w-full py-2" href={asHref(asset)} aria-label={$t('view')}> |           <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 start-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] bg-white" style:width={`${toProgressPercentage(index)}%`}></span> | ||||||
|           </a> |           </a> | ||||||
|         {/each} |         {/each} | ||||||
| 
 | 
 | ||||||
| @ -380,7 +380,7 @@ | |||||||
| 
 | 
 | ||||||
|     {#if galleryInView} |     {#if galleryInView} | ||||||
|       <div |       <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-0={!galleryInView} | ||||||
|         class:opacity-100={galleryInView} |         class:opacity-100={galleryInView} | ||||||
|       > |       > | ||||||
| @ -396,7 +396,7 @@ | |||||||
|     <!-- Viewer --> |     <!-- Viewer --> | ||||||
|     <section class="overflow-hidden pt-32 md:pt-20" bind:clientHeight={viewerHeight}> |     <section class="overflow-hidden pt-32 md:pt-20" bind:clientHeight={viewerHeight}> | ||||||
|       <div |       <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 --> |         <!-- PREVIOUS MEMORY --> | ||||||
|         <div class="h-1/2 w-[20vw] rounded-2xl {current.previousMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}"> |         <div class="h-1/2 w-[20vw] rounded-2xl {current.previousMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}"> | ||||||
| @ -424,7 +424,7 @@ | |||||||
|             {/if} |             {/if} | ||||||
| 
 | 
 | ||||||
|             {#if current.previousMemory} |             {#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-xs font-semibold text-gray-200">{$t('previous').toUpperCase()}</p> | ||||||
|                 <p class="text-xl">{$memoryLaneTitle(current.previousMemory)}</p> |                 <p class="text-xl">{$memoryLaneTitle(current.previousMemory)}</p> | ||||||
|               </div> |               </div> | ||||||
| @ -465,7 +465,7 @@ | |||||||
|             {/key} |             {/key} | ||||||
| 
 | 
 | ||||||
|             <div |             <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-0={galleryInView} | ||||||
|               class:opacity-100={!galleryInView} |               class:opacity-100={!galleryInView} | ||||||
|             > |             > | ||||||
| @ -521,7 +521,7 @@ | |||||||
|             </div> |             </div> | ||||||
|             <!-- CONTROL BUTTONS --> |             <!-- CONTROL BUTTONS --> | ||||||
|             {#if current.previous} |             {#if current.previous} | ||||||
|               <div class="absolute top-1/2 left-0 ml-4"> |               <div class="absolute top-1/2 start-0 ms-4"> | ||||||
|                 <CircleIconButton |                 <CircleIconButton | ||||||
|                   title={$t('previous_memory')} |                   title={$t('previous_memory')} | ||||||
|                   icon={mdiChevronLeft} |                   icon={mdiChevronLeft} | ||||||
| @ -532,7 +532,7 @@ | |||||||
|             {/if} |             {/if} | ||||||
| 
 | 
 | ||||||
|             {#if current.next} |             {#if current.next} | ||||||
|               <div class="absolute top-1/2 right-0 mr-4"> |               <div class="absolute top-1/2 end-0 me-4"> | ||||||
|                 <CircleIconButton |                 <CircleIconButton | ||||||
|                   title={$t('next_memory')} |                   title={$t('next_memory')} | ||||||
|                   icon={mdiChevronRight} |                   icon={mdiChevronRight} | ||||||
| @ -542,7 +542,7 @@ | |||||||
|               </div> |               </div> | ||||||
|             {/if} |             {/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> |               <p> | ||||||
|                 {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL, { |                 {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL, { | ||||||
|                   locale: $locale, |                   locale: $locale, | ||||||
| @ -582,7 +582,7 @@ | |||||||
|             {/if} |             {/if} | ||||||
| 
 | 
 | ||||||
|             {#if current.nextMemory} |             {#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-xs font-semibold text-gray-200">{$t('up_next').toUpperCase()}</p> | ||||||
|                 <p class="text-xl">{$memoryLaneTitle(current.nextMemory)}</p> |                 <p class="text-xl">{$memoryLaneTitle(current.nextMemory)}</p> | ||||||
|               </div> |               </div> | ||||||
|  | |||||||
| @ -149,7 +149,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       {/if} |       {/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} |         {dateGroup.groupTitle} | ||||||
|       </span> |       </span> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -743,7 +743,7 @@ | |||||||
| <!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar --> | <!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar --> | ||||||
| <section | <section | ||||||
|   id="asset-grid" |   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'} |   style:margin-right={(usingMobileDevice ? 0 : scrubberWidth) + 'px'} | ||||||
|   tabindex="-1" |   tabindex="-1" | ||||||
|   bind:clientHeight={assetStore.viewportHeight} |   bind:clientHeight={assetStore.viewportHeight} | ||||||
|  | |||||||
| @ -44,9 +44,9 @@ | |||||||
|     onscroll={onScroll} |     onscroll={onScroll} | ||||||
|   > |   > | ||||||
|     {#if canScrollLeft || canScrollRight} |     {#if canScrollLeft || canScrollRight} | ||||||
|       <div class="sticky left-0 z-20"> |       <div class="sticky start-0 z-20"> | ||||||
|         {#if canScrollLeft} |         {#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 |             <button | ||||||
|               type="button" |               type="button" | ||||||
|               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100" |               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> |           </div> | ||||||
|         {/if} |         {/if} | ||||||
|         {#if canScrollRight} |         {#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 |             <button | ||||||
|               type="button" |               type="button" | ||||||
|               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100" |               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)}> |     <div class="inline-block" use:resizeObserver={({ width }) => (innerWidth = width)}> | ||||||
|       {#each memoryStore.memories as memory (memory.id)} |       {#each memoryStore.memories as memory (memory.id)} | ||||||
|         <a |         <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}" |           href="{AppRoute.MEMORY}?{QueryParameter.ID}={memory.assets[0].id}" | ||||||
|         > |         > | ||||||
|           <img |           <img | ||||||
| @ -85,11 +85,11 @@ | |||||||
|             alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })} |             alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })} | ||||||
|             draggable="false" |             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)} |             {$memoryLaneTitle(memory)} | ||||||
|           </p> |           </p> | ||||||
|           <div |           <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> |           ></div> | ||||||
|         </a> |         </a> | ||||||
|       {/each} |       {/each} | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|     {title} |     {title} | ||||||
|   </div> |   </div> | ||||||
|   <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)" |     style:width="calc(100% - 20px)" | ||||||
|     data-skeleton="true" |     data-skeleton="true" | ||||||
|   ></div> |   ></div> | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ | |||||||
|     <button |     <button | ||||||
|       type="button" |       type="button" | ||||||
|       onclick={() => togglePlacesGroupCollapsing(group.id)} |       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} |       aria-expanded={!isCollapsed} | ||||||
|     > |     > | ||||||
|       <Icon |       <Icon | ||||||
| @ -34,7 +34,7 @@ | |||||||
|         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}" |         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="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> |     </button> | ||||||
|     <hr class="dark:border-immich-dark-gray" /> |     <hr class="dark:border-immich-dark-gray" /> | ||||||
|   </div> |   </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 --> | ||||||
|   <!-- @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()} |   {#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"> |       <div class="flex flex-col"> | ||||||
|         <label for="datetime">{$t('date_and_time')}</label> |         <label for="datetime">{$t('date_and_time')}</label> | ||||||
|         <DateInput class="immich-form-input" id="datetime" type="datetime-local" bind:value={selectedDate} /> |         <DateInput class="immich-form-input" id="datetime" type="datetime-local" bind:value={selectedDate} /> | ||||||
|  | |||||||
| @ -147,7 +147,7 @@ | |||||||
|                   : ''}" |                   : ''}" | ||||||
|                 onclick={() => handleUseSuggested(place.latitude, place.longitude)} |                 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)} |                   {getLocation(place.name, place.admin1name, place.admin2name)} | ||||||
|                 </p> |                 </p> | ||||||
|               </button> |               </button> | ||||||
| @ -189,7 +189,7 @@ | |||||||
|         {/await} |         {/await} | ||||||
|       </div> |       </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 |         <CoordinatesInput | ||||||
|           lat={point ? point.lat : assetLat} |           lat={point ? point.lat : assetLat} | ||||||
|           lng={point ? point.lng : assetLng} |           lng={point ? point.lng : assetLng} | ||||||
|  | |||||||
| @ -258,7 +258,7 @@ | |||||||
| > | > | ||||||
|   <div> |   <div> | ||||||
|     {#if isActive} |     {#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"> |         <div class="dark:text-immich-dark-fg/75"> | ||||||
|           <Icon path={mdiMagnify} ariaHidden={true} /> |           <Icon path={mdiMagnify} ariaHidden={true} /> | ||||||
|         </div> |         </div> | ||||||
| @ -273,11 +273,11 @@ | |||||||
|       aria-expanded={isOpen} |       aria-expanded={isOpen} | ||||||
|       autocomplete="off" |       autocomplete="off" | ||||||
|       bind:this={input} |       bind:this={input} | ||||||
|       class:!pl-8={isActive} |       class:!ps-8={isActive} | ||||||
|       class:!rounded-b-none={isOpen && dropdownDirection === 'bottom'} |       class:!rounded-b-none={isOpen && dropdownDirection === 'bottom'} | ||||||
|       class:!rounded-t-none={isOpen && dropdownDirection === 'top'} |       class:!rounded-t-none={isOpen && dropdownDirection === 'top'} | ||||||
|       class:cursor-pointer={!isActive} |       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} |       id={inputId} | ||||||
|       onfocus={activate} |       onfocus={activate} | ||||||
|       oninput={onInput} |       oninput={onInput} | ||||||
| @ -325,8 +325,8 @@ | |||||||
|     /> |     /> | ||||||
| 
 | 
 | ||||||
|     <div |     <div | ||||||
|       class="absolute right-0 top-0 h-full flex px-4 justify-center items-center content-between" |       class="absolute end-0 top-0 h-full flex px-4 justify-center items-center content-between" | ||||||
|       class:pr-2={selectedOption} |       class:pe-2={selectedOption} | ||||||
|       class:pointer-events-none={!selectedOption} |       class:pointer-events-none={!selectedOption} | ||||||
|     > |     > | ||||||
|       {#if selectedOption} |       {#if selectedOption} | ||||||
| @ -341,7 +341,7 @@ | |||||||
|     role="listbox" |     role="listbox" | ||||||
|     id={listboxId} |     id={listboxId} | ||||||
|     transition:fly={{ duration: 250 }} |     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-b-xl={dropdownDirection === 'bottom'} | ||||||
|     class:rounded-t-xl={dropdownDirection === 'top'} |     class:rounded-t-xl={dropdownDirection === 'top'} | ||||||
|     class:shadow={dropdownDirection === 'bottom'} |     class:shadow={dropdownDirection === 'bottom'} | ||||||
| @ -360,7 +360,7 @@ | |||||||
|           role="option" |           role="option" | ||||||
|           aria-selected={selectedIndex === 0} |           aria-selected={selectedIndex === 0} | ||||||
|           aria-disabled={true} |           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}`} |           id={`${listboxId}-${0}`} | ||||||
|           onclick={closeDropdown} |           onclick={closeDropdown} | ||||||
|         > |         > | ||||||
| @ -372,7 +372,7 @@ | |||||||
|         <li |         <li | ||||||
|           aria-selected={index === selectedIndex} |           aria-selected={index === selectedIndex} | ||||||
|           bind:this={optionRefs[index]} |           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}`} |           id={`${listboxId}-${index}`} | ||||||
|           onclick={() => handleSelect(option)} |           onclick={() => handleSelect(option)} | ||||||
|           role="option" |           role="option" | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ | |||||||
|   } from '$lib/components/elements/buttons/circle-icon-button.svelte'; |   } from '$lib/components/elements/buttons/circle-icon-button.svelte'; | ||||||
|   import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; |   import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; | ||||||
|   import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store'; |   import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store'; | ||||||
|  |   import { languageManager } from '$lib/stores/language-manager.svelte'; | ||||||
|   import { |   import { | ||||||
|     getContextMenuPositionFromBoundingRect, |     getContextMenuPositionFromBoundingRect, | ||||||
|     getContextMenuPositionFromEvent, |     getContextMenuPositionFromEvent, | ||||||
| @ -26,6 +27,7 @@ | |||||||
|     /** |     /** | ||||||
|      * The direction in which the context menu should open. |      * The direction in which the context menu should open. | ||||||
|      */ |      */ | ||||||
|  |     // TODO change to start vs end | ||||||
|     direction?: 'left' | 'right'; |     direction?: 'left' | 'right'; | ||||||
|     color?: Color; |     color?: Color; | ||||||
|     size?: string | undefined; |     size?: string | undefined; | ||||||
| @ -62,7 +64,15 @@ | |||||||
|   const menuId = `context-menu-${id}`; |   const menuId = `context-menu-${id}`; | ||||||
| 
 | 
 | ||||||
|   const openDropdown = (event: KeyboardEvent | MouseEvent) => { |   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; |     isOpen = true; | ||||||
|     menuContainer?.focus(); |     menuContainer?.focus(); | ||||||
|   }; |   }; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
|   import { slide } from 'svelte/transition'; |   import { slide } from 'svelte/transition'; | ||||||
|   import { clickOutside } from '$lib/actions/click-outside'; |   import { clickOutside } from '$lib/actions/click-outside'; | ||||||
|   import type { Snippet } from 'svelte'; |   import type { Snippet } from 'svelte'; | ||||||
|  |   import { languageManager } from '$lib/stores/language-manager.svelte'; | ||||||
| 
 | 
 | ||||||
|   interface Props { |   interface Props { | ||||||
|     isVisible?: boolean; |     isVisible?: boolean; | ||||||
| @ -41,12 +42,17 @@ | |||||||
| 
 | 
 | ||||||
|   $effect(() => { |   $effect(() => { | ||||||
|     if (menuElement) { |     if (menuElement) { | ||||||
|  |       let layoutDirection = direction; | ||||||
|  |       if (languageManager.rtl) { | ||||||
|  |         layoutDirection = direction === 'left' ? 'right' : 'left'; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       const rect = menuElement.getBoundingClientRect(); |       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; |       const menuHeight = Math.min(menuElement.clientHeight, height) || 0; | ||||||
| 
 | 
 | ||||||
|       left = Math.min(window.innerWidth - rect.width, x - directionWidth); |       left = Math.max(8, Math.min(window.innerWidth - rect.width, x - directionWidth)); | ||||||
|       top = Math.min(window.innerHeight - menuHeight, y); |       top = Math.max(8, Math.min(window.innerHeight - menuHeight, y)); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| </script> | </script> | ||||||
| @ -66,7 +72,7 @@ | |||||||
|     aria-labelledby={ariaLabelledBy} |     aria-labelledby={ariaLabelledBy} | ||||||
|     bind:this={menuElement} |     bind:this={menuElement} | ||||||
|     class="{isVisible |     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" |       : 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto" | ||||||
|     role="menu" |     role="menu" | ||||||
|     tabindex="-1" |     tabindex="-1" | ||||||
|  | |||||||
| @ -53,7 +53,7 @@ | |||||||
|   onclick={handleClick} |   onclick={handleClick} | ||||||
|   onmouseover={() => ($selectedIdStore = id)} |   onmouseover={() => ($selectedIdStore = id)} | ||||||
|   onmouseleave={() => ($selectedIdStore = undefined)} |   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 |     ? activeColor | ||||||
|     : 'bg-slate-100'}" |     : 'bg-slate-100'}" | ||||||
|   role="menuitem" |   role="menuitem" | ||||||
| @ -65,7 +65,7 @@ | |||||||
|     <div class="flex justify-between"> |     <div class="flex justify-between"> | ||||||
|       {text} |       {text} | ||||||
|       {#if shortcutLabel} |       {#if shortcutLabel} | ||||||
|         <span class="text-gray-500 pl-4"> |         <span class="text-gray-500 ps-4"> | ||||||
|           {shortcutLabel} |           {shortcutLabel} | ||||||
|         </span> |         </span> | ||||||
|       {/if} |       {/if} | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ | |||||||
|     const elements = document.elementsFromPoint(event.x, event.y); |     const elements = document.elementsFromPoint(event.x, event.y); | ||||||
| 
 | 
 | ||||||
|     if (menuContainer && elements.includes(menuContainer)) { |     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 |       // menu as is | ||||||
|       return; |       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 |         <ContextMenu | ||||||
|           {direction} |           {direction} | ||||||
|           {x} |           {x} | ||||||
|  | |||||||
| @ -91,7 +91,7 @@ | |||||||
|       {@render children?.()} |       {@render children?.()} | ||||||
|     </div> |     </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?.()} |       {@render trailing?.()} | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ | |||||||
| <FullScreenModal title={$t('deduplication_info')} width="auto" {onClose}> | <FullScreenModal title={$t('deduplication_info')} width="auto" {onClose}> | ||||||
|   <div class="text-sm dark:text-white"> |   <div class="text-sm dark:text-white"> | ||||||
|     <p>{$t('deduplication_info_description')}</p> |     <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_1')}</li> | ||||||
|       <li>{$t('deduplication_criteria_2')}</li> |       <li>{$t('deduplication_criteria_2')}</li> | ||||||
|     </ol> |     </ol> | ||||||
|  | |||||||
| @ -77,7 +77,7 @@ | |||||||
|   role="presentation" |   role="presentation" | ||||||
|   in:fade={{ duration: 100 }} |   in:fade={{ duration: 100 }} | ||||||
|   out: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) => { |   onkeydown={(event) => { | ||||||
|     event.stopPropagation(); |     event.stopPropagation(); | ||||||
|   }} |   }} | ||||||
|  | |||||||
| @ -3,6 +3,6 @@ | |||||||
|   import { mobileDevice } from '$lib/stores/mobile-device.svelte'; |   import { mobileDevice } from '$lib/stores/mobile-device.svelte'; | ||||||
| </script> | </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} /> |   <ImmichLogo class="h-[50px] max-w-none md:w-auto md:max-w-full" noText={mobileDevice.maxMd} /> | ||||||
| </a> | </a> | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ | |||||||
|   in:fade={{ duration: 100 }} |   in:fade={{ duration: 100 }} | ||||||
|   out:fade={{ duration: 100 }} |   out:fade={{ duration: 100 }} | ||||||
|   id="account-info-panel" |   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 |   use:focusTrap | ||||||
| > | > | ||||||
|   <div |   <div | ||||||
| @ -56,7 +56,7 @@ | |||||||
|   > |   > | ||||||
|     <div class="relative"> |     <div class="relative"> | ||||||
|       <UserAvatar user={$user} size="xl" /> |       <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 |         <CircleIconButton | ||||||
|           color="primary" |           color="primary" | ||||||
|           icon={mdiPencil} |           icon={mdiPencil} | ||||||
|  | |||||||
| @ -83,8 +83,8 @@ | |||||||
|         <ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} /> |         <ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} /> | ||||||
|       </a> |       </a> | ||||||
|     </div> |     </div> | ||||||
|     <div class="flex justify-between gap-4 lg:gap-8 pr-6"> |     <div class="flex justify-between gap-4 lg:gap-8 pe-6"> | ||||||
|       <div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block"> |       <div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block"> | ||||||
|         {#if $featureFlags.search} |         {#if $featureFlags.search} | ||||||
|           <SearchBar grayTheme={true} /> |           <SearchBar grayTheme={true} /> | ||||||
|         {/if} |         {/if} | ||||||
| @ -154,7 +154,7 @@ | |||||||
|         > |         > | ||||||
|           <button |           <button | ||||||
|             type="button" |             type="button" | ||||||
|             class="flex pl-2" |             class="flex ps-2" | ||||||
|             onmouseover={() => (shouldShowAccountInfo = true)} |             onmouseover={() => (shouldShowAccountInfo = true)} | ||||||
|             onfocus={() => (shouldShowAccountInfo = true)} |             onfocus={() => (shouldShowAccountInfo = true)} | ||||||
|             onblur={() => (shouldShowAccountInfo = false)} |             onblur={() => (shouldShowAccountInfo = false)} | ||||||
| @ -170,7 +170,7 @@ | |||||||
|             <div |             <div | ||||||
|               in:fade={{ delay: 500, duration: 150 }} |               in:fade={{ delay: 500, duration: 150 }} | ||||||
|               out:fade={{ delay: 200, 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.name}</p> | ||||||
|               <p>{$user.email}</p> |               <p>{$user.email}</p> | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if showing} | {#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> |     <span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span> | ||||||
|   </div> |   </div> | ||||||
| {/if} | {/if} | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ | |||||||
|     /> |     /> | ||||||
|   </div> |   </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)} |     {#if isComponentNotification(notification)} | ||||||
|       <notification.component.type {...notification.component.props} /> |       <notification.component.type {...notification.component.props} /> | ||||||
|     {:else} |     {:else} | ||||||
| @ -109,7 +109,7 @@ | |||||||
|   </p> |   </p> | ||||||
| 
 | 
 | ||||||
|   {#if notification.button} |   {#if notification.button} | ||||||
|     <p class="pl-[28px] mt-2.5 text-sm"> |     <p class="ps-[28px] mt-2.5 text-sm"> | ||||||
|       <button |       <button | ||||||
|         type="button" |         type="button" | ||||||
|         class="{buttonStyle[notification.type]} rounded px-3 pt-1.5 pb-1 transition-all duration-200" |         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')}> | <div role="status" aria-relevant="additions" aria-label={$t('notifications')}> | ||||||
|   {#if $notificationList.length > 0} |   {#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)} |       {#each $notificationList as notification (notification.id)} | ||||||
|         <div animate:flip={{ duration: 250, easing: quintOut }}> |         <div animate:flip={{ duration: 250, easing: quintOut }}> | ||||||
|           <NotificationCard {notification} /> |           <NotificationCard {notification} /> | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ | |||||||
| <div class="relative w-full"> | <div class="relative w-full"> | ||||||
|   <input |   <input | ||||||
|     {...rest} |     {...rest} | ||||||
|     class="immich-form-input w-full !pr-12" |     class="immich-form-input w-full !pe-12" | ||||||
|     type={showPassword ? 'text' : 'password'} |     type={showPassword ? 'text' : 'password'} | ||||||
|     {required} |     {required} | ||||||
|     value={password} |     value={password} | ||||||
|  | |||||||
| @ -88,5 +88,5 @@ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if !hidden} | {#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} | {/if} | ||||||
|  | |||||||
| @ -446,7 +446,7 @@ | |||||||
|   aria-valuemax={toScrollY(1)} |   aria-valuemax={toScrollY(1)} | ||||||
|   aria-valuemin={toScrollY(0)} |   aria-valuemin={toScrollY(0)} | ||||||
|   data-id="immich-scrubbable-scrollbar" |   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-top={PADDING_TOP + 'px'} | ||||||
|   style:padding-bottom={PADDING_BOTTOM + 'px'} |   style:padding-bottom={PADDING_BOTTOM + 'px'} | ||||||
|   style:width |   style:width | ||||||
| @ -464,7 +464,7 @@ | |||||||
|       class={[ |       class={[ | ||||||
|         { 'border-b-2': isDragging }, |         { 'border-b-2': isDragging }, | ||||||
|         { 'rounded-bl-md': !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" |       style:top="{hoverY + 2}px" | ||||||
|     > |     > | ||||||
| @ -474,7 +474,7 @@ | |||||||
|   {#if usingMobileDevice && ((assetStore.scrolling && scrollHoverLabel) || isHover || isDragging)} |   {#if usingMobileDevice && ((assetStore.scrolling && scrollHoverLabel) || isHover || isDragging)} | ||||||
|     <div |     <div | ||||||
|       id="time-label" |       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:top="{PADDING_TOP + (scrollY - 50 / 2)}px" | ||||||
|       style:height="50px" |       style:height="50px" | ||||||
|       style:right="0" |       style:right="0" | ||||||
| @ -482,8 +482,8 @@ | |||||||
|       in:fade={{ duration: 200 }} |       in:fade={{ duration: 200 }} | ||||||
|       out: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-[9px] -end-[2px]" /> | ||||||
|       <Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -right-[2px]" /> |       <Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -end-[2px]" /> | ||||||
|       {#if (assetStore.scrolling && scrollHoverLabel) || isHover || isDragging} |       {#if (assetStore.scrolling && scrollHoverLabel) || isHover || isDragging} | ||||||
|         <p |         <p | ||||||
|           transition:fade={{ duration: 200 }} |           transition:fade={{ duration: 200 }} | ||||||
| @ -500,13 +500,13 @@ | |||||||
|   <!-- Scroll Position Indicator Line --> |   <!-- Scroll Position Indicator Line --> | ||||||
|   {#if !usingMobileDevice && !isDragging} |   {#if !usingMobileDevice && !isDragging} | ||||||
|     <div |     <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" |       style:top="{scrollY + PADDING_TOP - 2}px" | ||||||
|     > |     > | ||||||
|       {#if assetStore.scrolling && scrollHoverLabel && !isHover} |       {#if assetStore.scrolling && scrollHoverLabel && !isHover} | ||||||
|         <p |         <p | ||||||
|           transition:fade={{ duration: 200 }} |           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} |           {scrollHoverLabel} | ||||||
|         </p> |         </p> | ||||||
| @ -521,7 +521,7 @@ | |||||||
|     data-label={segments.at(0)?.dateFormatted} |     data-label={segments.at(0)?.dateFormatted} | ||||||
|   > |   > | ||||||
|     {#if relativeTopOffset > 6} |     {#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} |     {/if} | ||||||
|   </div> |   </div> | ||||||
|   <!-- Time Segment --> |   <!-- Time Segment --> | ||||||
| @ -535,12 +535,12 @@ | |||||||
|     > |     > | ||||||
|       {#if !usingMobileDevice} |       {#if !usingMobileDevice} | ||||||
|         {#if segment.hasLabel} |         {#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} |             {segment.date.year} | ||||||
|           </div> |           </div> | ||||||
|         {/if} |         {/if} | ||||||
|         {#if segment.hasDot} |         {#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} | ||||||
|       {/if} |       {/if} | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -261,15 +261,15 @@ | |||||||
|       /> |       /> | ||||||
|     </div> |     </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" /> |       <CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" /> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     {#if isFocus} |     {#if isFocus} | ||||||
|       <div |       <div | ||||||
|         class="absolute inset-y-0 flex items-center" |         class="absolute inset-y-0 flex items-center" | ||||||
|         class:right-16={isFocus} |         class:end-16={isFocus} | ||||||
|         class:right-28={isFocus && value.length > 0} |         class:end-28={isFocus && value.length > 0} | ||||||
|       > |       > | ||||||
|         <p |         <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" |           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} | ||||||
| 
 | 
 | ||||||
|     {#if showClearIcon} |     {#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" /> |         <CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" /> | ||||||
|       </div> |       </div> | ||||||
|     {/if} |     {/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 |       <CircleIconButton | ||||||
|         type="submit" |         type="submit" | ||||||
|         disabled={showFilter} |         disabled={showFilter} | ||||||
|  | |||||||
| @ -122,7 +122,7 @@ | |||||||
|             <!-- svelte-ignore a11y_click_events_have_key_events --> |             <!-- svelte-ignore a11y_click_events_have_key_events --> | ||||||
|             <div |             <div | ||||||
|               id={getId(index)} |               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)} |               onclick={() => handleSelect(savedSearchTerm)} | ||||||
|               role="option" |               role="option" | ||||||
|               tabindex="-1" |               tabindex="-1" | ||||||
| @ -132,7 +132,7 @@ | |||||||
|               <Icon path={mdiMagnify} size="1.5em" ariaHidden={true} /> |               <Icon path={mdiMagnify} size="1.5em" ariaHidden={true} /> | ||||||
|               {savedSearchTerm} |               {savedSearchTerm} | ||||||
|             </div> |             </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 |               <CircleIconButton | ||||||
|                 icon={mdiClose} |                 icon={mdiClose} | ||||||
|                 title={$t('remove')} |                 title={$t('remove')} | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ | |||||||
|         {#if tag} |         {#if tag} | ||||||
|           <div class="flex group transition-all"> |           <div class="flex group transition-all"> | ||||||
|             <span |             <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"> |               <p class="text-sm"> | ||||||
|                 {tag.value} |                 {tag.value} | ||||||
| @ -66,7 +66,7 @@ | |||||||
| 
 | 
 | ||||||
|             <button |             <button | ||||||
|               type="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" |               title="Remove tag" | ||||||
|               onclick={() => handleRemove(tagId)} |               onclick={() => handleRemove(tagId)} | ||||||
|             > |             > | ||||||
|  | |||||||
| @ -73,7 +73,7 @@ | |||||||
|     type="button" |     type="button" | ||||||
|     aria-expanded={isOpen} |     aria-expanded={isOpen} | ||||||
|     {onclick} |     {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> | ||||||
|       <div class="flex gap-2 place-items-center"> |       <div class="flex gap-2 place-items-center"> | ||||||
| @ -110,7 +110,7 @@ | |||||||
|   </button> |   </button> | ||||||
| 
 | 
 | ||||||
|   {#if isOpen} |   {#if isOpen} | ||||||
|     <ul transition:slide={{ duration: 150 }} class="mb-2 ml-4"> |     <ul transition:slide={{ duration: 150 }} class="mb-2 ms-4"> | ||||||
|       {@render children?.()} |       {@render children?.()} | ||||||
|     </ul> |     </ul> | ||||||
|   {/if} |   {/if} | ||||||
|  | |||||||
| @ -101,7 +101,7 @@ | |||||||
|       {#if inputType === SettingInputFieldType.COLOR} |       {#if inputType === SettingInputFieldType.COLOR} | ||||||
|         <input |         <input | ||||||
|           bind:this={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-describedby={description ? `${label}-desc` : undefined} | ||||||
|           aria-labelledby="{label}-label" |           aria-labelledby="{label}-label" | ||||||
|           id={label} |           id={label} | ||||||
|  | |||||||
| @ -65,12 +65,12 @@ | |||||||
|       path={mdiChevronDown} |       path={mdiChevronDown} | ||||||
|       size="1.2em" |       size="1.2em" | ||||||
|       ariaHidden={true} |       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-bg' | ||||||
|         : 'text-immich-fg dark:text-immich-bg'}" |         : 'text-immich-fg dark:text-immich-bg'}" | ||||||
|     /> |     /> | ||||||
|     <select |     <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} |       {disabled} | ||||||
|       aria-describedby={desc ? `${name}-desc` : undefined} |       aria-describedby={desc ? `${name}-desc` : undefined} | ||||||
|       {name} |       {name} | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="flex place-items-center justify-between"> | <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"> |     <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}> |       <label class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm" for={sliderId}> | ||||||
|         {title} |         {title} | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ | |||||||
|             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm"> |             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm"> | ||||||
|               <div class="flex justify-self-end"> |               <div class="flex justify-self-end"> | ||||||
|                 {#each shortcut.key as key (key)} |                 {#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} |                     {key} | ||||||
|                   </p> |                   </p> | ||||||
|                 {/each} |                 {/each} | ||||||
| @ -74,7 +74,7 @@ | |||||||
|             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm"> |             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm"> | ||||||
|               <div class="flex justify-self-end"> |               <div class="flex justify-self-end"> | ||||||
|                 {#each shortcut.key as key (key)} |                 {#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} |                     {key} | ||||||
|                   </p> |                   </p> | ||||||
|                 {/each} |                 {/each} | ||||||
|  | |||||||
| @ -78,7 +78,7 @@ | |||||||
|   <LicenseModal onClose={() => (isOpen = false)} /> |   <LicenseModal onClose={() => (isOpen = false)} /> | ||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <div class="license-status pl-4 text-sm"> | <div class="license-status ps-4 text-sm"> | ||||||
|   {#if $isPurchased && $preferences.purchase.showSupportBadge} |   {#if $isPurchased && $preferences.purchase.showSupportBadge} | ||||||
|     <button |     <button | ||||||
|       onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)} |       onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)} | ||||||
| @ -123,7 +123,7 @@ | |||||||
|   {#if showMessage} |   {#if showMessage} | ||||||
|     <dialog |     <dialog | ||||||
|       open |       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 }} |       transition:fade={{ duration: 150 }} | ||||||
|       onmouseover={() => (hoverMessage = true)} |       onmouseover={() => (hoverMessage = true)} | ||||||
|       onmouseleave={() => (hoverMessage = false)} |       onmouseleave={() => (hoverMessage = false)} | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ | |||||||
|   <a |   <a | ||||||
|     href={'/albums/' + album.id} |     href={'/albums/' + album.id} | ||||||
|     title={album.albumName} |     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> | ||||||
|       <div |       <div | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ | |||||||
| {/if} | {/if} | ||||||
| 
 | 
 | ||||||
| <div | <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} |   {#if $connected} | ||||||
|     <div class="flex gap-2 place-items-center place-content-center"> |     <div class="flex gap-2 place-items-center place-content-center"> | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ | |||||||
| 
 | 
 | ||||||
| <div class="relative"> | <div class="relative"> | ||||||
|   {#if hasDropdown} |   {#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 |       <button | ||||||
|         type="button" |         type="button" | ||||||
|         aria-label={$t('recent-albums')} |         aria-label={$t('recent-albums')} | ||||||
| @ -59,12 +59,12 @@ | |||||||
|     data-sveltekit-preload-data={preloadData ? 'hover' : 'off'} |     data-sveltekit-preload-data={preloadData ? 'hover' : 'off'} | ||||||
|     draggable="false" |     draggable="false" | ||||||
|     aria-current={isSelected ? 'page' : undefined} |     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 |     {isSelected | ||||||
|       ? 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/10 dark:bg-immich-dark-primary/10 dark:text-immich-dark-primary' |       ? '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 /> |       <Icon path={icon} size="1.5em" class="shrink-0" flipped={flippedLogo} ariaHidden /> | ||||||
|       <span class="text-sm font-medium">{title}</span> |       <span class="text-sm font-medium">{title}</span> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ | |||||||
|   tabindex="-1" |   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="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:shadow-2xl={isExpanded} | ||||||
|   class:dark:border-r-immich-dark-gray={isExpanded} |   class:dark:border-e-immich-dark-gray={isExpanded} | ||||||
|   class:border-r={isExpanded} |   class:border-r={isExpanded} | ||||||
|   class:w-[min(100vw,16rem)]={sidebarStore.isOpen} |   class:w-[min(100vw,16rem)]={sidebarStore.isOpen} | ||||||
|   data-testid="sidebar-parent" |   data-testid="sidebar-parent" | ||||||
| @ -43,7 +43,7 @@ | |||||||
|   use:clickOutside={{ onOutclick: closeSidebar, onEscape: closeSidebar }} |   use:clickOutside={{ onOutclick: closeSidebar, onEscape: closeSidebar }} | ||||||
|   use:focusTrap={{ active: isExpanded }} |   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?.()} |     {@render children?.()} | ||||||
|   </div> |   </div> | ||||||
| </section> | </section> | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div | <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', { |   title={$t('storage_usage', { | ||||||
|     values: { |     values: { | ||||||
|       used: getByteUnitString(usedBytes, $locale, 3), |       used: getByteUnitString(usedBytes, $locale, 3), | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ | |||||||
|         icon={mdiArrowUpLeft} |         icon={mdiArrowUpLeft} | ||||||
|         title={$t('to_parent')} |         title={$t('to_parent')} | ||||||
|         href={getLink(pathSegments.slice(0, -1).join('/'))} |         href={getLink(pathSegments.slice(0, -1).join('/'))} | ||||||
|         class="mr-2" |         class="me-2" | ||||||
|         padding="2" |         padding="2" | ||||||
|         onclick={() => {}} |         onclick={() => {}} | ||||||
|       /> |       /> | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|   let { items, parent = '', active = '', icons, getLink, getColor = () => undefined }: Props = $props(); |   let { items, parent = '', active = '', icons, getLink, getColor = () => undefined }: Props = $props(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ul class="list-none ml-2"> | <ul class="list-none ms-2"> | ||||||
|   <!-- eslint-disable-next-line svelte/require-each-key --> |   <!-- eslint-disable-next-line svelte/require-each-key --> | ||||||
|   {#each Object.entries(items).sort() as [path, tree]} |   {#each Object.entries(items).sort() as [path, tree]} | ||||||
|     {@const value = normalizeTreePath(`${parent}/${path}`)} |     {@const value = normalizeTreePath(`${parent}/${path}`)} | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ | |||||||
| <a | <a | ||||||
|   href={getLink(path)} |   href={getLink(path)} | ||||||
|   title={value} |   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 |   data-sveltekit-keepfocus | ||||||
| > | > | ||||||
|   <button type="button" {onclick} class={Object.values(tree).length === 0 ? 'invisible' : ''}> |   <button type="button" {onclick} class={Object.values(tree).length === 0 ? 'invisible' : ''}> | ||||||
| @ -45,7 +45,7 @@ | |||||||
|       size={20} |       size={20} | ||||||
|     /> |     /> | ||||||
|   </div> |   </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> | </a> | ||||||
| 
 | 
 | ||||||
| {#if isOpen} | {#if isOpen} | ||||||
|  | |||||||
| @ -48,7 +48,7 @@ | |||||||
|       } |       } | ||||||
|       uploadAssetsStore.reset(); |       uploadAssetsStore.reset(); | ||||||
|     }} |     }} | ||||||
|     class="fixed bottom-6 right-16 z-[10000]" |     class="fixed bottom-6 end-16 z-[10000]" | ||||||
|   > |   > | ||||||
|     {#if showDetail} |     {#if showDetail} | ||||||
|       <div |       <div | ||||||
| @ -136,7 +136,7 @@ | |||||||
|           type="button" |           type="button" | ||||||
|           in:scale={{ duration: 250, easing: quartInOut }} |           in:scale={{ duration: 250, easing: quartInOut }} | ||||||
|           onclick={() => (showDetail = true)} |           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)} |           {$remainingUploads.toLocaleString($locale)} | ||||||
|         </button> |         </button> | ||||||
| @ -145,7 +145,7 @@ | |||||||
|             type="button" |             type="button" | ||||||
|             in:scale={{ duration: 250, easing: quartInOut }} |             in:scale={{ duration: 250, easing: quartInOut }} | ||||||
|             onclick={() => (showDetail = true)} |             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)} |             {$stats.errors.toLocaleString($locale)} | ||||||
|           </button> |           </button> | ||||||
|  | |||||||
| @ -96,8 +96,8 @@ | |||||||
| 
 | 
 | ||||||
| <section class="my-4"> | <section class="my-4"> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <div class="ml-4 mt-4 flex flex-col gap-4"> |     <div class="ms-4 mt-4 flex flex-col gap-4"> | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('theme_selection')} |           title={$t('theme_selection')} | ||||||
|           subtitle={$t('theme_selection_description')} |           subtitle={$t('theme_selection_description')} | ||||||
| @ -106,7 +106,7 @@ | |||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingCombobox |         <SettingCombobox | ||||||
|           comboboxPlaceholder={$t('language')} |           comboboxPlaceholder={$t('language')} | ||||||
|           selectedOption={langOptions.find(({ value }) => value === closestLanguage) || defaultLangOption} |           selectedOption={langOptions.find(({ value }) => value === closestLanguage) || defaultLangOption} | ||||||
| @ -117,7 +117,7 @@ | |||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('default_locale')} |           title={$t('default_locale')} | ||||||
|           subtitle={$t('default_locale_description')} |           subtitle={$t('default_locale_description')} | ||||||
| @ -128,7 +128,7 @@ | |||||||
|         </SettingSwitch> |         </SettingSwitch> | ||||||
|       </div> |       </div> | ||||||
|       {#if $locale !== undefined} |       {#if $locale !== undefined} | ||||||
|         <div class="ml-4"> |         <div class="ms-4"> | ||||||
|           <SettingCombobox |           <SettingCombobox | ||||||
|             comboboxPlaceholder={$t('searching_locales')} |             comboboxPlaceholder={$t('searching_locales')} | ||||||
|             {selectedOption} |             {selectedOption} | ||||||
| @ -140,7 +140,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       {/if} |       {/if} | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('display_original_photos')} |           title={$t('display_original_photos')} | ||||||
|           subtitle={$t('display_original_photos_setting_description')} |           subtitle={$t('display_original_photos_setting_description')} | ||||||
| @ -148,7 +148,7 @@ | |||||||
|           onToggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)} |           onToggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('video_hover_setting')} |           title={$t('video_hover_setting')} | ||||||
|           subtitle={$t('video_hover_setting_description')} |           subtitle={$t('video_hover_setting_description')} | ||||||
| @ -156,7 +156,7 @@ | |||||||
|           onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)} |           onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('loop_videos')} |           title={$t('loop_videos')} | ||||||
|           subtitle={$t('loop_videos_description')} |           subtitle={$t('loop_videos_description')} | ||||||
| @ -165,7 +165,7 @@ | |||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="ml-4"> |       <div class="ms-4"> | ||||||
|         <SettingSwitch |         <SettingSwitch | ||||||
|           title={$t('permanent_deletion_warning')} |           title={$t('permanent_deletion_warning')} | ||||||
|           subtitle={$t('permanent_deletion_warning_setting_description')} |           subtitle={$t('permanent_deletion_warning_setting_description')} | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ | |||||||
| <section class="my-4"> | <section class="my-4"> | ||||||
|   <div in:fade={{ duration: 500 }}> |   <div in:fade={{ duration: 500 }}> | ||||||
|     <form autocomplete="off" {onsubmit}> |     <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 |         <SettingInputField | ||||||
|           inputType={SettingInputFieldType.PASSWORD} |           inputType={SettingInputFieldType.PASSWORD} | ||||||
|           label={$t('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