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 | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./server | ||||
|           file: ./server/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           push: true | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -50,21 +49,20 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./machine-learning | ||||
|           file: ./machine-learning/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64 | ||||
|           push: true | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -78,6 +76,11 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
| @@ -85,15 +88,9 @@ jobs: | ||||
|           file: ./web/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           target: prod | ||||
|           push: true | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -107,18 +104,17 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./nginx | ||||
|           file: ./nginx/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           push: true | ||||
|           tags: | | ||||
|             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 | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./server | ||||
|           file: ./server/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           push: ${{ github.event_name == 'pull_request' }} | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -53,22 +51,20 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./machine-learning | ||||
|           file: ./machine-learning/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64 | ||||
|           push: ${{ github.event_name == 'pull_request' }} | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -82,6 +78,11 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
| @@ -89,16 +90,9 @@ jobs: | ||||
|           file: ./web/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           target: prod | ||||
|           push: ${{ github.event_name == 'pull_request' }} | ||||
|           tags: | | ||||
|             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: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -112,19 +106,17 @@ jobs: | ||||
|       - name: Set up Docker Buildx | ||||
|         id: buildx | ||||
|         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 | ||||
|         uses: docker/build-push-action@v3.1.0 | ||||
|         with: | ||||
|           context: ./nginx | ||||
|           file: ./nginx/Dockerfile | ||||
|           platforms: linux/arm/v7,linux/amd64,linux/arm64 | ||||
|           push: ${{ github.event_name == 'pull_request' }} | ||||
|           tags: | | ||||
|             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: | ||||
| 	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: | ||||
| 	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 { | ||||
| 	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 { | ||||
|   | ||||
| @@ -1,18 +1,30 @@ | ||||
| <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 CircleIconButton from '../shared-components/circle-icon-button.svelte'; | ||||
|  | ||||
| 	export let backIcon = Close; | ||||
| 	let appBarBorder = ''; | ||||
| 	let appBarBorder = 'bg-immich-bg'; | ||||
| 	const dispatch = createEventDispatcher(); | ||||
|  | ||||
| 	onMount(() => { | ||||
| 		window.onscroll = () => { | ||||
| 		if (browser) { | ||||
| 			document.addEventListener('scroll', (e) => { | ||||
| 				if (window.pageYOffset > 80) { | ||||
| 					appBarBorder = 'border border-gray-200 bg-gray-50'; | ||||
| 				} else { | ||||
| 				appBarBorder = ''; | ||||
| 					appBarBorder = 'bg-immich-bg'; | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	onDestroy(() => { | ||||
| 		if (browser) { | ||||
| 			document.removeEventListener('scroll', (e) => {}); | ||||
| 		} | ||||
| 		}; | ||||
| 	}); | ||||
| </script> | ||||
|  | ||||
| @@ -22,17 +34,19 @@ | ||||
| 		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"> | ||||
| 			<button | ||||
| 			<CircleIconButton | ||||
| 				on:click={() => dispatch('close-button-click')} | ||||
| 				id="immich-circle-icon-button" | ||||
| 				class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`} | ||||
| 			> | ||||
| 				<svelte:component this={backIcon} size="24" /> | ||||
| 			</button> | ||||
| 				logo={backIcon} | ||||
| 				backgroundColor={'transparent'} | ||||
| 				logoColor={'rgb(75 85 99)'} | ||||
| 				hoverColor={'#e2e7e9'} | ||||
| 				size={'24'} | ||||
| 			/> | ||||
|  | ||||
| 			<slot name="leading" /> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="flex place-items-center gap-6 mr-4"> | ||||
| 		<div class="flex place-items-center gap-1 mr-4"> | ||||
| 			<slot name="trailing" /> | ||||
| 		</div> | ||||
| 	</div> | ||||
|   | ||||
| @@ -6,15 +6,16 @@ | ||||
| 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; | ||||
| 	import Plus from 'svelte-material-icons/Plus.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 CircleAvatar from '../shared-components/circle-avatar.svelte'; | ||||
| 	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; | ||||
| 	import AssetSelection from './asset-selection.svelte'; | ||||
| 	import _ from 'lodash-es'; | ||||
| 	import { assets } from '$app/paths'; | ||||
| 	import UserSelection from './user-selection-modal.svelte'; | ||||
| 	import AlbumAppBar from './album-app-bar.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(); | ||||
| 	export let album: AlbumResponseDto; | ||||
| @@ -24,6 +25,7 @@ | ||||
| 	let isShowShareUserSelection = false; | ||||
| 	let isEditingTitle = false; | ||||
| 	let isCreatingSharedAlbum = false; | ||||
| 	let isShowShareInfoModal = false; | ||||
|  | ||||
| 	let selectedAsset: AssetResponseDto; | ||||
| 	let currentViewAssetIndex = 0; | ||||
| @@ -34,7 +36,6 @@ | ||||
| 	let backUrl = '/albums'; | ||||
| 	let currentAlbumName = ''; | ||||
| 	let currentUser: UserResponseDto; | ||||
| 	let bodyElement: HTMLElement; | ||||
|  | ||||
| 	$: isOwned = currentUser?.id == album.ownerId; | ||||
|  | ||||
| @@ -70,14 +71,6 @@ | ||||
| 	}; | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		window.onscroll = (event: Event) => { | ||||
| 			if (window.pageYOffset > 80) { | ||||
| 				border = 'border border-gray-200 bg-gray-50'; | ||||
| 			} else { | ||||
| 				border = ''; | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		currentAlbumName = album.albumName; | ||||
|  | ||||
| 		try { | ||||
| @@ -178,28 +171,40 @@ | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	// Prevent scrolling when modal is open | ||||
| 	$: { | ||||
| 		if (isShowShareUserSelection == true) { | ||||
| 			document.body.style.overflow = 'hidden'; | ||||
| 		} else { | ||||
| 			document.body.style.overflow = ''; | ||||
| 	const sharedUserDeletedHandler = async (event: CustomEvent) => { | ||||
| 		const { userId }: { userId: string } = event.detail; | ||||
|  | ||||
| 		if (userId == 'me') { | ||||
| 			isShowShareInfoModal = false; | ||||
| 			goto(backUrl); | ||||
| 		} | ||||
|  | ||||
| 		try { | ||||
| 			const { data } = await api.albumApi.getAlbumInfo(album.id); | ||||
|  | ||||
| 			album = data; | ||||
| 			isShowShareInfoModal = false; | ||||
| 		} catch (e) { | ||||
| 			console.log('Error [sharedUserDeletedHandler] ', e); | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <svelte:body bind:this={bodyElement} /> | ||||
| <section class="bg-immich-bg relative"> | ||||
| <section class="bg-immich-bg"> | ||||
| 	<AlbumAppBar on:close-button-click={() => goto(backUrl)} backIcon={ArrowLeft}> | ||||
| 		<svelte:fragment slot="trailing"> | ||||
| 			{#if album.assets.length > 0} | ||||
| 				<button | ||||
| 					id="immich-circle-icon-button" | ||||
| 					class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`} | ||||
| 				<CircleIconButton | ||||
| 					title="Add Photos" | ||||
| 					on:click={() => (isShowAssetSelection = true)} | ||||
| 				> | ||||
| 					<FileImagePlusOutline size="24" /> | ||||
| 				</button> | ||||
| 					logo={FileImagePlusOutline} | ||||
| 				/> | ||||
|  | ||||
| 				<CircleIconButton | ||||
| 					title="Share" | ||||
| 					on:click={() => (isShowShareUserSelection = true)} | ||||
| 					logo={ShareVariantOutline} | ||||
| 				/> | ||||
| 			{/if} | ||||
|  | ||||
| 			{#if isCreatingSharedAlbum && album.sharedUsers.length == 0} | ||||
| @@ -226,14 +231,14 @@ | ||||
| 		/> | ||||
|  | ||||
| 		{#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 album.shared} | ||||
| 			<div class="my-4 flex"> | ||||
| 			<div class="my-6 flex"> | ||||
| 				{#each album.sharedUsers as user} | ||||
| 					<span class="mr-1"> | ||||
| 						<CircleAvatar {user} /> | ||||
| 						<CircleAvatar {user} on:click={() => (isShowShareInfoModal = true)} /> | ||||
| 					</span> | ||||
| 				{/each} | ||||
|  | ||||
| @@ -248,7 +253,7 @@ | ||||
| 		{/if} | ||||
|  | ||||
| 		{#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} | ||||
| 					{#if album.assets.length < 7} | ||||
| 						<ImmichThumbnail | ||||
| @@ -305,3 +310,11 @@ | ||||
| 		sharedUsersInAlbum={new Set(album.sharedUsers)} | ||||
| 	/> | ||||
| {/if} | ||||
|  | ||||
| {#if isShowShareInfoModal} | ||||
| 	<ShareInfoModal | ||||
| 		on:close={() => (isShowShareInfoModal = false)} | ||||
| 		{album} | ||||
| 		on:user-deleted={sharedUserDeletedHandler} | ||||
| 	/> | ||||
| {/if} | ||||
|   | ||||
| @@ -133,8 +133,8 @@ | ||||
| </script> | ||||
|  | ||||
| <section | ||||
| 	transition:fly={{ y: 1000, duration: 200, easing: quintOut }} | ||||
| 	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[200]" | ||||
| 	transition:fly={{ y: 500, duration: 100, easing: quintOut }} | ||||
| 	class="absolute top-0 left-0 w-full h-full  bg-immich-bg z-[9999]" | ||||
| > | ||||
| 	<AlbumAppBar on:close-button-click={() => dispatch('go-back')}> | ||||
| 		<svelte:fragment slot="leading"> | ||||
| @@ -155,7 +155,7 @@ | ||||
| 		</svelte:fragment> | ||||
| 	</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} | ||||
| 			<!-- Asset Group By Date --> | ||||
| 			<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> | ||||
| 	</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} | ||||
| 			<div class="flex gap-4 py-2 px-5 overflow-x-auto place-items-center mb-2"> | ||||
| 				<p class="font-medium">To</p> | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
| 	import { downloadAssets } from '$lib/stores/download'; | ||||
| 	import VideoViewer from './video-viewer.svelte'; | ||||
| 	import { api, AssetResponseDto, AssetTypeEnum } from '@api'; | ||||
| 	import { browser } from '$app/env'; | ||||
|  | ||||
| 	const dispatch = createEventDispatcher(); | ||||
|  | ||||
| @@ -20,7 +21,9 @@ | ||||
| 	let isShowDetail = false; | ||||
|  | ||||
| 	onMount(() => { | ||||
| 		if (browser) { | ||||
| 			document.addEventListener('keydown', (keyInfo) => handleKeyboardPress(keyInfo.key)); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	const handleKeyboardPress = (key: string) => { | ||||
| @@ -123,7 +126,7 @@ | ||||
|  | ||||
| <section | ||||
| 	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"> | ||||
| 		<AsserViewerNavBar | ||||
|   | ||||
| @@ -1,30 +1,54 @@ | ||||
| <script lang="ts"> | ||||
| 	import { fly } from 'svelte/transition'; | ||||
| 	import { fade } from 'svelte/transition'; | ||||
| 	import { quintOut } from 'svelte/easing'; | ||||
| 	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(); | ||||
| 	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> | ||||
|  | ||||
| <div | ||||
| 	id="immich-modal" | ||||
| 	transition:fly={{ y: 1000, duration: 200, easing: quintOut }} | ||||
| 	class="absolute top-0 w-screen h-screen z-[9999] bg-black/50 flex place-items-center place-content-center" | ||||
| 	style:z-index={zIndex} | ||||
| 	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> | ||||
| 				<slot name="title"> | ||||
| 					<p>Modal Title</p> | ||||
| 				</slot> | ||||
| 			</div> | ||||
| 			<button on:click={() => dispatch('close')}> | ||||
| 				<Close size="24" /> | ||||
| 			</button> | ||||
|  | ||||
| 			<CircleIconButton on:click={() => dispatch('close')} logo={Close} size={'20'} /> | ||||
| 		</div> | ||||
|  | ||||
| 		<div class="mt-4"> | ||||
| 		<div class=""> | ||||
| 			<slot /> | ||||
| 		</div> | ||||
| 	</div> | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| <script lang="ts"> | ||||
| 	import { api, UserResponseDto } from '@api'; | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
|  | ||||
| 	export let user: UserResponseDto; | ||||
|  | ||||
| 	// Avatar Size In Pixel | ||||
| 	export let size: number = 48; | ||||
|  | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 	const getUserAvatar = async () => { | ||||
| 		try { | ||||
| 			const { data } = await api.userApi.getProfileImage(user.id, { | ||||
| @@ -22,12 +24,14 @@ | ||||
| </script> | ||||
|  | ||||
| {#await getUserAvatar()} | ||||
| 	<div | ||||
| 	<button | ||||
| 		on:click={() => dispatch('click')} | ||||
| 		style:width={`${size}px`} | ||||
| 		style:height={`${size}px`} | ||||
| 		class={` rounded-full bg-immich-primary/25`} | ||||
| 	/> | ||||
| {:then data} | ||||
| 	<button on:click={() => dispatch('click')}> | ||||
| 		<img | ||||
| 			src={data} | ||||
| 			alt="profile-img" | ||||
| @@ -36,4 +40,5 @@ | ||||
| 			class={`inline rounded-full  object-cover border shadow-md`} | ||||
| 			title={user.email} | ||||
| 		/> | ||||
| 	</button> | ||||
| {/await} | ||||
|   | ||||
| @@ -1,18 +1,41 @@ | ||||
| <script lang="ts"> | ||||
| 	/** | ||||
| 	 * This is the circle icon component. | ||||
| 	 */ | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
|  | ||||
| 	export let logo: any; | ||||
| 	export let backgroundColor: string = ''; | ||||
| 	export let logoColor: string = ''; | ||||
|  | ||||
| 	export let backgroundColor: string = 'transparent'; | ||||
| 	export let hoverColor: string = '#e2e7e9'; | ||||
| 	export let logoColor: string = '#5f6368'; | ||||
| 	export let size = '24'; | ||||
| 	export let title = ''; | ||||
| 	let iconButton: HTMLButtonElement; | ||||
| 	const dispatch = createEventDispatcher(); | ||||
|  | ||||
| 	$: { | ||||
| 		if (iconButton) { | ||||
| 			iconButton.style.backgroundColor = backgroundColor; | ||||
| 			iconButton.style.setProperty('--immich-icon-button-hover-color', hoverColor); | ||||
| 		} | ||||
| 	} | ||||
| </script> | ||||
|  | ||||
| <button | ||||
| 	class="rounded-full p-3 flex place-items-center place-content-center text-gray-50 hover:bg-gray-800" | ||||
| 	class:background-color={backgroundColor} | ||||
| 	class:color={logoColor} | ||||
| 	{title} | ||||
| 	bind:this={iconButton} | ||||
| 	class={`immich-circle-icon-button rounded-full p-3 flex place-items-center place-content-center transition-all`} | ||||
| 	on:click={() => dispatch('click')} | ||||
| > | ||||
| 	<svelte:component this={logo} size="24" /> | ||||
| 	<svelte:component this={logo} {size} color={logoColor} /> | ||||
| </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"> | ||||
| 	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 AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte'; | ||||
|   | ||||
| @@ -44,4 +44,6 @@ | ||||
| 	<title>{album.albumName} - Immich</title> | ||||
| </svelte:head> | ||||
|  | ||||
| <AlbumViewer {album} /> | ||||
| <div class="relative immich-scrollbar"> | ||||
| 	<AlbumViewer {album} /> | ||||
| </div> | ||||
|   | ||||
| @@ -90,12 +90,12 @@ | ||||
| 	<NavigationBar {user} on:uploadClicked={() => {}} /> | ||||
| </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 /> | ||||
|  | ||||
| 	<!-- 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"> | ||||
| 			<div class="px-4 flex justify-between place-items-center"> | ||||
| 				<div> | ||||
|   | ||||
| @@ -142,7 +142,7 @@ | ||||
| 	<SideBar /> | ||||
|  | ||||
| 	<!-- 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="image-grid" class="flex flex-wrap gap-14"> | ||||
| 				{#each $assetsGroupByDate as assetsInDateGroup, groupIndex} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user