mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05: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:
		
							parent
							
								
									6021124688
								
							
						
					
					
						commit
						3b97c7729b
					
				
							
								
								
									
										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}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user