mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	Implement mechanism to remove and add shared user in album on web (#369)
* AFixed overlay issue of modal * Added modal with existing user * Added custom scrollbar to all pages * Fixed Document is not define when access document DOM node in browswer * Added context menu * Added api to remove user from album * Handle user leave album * Added share button to non-shared album * Added padding to album viewer: * Fixed margin top of asset selection page * Fixed issue cannot push to dockerhub
This commit is contained in:
		
							
								
								
									
										52
									
								
								.github/workflows/build_push_docker_latest.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										52
									
								
								.github/workflows/build_push_docker_latest.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,21 +21,20 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and push Immich Mono Repo |       - name: Build and push Immich Mono Repo | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./server |           context: ./server | ||||||
|           file: ./server/Dockerfile |           file: ./server/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|  |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-server:latest |             altran1502/immich-server:latest | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         run: docker push altran1502/immich-server:latest |  | ||||||
|  |  | ||||||
|   build_and_push_machine_learning_latest: |   build_and_push_machine_learning_latest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -50,21 +49,20 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Machine Learning |       - name: Build and Push Machine Learning | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./machine-learning |           context: ./machine-learning | ||||||
|           file: ./machine-learning/Dockerfile |           file: ./machine-learning/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64 |           platforms: linux/arm/v7,linux/amd64 | ||||||
|  |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-machine-learning:latest |             altran1502/immich-machine-learning:latest | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         run: docker push altran1502/immich-machine-learning:latest |  | ||||||
|  |  | ||||||
|   build_and_push_web_latest: |   build_and_push_web_latest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -78,6 +76,11 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Web |       - name: Build and Push Web | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
| @@ -85,15 +88,9 @@ jobs: | |||||||
|           file: ./web/Dockerfile |           file: ./web/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|           target: prod |           target: prod | ||||||
|  |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-web:latest |             altran1502/immich-web:latest | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         run: docker push altran1502/immich-web:latest |  | ||||||
|  |  | ||||||
|   build_and_push_nginx_latest: |   build_and_push_nginx_latest: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -107,18 +104,17 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Proxy |       - name: Build and Push Proxy | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./nginx |           context: ./nginx | ||||||
|           file: ./nginx/Dockerfile |           file: ./nginx/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|  |           push: true | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-proxy:latest |             altran1502/immich-proxy:latest | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         run: docker push altran1502/immich-proxy:latest |  | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								.github/workflows/build_push_docker_staging.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										56
									
								
								.github/workflows/build_push_docker_staging.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,22 +23,20 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and push Immich Mono Repo |       - name: Build and push Immich Mono Repo | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./server |           context: ./server | ||||||
|           file: ./server/Dockerfile |           file: ./server/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|  |           push: ${{ github.event_name == 'pull_request' }} | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-server:staging |             altran1502/immich-server:staging | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         if: ${{ github.event_name == 'pull_request' }} |  | ||||||
|         run: docker push altran1502/immich-server:staging |  | ||||||
|  |  | ||||||
|   build_and_push_machine_learning_staging: |   build_and_push_machine_learning_staging: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -53,22 +51,20 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Machine Learning |       - name: Build and Push Machine Learning | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./machine-learning |           context: ./machine-learning | ||||||
|           file: ./machine-learning/Dockerfile |           file: ./machine-learning/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64 |           platforms: linux/arm/v7,linux/amd64 | ||||||
|  |           push: ${{ github.event_name == 'pull_request' }} | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-machine-learning:staging |             altran1502/immich-machine-learning:staging | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         if: ${{ github.event_name == 'pull_request' }} |  | ||||||
|         run: docker push altran1502/immich-machine-learning:staging |  | ||||||
|  |  | ||||||
|   build_and_push_web_staging: |   build_and_push_web_staging: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -82,6 +78,11 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Web |       - name: Build and Push Web | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
| @@ -89,16 +90,9 @@ jobs: | |||||||
|           file: ./web/Dockerfile |           file: ./web/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|           target: prod |           target: prod | ||||||
|  |           push: ${{ github.event_name == 'pull_request' }} | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-web:staging |             altran1502/immich-web:staging | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         if: ${{ github.event_name == 'pull_request' }} |  | ||||||
|         run: docker push altran1502/immich-web:staging |  | ||||||
|  |  | ||||||
|   build_and_push_nginx_staging: |   build_and_push_nginx_staging: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -112,19 +106,17 @@ jobs: | |||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         id: buildx |         id: buildx | ||||||
|         uses: docker/setup-buildx-action@v2.0.0 |         uses: docker/setup-buildx-action@v2.0.0 | ||||||
|  |       - name: Login to Docker Hub | ||||||
|  |         uses: docker/login-action@v2 | ||||||
|  |         with: | ||||||
|  |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|       - name: Build and Push Proxy |       - name: Build and Push Proxy | ||||||
|         uses: docker/build-push-action@v3.1.0 |         uses: docker/build-push-action@v3.1.0 | ||||||
|         with: |         with: | ||||||
|           context: ./nginx |           context: ./nginx | ||||||
|           file: ./nginx/Dockerfile |           file: ./nginx/Dockerfile | ||||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 |           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||||
|  |           push: ${{ github.event_name == 'pull_request' }} | ||||||
|           tags: | |           tags: | | ||||||
|             altran1502/immich-proxy:staging |             altran1502/immich-proxy:staging | ||||||
|       - name: Login to Docker Hub |  | ||||||
|         uses: docker/login-action@v2 |  | ||||||
|         with: |  | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |  | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |  | ||||||
|       - name: Docker push |  | ||||||
|         if: ${{ github.event_name == 'pull_request' }} |  | ||||||
|         run: docker push altran1502/immich-proxy:staging |  | ||||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							| @@ -10,6 +10,9 @@ dev-scale: | |||||||
| stage: | stage: | ||||||
| 	docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans | 	docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans | ||||||
|  |  | ||||||
|  | pull-stage: | ||||||
|  | 	docker-compose -f ./docker/docker-compose.staging.yml pull | ||||||
|  |  | ||||||
| test-e2e: | test-e2e: | ||||||
| 	docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up  --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build | 	docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up  --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,34 @@ | |||||||
|  |  | ||||||
| :root { | :root { | ||||||
| 	font-family: 'Work Sans', sans-serif; | 	font-family: 'Work Sans', sans-serif; | ||||||
|  | 	/* --immich-icon-button-hover-color: #d3d3d3; */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html { | ||||||
|  | 	height: 100%; | ||||||
|  | 	width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html::-webkit-scrollbar { | ||||||
|  | 	width: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Track */ | ||||||
|  | html::-webkit-scrollbar-track { | ||||||
|  | 	background: #f1f1f1; | ||||||
|  | 	border-radius: 16px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Handle */ | ||||||
|  | html::-webkit-scrollbar-thumb { | ||||||
|  | 	background: rgba(85, 86, 87, 0.408); | ||||||
|  | 	border-radius: 16px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Handle on hover */ | ||||||
|  | html::-webkit-scrollbar-thumb:hover { | ||||||
|  | 	background: #4250afad; | ||||||
|  | 	border-radius: 16px; | ||||||
| } | } | ||||||
|  |  | ||||||
| body { | body { | ||||||
|   | |||||||
| @@ -1,18 +1,30 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | 	import { browser } from '$app/env'; | ||||||
|  |  | ||||||
|  | 	import { createEventDispatcher, onDestroy, onMount } from 'svelte'; | ||||||
| 	import Close from 'svelte-material-icons/Close.svelte'; | 	import Close from 'svelte-material-icons/Close.svelte'; | ||||||
|  | 	import CircleIconButton from '../shared-components/circle-icon-button.svelte'; | ||||||
|  |  | ||||||
| 	export let backIcon = Close; | 	export let backIcon = Close; | ||||||
| 	let appBarBorder = ''; | 	let appBarBorder = 'bg-immich-bg'; | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
| 	onMount(() => { | 	onMount(() => { | ||||||
| 		window.onscroll = () => { | 		if (browser) { | ||||||
| 			if (window.pageYOffset > 80) { | 			document.addEventListener('scroll', (e) => { | ||||||
| 				appBarBorder = 'border border-gray-200 bg-gray-50'; | 				if (window.pageYOffset > 80) { | ||||||
| 			} else { | 					appBarBorder = 'border border-gray-200 bg-gray-50'; | ||||||
| 				appBarBorder = ''; | 				} else { | ||||||
| 			} | 					appBarBorder = 'bg-immich-bg'; | ||||||
| 		}; | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	onDestroy(() => { | ||||||
|  | 		if (browser) { | ||||||
|  | 			document.removeEventListener('scroll', (e) => {}); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @@ -22,17 +34,19 @@ | |||||||
| 		class={`flex justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2  transition-all place-items-center`} | 		class={`flex justify-between ${appBarBorder} rounded-lg p-2 mx-2 mt-2  transition-all place-items-center`} | ||||||
| 	> | 	> | ||||||
| 		<div class="flex place-items-center gap-6"> | 		<div class="flex place-items-center gap-6"> | ||||||
| 			<button | 			<CircleIconButton | ||||||
| 				on:click={() => dispatch('close-button-click')} | 				on:click={() => dispatch('close-button-click')} | ||||||
| 				id="immich-circle-icon-button" | 				logo={backIcon} | ||||||
| 				class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`} | 				backgroundColor={'transparent'} | ||||||
| 			> | 				logoColor={'rgb(75 85 99)'} | ||||||
| 				<svelte:component this={backIcon} size="24" /> | 				hoverColor={'#e2e7e9'} | ||||||
| 			</button> | 				size={'24'} | ||||||
|  | 			/> | ||||||
|  |  | ||||||
| 			<slot name="leading" /> | 			<slot name="leading" /> | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
| 		<div class="flex place-items-center gap-6 mr-4"> | 		<div class="flex place-items-center gap-1 mr-4"> | ||||||
| 			<slot name="trailing" /> | 			<slot name="trailing" /> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|   | |||||||
| @@ -6,15 +6,16 @@ | |||||||
| 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; | 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; | ||||||
| 	import Plus from 'svelte-material-icons/Plus.svelte'; | 	import Plus from 'svelte-material-icons/Plus.svelte'; | ||||||
| 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte'; | 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte'; | ||||||
|  | 	import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte'; | ||||||
| 	import AssetViewer from '../asset-viewer/asset-viewer.svelte'; | 	import AssetViewer from '../asset-viewer/asset-viewer.svelte'; | ||||||
| 	import CircleAvatar from '../shared-components/circle-avatar.svelte'; | 	import CircleAvatar from '../shared-components/circle-avatar.svelte'; | ||||||
| 	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; | 	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; | ||||||
| 	import AssetSelection from './asset-selection.svelte'; | 	import AssetSelection from './asset-selection.svelte'; | ||||||
| 	import _ from 'lodash-es'; | 	import _ from 'lodash-es'; | ||||||
| 	import { assets } from '$app/paths'; |  | ||||||
| 	import UserSelection from './user-selection-modal.svelte'; |  | ||||||
| 	import AlbumAppBar from './album-app-bar.svelte'; | 	import AlbumAppBar from './album-app-bar.svelte'; | ||||||
| 	import UserSelectionModal from './user-selection-modal.svelte'; | 	import UserSelectionModal from './user-selection-modal.svelte'; | ||||||
|  | 	import ShareInfoModal from './share-info-modal.svelte'; | ||||||
|  | 	import CircleIconButton from '../shared-components/circle-icon-button.svelte'; | ||||||
|  |  | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
| 	export let album: AlbumResponseDto; | 	export let album: AlbumResponseDto; | ||||||
| @@ -24,6 +25,7 @@ | |||||||
| 	let isShowShareUserSelection = false; | 	let isShowShareUserSelection = false; | ||||||
| 	let isEditingTitle = false; | 	let isEditingTitle = false; | ||||||
| 	let isCreatingSharedAlbum = false; | 	let isCreatingSharedAlbum = false; | ||||||
|  | 	let isShowShareInfoModal = false; | ||||||
|  |  | ||||||
| 	let selectedAsset: AssetResponseDto; | 	let selectedAsset: AssetResponseDto; | ||||||
| 	let currentViewAssetIndex = 0; | 	let currentViewAssetIndex = 0; | ||||||
| @@ -34,7 +36,6 @@ | |||||||
| 	let backUrl = '/albums'; | 	let backUrl = '/albums'; | ||||||
| 	let currentAlbumName = ''; | 	let currentAlbumName = ''; | ||||||
| 	let currentUser: UserResponseDto; | 	let currentUser: UserResponseDto; | ||||||
| 	let bodyElement: HTMLElement; |  | ||||||
|  |  | ||||||
| 	$: isOwned = currentUser?.id == album.ownerId; | 	$: isOwned = currentUser?.id == album.ownerId; | ||||||
|  |  | ||||||
| @@ -70,14 +71,6 @@ | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	onMount(async () => { | 	onMount(async () => { | ||||||
| 		window.onscroll = (event: Event) => { |  | ||||||
| 			if (window.pageYOffset > 80) { |  | ||||||
| 				border = 'border border-gray-200 bg-gray-50'; |  | ||||||
| 			} else { |  | ||||||
| 				border = ''; |  | ||||||
| 			} |  | ||||||
| 		}; |  | ||||||
|  |  | ||||||
| 		currentAlbumName = album.albumName; | 		currentAlbumName = album.albumName; | ||||||
|  |  | ||||||
| 		try { | 		try { | ||||||
| @@ -178,28 +171,40 @@ | |||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Prevent scrolling when modal is open | 	const sharedUserDeletedHandler = async (event: CustomEvent) => { | ||||||
| 	$: { | 		const { userId }: { userId: string } = event.detail; | ||||||
| 		if (isShowShareUserSelection == true) { |  | ||||||
| 			document.body.style.overflow = 'hidden'; | 		if (userId == 'me') { | ||||||
| 		} else { | 			isShowShareInfoModal = false; | ||||||
| 			document.body.style.overflow = ''; | 			goto(backUrl); | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
|  | 		try { | ||||||
|  | 			const { data } = await api.albumApi.getAlbumInfo(album.id); | ||||||
|  |  | ||||||
|  | 			album = data; | ||||||
|  | 			isShowShareInfoModal = false; | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.log('Error [sharedUserDeletedHandler] ', e); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <svelte:body bind:this={bodyElement} /> | <section class="bg-immich-bg"> | ||||||
| <section class="bg-immich-bg relative"> |  | ||||||
| 	<AlbumAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}> | 	<AlbumAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}> | ||||||
| 		<svelte:fragment slot="trailing"> | 		<svelte:fragment slot="trailing"> | ||||||
| 			{#if album.assets.length > 0} | 			{#if album.assets.length > 0} | ||||||
| 				<button | 				<CircleIconButton | ||||||
| 					id="immich-circle-icon-button" | 					title="Add Photos" | ||||||
| 					class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`} |  | ||||||
| 					on:click={() => (isShowAssetSelection = true)} | 					on:click={() => (isShowAssetSelection = true)} | ||||||
| 				> | 					logo={FileImagePlusOutline} | ||||||
| 					<FileImagePlusOutline size="24" /> | 				/> | ||||||
| 				</button> |  | ||||||
|  | 				<CircleIconButton | ||||||
|  | 					title="Share" | ||||||
|  | 					on:click={() => (isShowShareUserSelection = true)} | ||||||
|  | 					logo={ShareVariantOutline} | ||||||
|  | 				/> | ||||||
| 			{/if} | 			{/if} | ||||||
|  |  | ||||||
| 			{#if isCreatingSharedAlbum && album.sharedUsers.length == 0} | 			{#if isCreatingSharedAlbum && album.sharedUsers.length == 0} | ||||||
| @@ -226,14 +231,14 @@ | |||||||
| 		/> | 		/> | ||||||
|  |  | ||||||
| 		{#if album.assets.length > 0} | 		{#if album.assets.length > 0} | ||||||
| 			<p class="my-4 text-sm text-gray-500">{getDateRange()}</p> | 			<p class="my-4 text-sm text-gray-500 font-medium">{getDateRange()}</p> | ||||||
| 		{/if} | 		{/if} | ||||||
|  |  | ||||||
| 		{#if album.shared} | 		{#if album.shared} | ||||||
| 			<div class="my-4 flex"> | 			<div class="my-6 flex"> | ||||||
| 				{#each album.sharedUsers as user} | 				{#each album.sharedUsers as user} | ||||||
| 					<span class="mr-1"> | 					<span class="mr-1"> | ||||||
| 						<CircleAvatar {user} /> | 						<CircleAvatar {user} on:click={() => (isShowShareInfoModal = true)} /> | ||||||
| 					</span> | 					</span> | ||||||
| 				{/each} | 				{/each} | ||||||
|  |  | ||||||
| @@ -248,7 +253,7 @@ | |||||||
| 		{/if} | 		{/if} | ||||||
|  |  | ||||||
| 		{#if album.assets.length > 0} | 		{#if album.assets.length > 0} | ||||||
| 			<div class="flex flex-wrap gap-1 w-full" bind:clientWidth={viewWidth}> | 			<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}> | ||||||
| 				{#each album.assets as asset} | 				{#each album.assets as asset} | ||||||
| 					{#if album.assets.length < 7} | 					{#if album.assets.length < 7} | ||||||
| 						<ImmichThumbnail | 						<ImmichThumbnail | ||||||
| @@ -305,3 +310,11 @@ | |||||||
| 		sharedUsersInAlbum={new Set(album.sharedUsers)} | 		sharedUsersInAlbum={new Set(album.sharedUsers)} | ||||||
| 	/> | 	/> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | {#if isShowShareInfoModal} | ||||||
|  | 	<ShareInfoModal | ||||||
|  | 		on:close={() => (isShowShareInfoModal = false)} | ||||||
|  | 		{album} | ||||||
|  | 		on:user-deleted={sharedUserDeletedHandler} | ||||||
|  | 	/> | ||||||
|  | {/if} | ||||||
|   | |||||||
| @@ -133,8 +133,8 @@ | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <section | <section | ||||||
| 	transition:fly={{ y: 1000, duration: 200, easing: quintOut }} | 	transition:fly={{ y: 500, duration: 100, easing: quintOut }} | ||||||
| 	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[200]" | 	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[9999]" | ||||||
| > | > | ||||||
| 	<AlbumAppBar on:close-button-click={() => dispatch('go-back')}> | 	<AlbumAppBar on:close-button-click={() => dispatch('go-back')}> | ||||||
| 		<svelte:fragment slot="leading"> | 		<svelte:fragment slot="leading"> | ||||||
| @@ -155,7 +155,7 @@ | |||||||
| 		</svelte:fragment> | 		</svelte:fragment> | ||||||
| 	</AlbumAppBar> | 	</AlbumAppBar> | ||||||
|  |  | ||||||
| 	<section id="image-grid" class="flex flex-wrap gap-14 mt-[160px] px-20"> | 	<section class="flex flex-wrap gap-14  px-20 overflow-y-auto"> | ||||||
| 		{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} | 		{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} | ||||||
| 			<!-- Asset Group By Date --> | 			<!-- Asset Group By Date --> | ||||||
| 			<div class="flex flex-col"> | 			<div class="flex flex-col"> | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								web/src/lib/components/album-page/share-info-modal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								web/src/lib/components/album-page/share-info-modal.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { createEventDispatcher, onMount } from 'svelte'; | ||||||
|  | 	import { AlbumResponseDto, api, UserResponseDto } from '@api'; | ||||||
|  | 	import BaseModal from '../shared-components/base-modal.svelte'; | ||||||
|  | 	import CircleAvatar from '../shared-components/circle-avatar.svelte'; | ||||||
|  | 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; | ||||||
|  | 	import CircleIconButton from '../shared-components/circle-icon-button.svelte'; | ||||||
|  | 	import ContextMenu from '../shared-components/context-menu/context-menu.svelte'; | ||||||
|  | 	import MenuOption from '../shared-components/context-menu/menu-option.svelte'; | ||||||
|  |  | ||||||
|  | 	export let album: AlbumResponseDto; | ||||||
|  |  | ||||||
|  | 	const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
|  | 	let currentUser: UserResponseDto; | ||||||
|  | 	let isShowMenu = false; | ||||||
|  | 	let position = { x: 0, y: 0 }; | ||||||
|  | 	let targetUserId: string; | ||||||
|  | 	$: isOwned = currentUser?.id == album.ownerId; | ||||||
|  |  | ||||||
|  | 	onMount(async () => { | ||||||
|  | 		try { | ||||||
|  | 			const { data } = await api.userApi.getMyUserInfo(); | ||||||
|  | 			currentUser = data; | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.error('Error [share-info-modal] [getAllUsers]', e); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	const showContextMenu = (userId: string) => { | ||||||
|  | 		const iconButton = document.getElementById('icon-' + userId); | ||||||
|  |  | ||||||
|  | 		if (iconButton) { | ||||||
|  | 			position = { | ||||||
|  | 				x: iconButton.getBoundingClientRect().left, | ||||||
|  | 				y: iconButton.getBoundingClientRect().bottom | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		targetUserId = userId; | ||||||
|  | 		isShowMenu = !isShowMenu; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	const removeUser = async (userId: string) => { | ||||||
|  | 		try { | ||||||
|  | 			await api.albumApi.removeUserFromAlbum(album.id, userId); | ||||||
|  | 			dispatch('user-deleted', { userId }); | ||||||
|  | 		} catch (e) { | ||||||
|  | 			console.error('Error [share-info-modal] [removeUser]', e); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <BaseModal on:close={() => dispatch('close')}> | ||||||
|  | 	<svelte:fragment slot="title"> | ||||||
|  | 		<span class="flex gap-2 place-items-center"> | ||||||
|  | 			<p class="font-medium text-immich-fg">Options</p> | ||||||
|  | 		</span> | ||||||
|  | 	</svelte:fragment> | ||||||
|  |  | ||||||
|  | 	<section class="max-h-[400px] overflow-y-auto immich-scrollbar pb-4"> | ||||||
|  | 		{#each album.sharedUsers as user} | ||||||
|  | 			<div | ||||||
|  | 				class="flex gap-4 p-5 place-items-center justify-between w-full transition-colors hover:bg-gray-50" | ||||||
|  | 			> | ||||||
|  | 				<div class="flex gap-4 place-items-center"> | ||||||
|  | 					<CircleAvatar {user} /> | ||||||
|  | 					<p class="font-medium text-sm">{user.firstName} {user.lastName}</p> | ||||||
|  | 				</div> | ||||||
|  |  | ||||||
|  | 				<div id={`icon-${user.id}`} class="flex place-items-center"> | ||||||
|  | 					{#if isOwned} | ||||||
|  | 						<CircleIconButton | ||||||
|  | 							on:click={() => showContextMenu(user.id)} | ||||||
|  | 							logo={DotsVertical} | ||||||
|  | 							backgroundColor={'transparent'} | ||||||
|  | 							logoColor={'#5f6368'} | ||||||
|  | 							hoverColor={'#e2e7e9'} | ||||||
|  | 							size={'20'} | ||||||
|  | 						/> | ||||||
|  | 					{:else if user.id == currentUser?.id} | ||||||
|  | 						<button | ||||||
|  | 							on:click={() => removeUser('me')} | ||||||
|  | 							class="text-sm text-immich-primary font-medium transition-colors hover:text-immich-primary/75" | ||||||
|  | 							>Leave</button | ||||||
|  | 						> | ||||||
|  | 					{/if} | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		{/each} | ||||||
|  | 	</section> | ||||||
|  |  | ||||||
|  | 	{#if isShowMenu} | ||||||
|  | 		<ContextMenu {...position} on:clickoutside={() => (isShowMenu = false)}> | ||||||
|  | 			<MenuOption on:click={() => removeUser(targetUserId)} text="Remove" /> | ||||||
|  | 		</ContextMenu> | ||||||
|  | 	{/if} | ||||||
|  | </BaseModal> | ||||||
| @@ -50,7 +50,7 @@ | |||||||
| 		</span> | 		</span> | ||||||
| 	</svelte:fragment> | 	</svelte:fragment> | ||||||
|  |  | ||||||
| 	<div class="max-h-[400px] overflow-y-auto immich-scrollbar"> | 	<div class=" max-h-[400px] overflow-y-auto immich-scrollbar"> | ||||||
| 		{#if selectedUsers.size > 0} | 		{#if selectedUsers.size > 0} | ||||||
| 			<div class="flex gap-4 py-2 px-5 overflow-x-auto place-items-center mb-2"> | 			<div class="flex gap-4 py-2 px-5 overflow-x-auto place-items-center mb-2"> | ||||||
| 				<p class="font-medium">To</p> | 				<p class="font-medium">To</p> | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| 	import { downloadAssets } from '$lib/stores/download'; | 	import { downloadAssets } from '$lib/stores/download'; | ||||||
| 	import VideoViewer from './video-viewer.svelte'; | 	import VideoViewer from './video-viewer.svelte'; | ||||||
| 	import { api, AssetResponseDto, AssetTypeEnum } from '@api'; | 	import { api, AssetResponseDto, AssetTypeEnum } from '@api'; | ||||||
|  | 	import { browser } from '$app/env'; | ||||||
|  |  | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
| @@ -20,7 +21,9 @@ | |||||||
| 	let isShowDetail = false; | 	let isShowDetail = false; | ||||||
|  |  | ||||||
| 	onMount(() => { | 	onMount(() => { | ||||||
| 		document.addEventListener('keydown', (keyInfo) => handleKeyboardPress(keyInfo.key)); | 		if (browser) { | ||||||
|  | 			document.addEventListener('keydown', (keyInfo) => handleKeyboardPress(keyInfo.key)); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	const handleKeyboardPress = (key: string) => { | 	const handleKeyboardPress = (key: string) => { | ||||||
| @@ -123,7 +126,7 @@ | |||||||
|  |  | ||||||
| <section | <section | ||||||
| 	id="immich-asset-viewer" | 	id="immich-asset-viewer" | ||||||
| 	class="absolute h-screen w-screen top-0 overflow-y-hidden bg-black z-[999] grid grid-rows-[64px_1fr] grid-cols-4  " | 	class="fixed h-screen w-screen top-0 overflow-y-hidden bg-black z-[999] grid grid-rows-[64px_1fr] grid-cols-4  " | ||||||
| > | > | ||||||
| 	<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform"> | 	<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform"> | ||||||
| 		<AsserViewerNavBar | 		<AsserViewerNavBar | ||||||
|   | |||||||
| @@ -1,30 +1,54 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { fly } from 'svelte/transition'; | 	import { fade } from 'svelte/transition'; | ||||||
| 	import { quintOut } from 'svelte/easing'; | 	import { quintOut } from 'svelte/easing'; | ||||||
| 	import Close from 'svelte-material-icons/Close.svelte'; | 	import Close from 'svelte-material-icons/Close.svelte'; | ||||||
| 	import { createEventDispatcher } from 'svelte'; | 	import { createEventDispatcher, onMount, onDestroy } from 'svelte'; | ||||||
|  | 	import { browser } from '$app/env'; | ||||||
|  | 	import CircleIconButton from './circle-icon-button.svelte'; | ||||||
|  | 	import { clickOutside } from '$lib/utils/click-outside'; | ||||||
|  |  | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
|  | 	export let zIndex = 9999; | ||||||
|  |  | ||||||
|  | 	onMount(() => { | ||||||
|  | 		if (browser) { | ||||||
|  | 			const scrollTop = document.documentElement.scrollTop; | ||||||
|  | 			const scrollLeft = document.documentElement.scrollLeft; | ||||||
|  | 			window.onscroll = function () { | ||||||
|  | 				window.scrollTo(scrollLeft, scrollTop); | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	onDestroy(() => { | ||||||
|  | 		if (browser) { | ||||||
|  | 			window.onscroll = function () {}; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <div | <div | ||||||
| 	id="immich-modal" | 	id="immich-modal" | ||||||
| 	transition:fly={{ y: 1000, duration: 200, easing: quintOut }} | 	style:z-index={zIndex} | ||||||
| 	class="absolute top-0 w-screen h-screen z-[9999] bg-black/50 flex place-items-center place-content-center" | 	transition:fade={{ duration: 100, easing: quintOut }} | ||||||
|  | 	class="fixed top-0 w-full h-full  bg-black/50 flex place-items-center place-content-center overflow-hidden" | ||||||
| > | > | ||||||
| 	<div class="bg-white w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md"> | 	<div | ||||||
|  | 		use:clickOutside | ||||||
|  | 		on:out-click={() => dispatch('close')} | ||||||
|  | 		class="bg-white w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md" | ||||||
|  | 	> | ||||||
| 		<div class="flex justify-between place-items-center p-5"> | 		<div class="flex justify-between place-items-center p-5"> | ||||||
| 			<div> | 			<div> | ||||||
| 				<slot name="title"> | 				<slot name="title"> | ||||||
| 					<p>Modal Title</p> | 					<p>Modal Title</p> | ||||||
| 				</slot> | 				</slot> | ||||||
| 			</div> | 			</div> | ||||||
| 			<button on:click={() => dispatch('close')}> |  | ||||||
| 				<Close size="24" /> | 			<CircleIconButton on:click={() => dispatch('close')} logo={Close} size={'20'} /> | ||||||
| 			</button> |  | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
| 		<div class="mt-4"> | 		<div class=""> | ||||||
| 			<slot /> | 			<slot /> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|   | |||||||
| @@ -1,11 +1,13 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { api, UserResponseDto } from '@api'; | 	import { api, UserResponseDto } from '@api'; | ||||||
|  | 	import { createEventDispatcher } from 'svelte'; | ||||||
|  |  | ||||||
| 	export let user: UserResponseDto; | 	export let user: UserResponseDto; | ||||||
|  |  | ||||||
| 	// Avatar Size In Pixel | 	// Avatar Size In Pixel | ||||||
| 	export let size: number = 48; | 	export let size: number = 48; | ||||||
|  |  | ||||||
|  | 	const dispatch = createEventDispatcher(); | ||||||
| 	const getUserAvatar = async () => { | 	const getUserAvatar = async () => { | ||||||
| 		try { | 		try { | ||||||
| 			const { data } = await api.userApi.getProfileImage(user.id, { | 			const { data } = await api.userApi.getProfileImage(user.id, { | ||||||
| @@ -22,18 +24,21 @@ | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| {#await getUserAvatar()} | {#await getUserAvatar()} | ||||||
| 	<div | 	<button | ||||||
|  | 		on:click={() => dispatch('click')} | ||||||
| 		style:width={`${size}px`} | 		style:width={`${size}px`} | ||||||
| 		style:height={`${size}px`} | 		style:height={`${size}px`} | ||||||
| 		class={` rounded-full bg-immich-primary/25`} | 		class={` rounded-full bg-immich-primary/25`} | ||||||
| 	/> | 	/> | ||||||
| {:then data} | {:then data} | ||||||
| 	<img | 	<button on:click={() => dispatch('click')}> | ||||||
| 		src={data} | 		<img | ||||||
| 		alt="profile-img" | 			src={data} | ||||||
| 		style:width={`${size}px`} | 			alt="profile-img" | ||||||
| 		style:height={`${size}px`} | 			style:width={`${size}px`} | ||||||
| 		class={`inline rounded-full  object-cover border shadow-md`} | 			style:height={`${size}px`} | ||||||
| 		title={user.email} | 			class={`inline rounded-full  object-cover border shadow-md`} | ||||||
| 	/> | 			title={user.email} | ||||||
|  | 		/> | ||||||
|  | 	</button> | ||||||
| {/await} | {/await} | ||||||
|   | |||||||
| @@ -1,18 +1,41 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  | 	/** | ||||||
|  | 	 * This is the circle icon component. | ||||||
|  | 	 */ | ||||||
| 	import { createEventDispatcher } from 'svelte'; | 	import { createEventDispatcher } from 'svelte'; | ||||||
|  |  | ||||||
| 	export let logo: any; | 	export let logo: any; | ||||||
| 	export let backgroundColor: string = ''; | 	export let backgroundColor: string = 'transparent'; | ||||||
| 	export let logoColor: string = ''; | 	export let hoverColor: string = '#e2e7e9'; | ||||||
|  | 	export let logoColor: string = '#5f6368'; | ||||||
|  | 	export let size = '24'; | ||||||
|  | 	export let title = ''; | ||||||
|  | 	let iconButton: HTMLButtonElement; | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
|  | 	$: { | ||||||
|  | 		if (iconButton) { | ||||||
|  | 			iconButton.style.backgroundColor = backgroundColor; | ||||||
|  | 			iconButton.style.setProperty('--immich-icon-button-hover-color', hoverColor); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <button | <button | ||||||
| 	class="rounded-full p-3 flex place-items-center place-content-center text-gray-50 hover:bg-gray-800" | 	{title} | ||||||
| 	class:background-color={backgroundColor} | 	bind:this={iconButton} | ||||||
| 	class:color={logoColor} | 	class={`immich-circle-icon-button rounded-full p-3 flex place-items-center place-content-center transition-all`} | ||||||
| 	on:click={() => dispatch('click')} | 	on:click={() => dispatch('click')} | ||||||
| > | > | ||||||
| 	<svelte:component this={logo} size="24" /> | 	<svelte:component this={logo} {size} color={logoColor} /> | ||||||
| </button> | </button> | ||||||
|  |  | ||||||
|  | <style> | ||||||
|  | 	:root { | ||||||
|  | 		--immich-icon-button-hover-color: #d3d3d3; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	.immich-circle-icon-button:hover { | ||||||
|  | 		background-color: var(--immich-icon-button-hover-color) !important; | ||||||
|  | 	} | ||||||
|  | </style> | ||||||
|   | |||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { clickOutside } from '$lib/utils/click-outside'; | ||||||
|  | 	import { createEventDispatcher } from 'svelte'; | ||||||
|  | 	import { quintOut } from 'svelte/easing'; | ||||||
|  | 	import { slide } from 'svelte/transition'; | ||||||
|  |  | ||||||
|  | 	export let x: number = 0; | ||||||
|  | 	export let y: number = 0; | ||||||
|  |  | ||||||
|  | 	const dispatch = createEventDispatcher(); | ||||||
|  | 	let menuEl: HTMLElement; | ||||||
|  |  | ||||||
|  | 	$: (() => { | ||||||
|  | 		if (!menuEl) return; | ||||||
|  |  | ||||||
|  | 		const rect = menuEl.getBoundingClientRect(); | ||||||
|  | 		x = Math.min(window.innerWidth - rect.width, x); | ||||||
|  | 		if (y > window.innerHeight - rect.height) { | ||||||
|  | 			y -= rect.height; | ||||||
|  | 		} | ||||||
|  | 	})(); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div | ||||||
|  | 	transition:slide={{ duration: 200, easing: quintOut }} | ||||||
|  | 	bind:this={menuEl} | ||||||
|  | 	class="absolute bg-white w-[150px] z-[99999] rounded-lg shadow-md" | ||||||
|  | 	style={`top: ${y}px; left: ${x}px;`} | ||||||
|  | 	use:clickOutside | ||||||
|  | 	on:out-click={() => dispatch('clickoutside')} | ||||||
|  | > | ||||||
|  | 	<slot /> | ||||||
|  | </div> | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | <script> | ||||||
|  | 	import { createEventDispatcher } from 'svelte'; | ||||||
|  |  | ||||||
|  | 	export let isDisabled = false; | ||||||
|  | 	export let text = ''; | ||||||
|  |  | ||||||
|  | 	const dispatch = createEventDispatcher(); | ||||||
|  |  | ||||||
|  | 	const handleClick = () => { | ||||||
|  | 		if (isDisabled) return; | ||||||
|  |  | ||||||
|  | 		dispatch('click'); | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <button | ||||||
|  | 	class:disabled={isDisabled} | ||||||
|  | 	on:click={handleClick} | ||||||
|  | 	class="bg-white hover:bg-immich-bg transition-all p-4 w-full text-left rounded-lg" | ||||||
|  | > | ||||||
|  | 	{#if text} | ||||||
|  | 		{text} | ||||||
|  | 	{:else} | ||||||
|  | 		<slot /> | ||||||
|  | 	{/if} | ||||||
|  | </button> | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | const key = {}; | ||||||
|  |  | ||||||
|  | export { key }; | ||||||
| @@ -16,7 +16,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import '../app.css'; | 	import '../app.css'; | ||||||
|  |  | ||||||
| 	import { blur, fade, slide } from 'svelte/transition'; | 	import { fade } from 'svelte/transition'; | ||||||
|  |  | ||||||
| 	import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte'; | 	import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte'; | ||||||
| 	import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte'; | 	import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte'; | ||||||
|   | |||||||
| @@ -44,4 +44,6 @@ | |||||||
| 	<title>{album.albumName} - Immich</title> | 	<title>{album.albumName} - Immich</title> | ||||||
| </svelte:head> | </svelte:head> | ||||||
|  |  | ||||||
| <AlbumViewer {album} /> | <div class="relative immich-scrollbar"> | ||||||
|  | 	<AlbumViewer {album} /> | ||||||
|  | </div> | ||||||
|   | |||||||
| @@ -90,12 +90,12 @@ | |||||||
| 	<NavigationBar {user} on:uploadClicked={() => {}} /> | 	<NavigationBar {user} on:uploadClicked={() => {}} /> | ||||||
| </section> | </section> | ||||||
|  |  | ||||||
| <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg"> | <section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg "> | ||||||
| 	<SideBar /> | 	<SideBar /> | ||||||
|  |  | ||||||
| 	<!-- Main Section --> | 	<!-- Main Section --> | ||||||
|  |  | ||||||
| 	<section class="overflow-y-auto relative"> | 	<section class="overflow-y-auto relative immich-scrollbar"> | ||||||
| 		<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg"> | 		<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg"> | ||||||
| 			<div class="px-4 flex justify-between place-items-center"> | 			<div class="px-4 flex justify-between place-items-center"> | ||||||
| 				<div> | 				<div> | ||||||
|   | |||||||
| @@ -142,7 +142,7 @@ | |||||||
| 	<SideBar /> | 	<SideBar /> | ||||||
|  |  | ||||||
| 	<!-- Main Section --> | 	<!-- Main Section --> | ||||||
| 	<section class="overflow-y-auto relative"> | 	<section class="overflow-y-auto relative immich-scrollbar"> | ||||||
| 		<section id="assets-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg"> | 		<section id="assets-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg"> | ||||||
| 			<section id="image-grid" class="flex flex-wrap gap-14"> | 			<section id="image-grid" class="flex flex-wrap gap-14"> | ||||||
| 				{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} | 				{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user