mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
Added sharing page to web (#355)
* Added shared album * Added list tile * Show info of shared album owner
This commit is contained in:
parent
5d03e9bda8
commit
c6ecfb679a
@ -99,4 +99,3 @@ lib/model/user_count_response_dto.dart
|
|||||||
lib/model/user_response_dto.dart
|
lib/model/user_response_dto.dart
|
||||||
lib/model/validate_access_token_response_dto.dart
|
lib/model/validate_access_token_response_dto.dart
|
||||||
pubspec.yaml
|
pubspec.yaml
|
||||||
test/thumbnail_format_test.dart
|
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -45,6 +45,11 @@ export class UserController {
|
|||||||
return await this.userService.getAllUsers(authUser, isAll);
|
return await this.userService.getAllUsers(authUser, isAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/:userId')
|
||||||
|
async getUserById(@Param('userId') userId: string): Promise<UserResponseDto> {
|
||||||
|
return await this.userService.getUserById(userId);
|
||||||
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Get('me')
|
@Get('me')
|
||||||
|
@ -38,6 +38,14 @@ export class UserService {
|
|||||||
return allUserExceptRequestedUser.map(mapUser);
|
return allUserExceptRequestedUser.map(mapUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserById(userId: string): Promise<UserResponseDto> {
|
||||||
|
const user = await this.userRepository.get(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapUser(user);
|
||||||
|
}
|
||||||
async getUserInfo(authUser: AuthUserDto): Promise<UserResponseDto> {
|
async getUserInfo(authUser: AuthUserDto): Promise<UserResponseDto> {
|
||||||
const user = await this.userRepository.get(authUser.id);
|
const user = await this.userRepository.get(authUser.id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -94,7 +102,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.userRepository.createProfileImage(user, fileInfo)
|
await this.userRepository.createProfileImage(user, fileInfo);
|
||||||
|
|
||||||
return mapCreateProfileImageResponse(authUser.id, fileInfo.path);
|
return mapCreateProfileImageResponse(authUser.id, fileInfo.path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -3605,6 +3605,39 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getUserById: async (userId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'userId' is not null or undefined
|
||||||
|
assertParamExists('getUserById', 'userId', userId)
|
||||||
|
const localVarPath = `/user/{userId}`
|
||||||
|
.replace(`{${"userId"}}`, encodeURIComponent(String(userId)));
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
@ -3741,6 +3774,16 @@ export const UserApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(userId, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getProfileImage(userId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getUserById(userId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getUserById(userId, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@ -3814,6 +3857,15 @@ export const UserApiFactory = function (configuration?: Configuration, basePath?
|
|||||||
getProfileImage(userId: string, options?: any): AxiosPromise<object> {
|
getProfileImage(userId: string, options?: any): AxiosPromise<object> {
|
||||||
return localVarFp.getProfileImage(userId, options).then((request) => request(axios, basePath));
|
return localVarFp.getProfileImage(userId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getUserById(userId: string, options?: any): AxiosPromise<UserResponseDto> {
|
||||||
|
return localVarFp.getUserById(userId, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
@ -3895,6 +3947,17 @@ export class UserApi extends BaseAPI {
|
|||||||
return UserApiFp(this.configuration).getProfileImage(userId, options).then((request) => request(this.axios, this.basePath));
|
return UserApiFp(this.configuration).getProfileImage(userId, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof UserApi
|
||||||
|
*/
|
||||||
|
public getUserById(userId: string, options?: AxiosRequestConfig) {
|
||||||
|
return UserApiFp(this.configuration).getUserById(userId, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { afterNavigate } from '$app/navigation';
|
||||||
|
|
||||||
import { AlbumResponseDto, ThumbnailFormat } from '@api';
|
import { AlbumResponseDto, ThumbnailFormat } from '@api';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
||||||
import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
|
import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
|
||||||
import CircleAvatar from '../shared/circle-avatar.svelte';
|
import CircleAvatar from '../shared-components/circle-avatar.svelte';
|
||||||
import ImmichThumbnail from '../shared/immich-thumbnail.svelte';
|
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
let viewWidth: number;
|
let viewWidth: number;
|
||||||
let thumbnailSize: number = 300;
|
let thumbnailSize: number = 300;
|
||||||
let border = '';
|
let border = '';
|
||||||
|
let backUrl = '/albums';
|
||||||
|
|
||||||
|
afterNavigate(({ from, to }) => {
|
||||||
|
backUrl = from?.pathname ?? '/albums';
|
||||||
|
});
|
||||||
$: {
|
$: {
|
||||||
if (album.assets.length < 6) {
|
if (album.assets.length < 6) {
|
||||||
thumbnailSize = Math.floor(viewWidth / album.assets.length - album.assets.length);
|
thumbnailSize = Math.floor(viewWidth / album.assets.length - album.assets.length);
|
||||||
@ -51,7 +57,7 @@
|
|||||||
<section class="w-screen h-screen bg-immich-bg">
|
<section class="w-screen h-screen bg-immich-bg">
|
||||||
<div class="fixed top-0 w-full bg-immich-bg z-[100]">
|
<div class="fixed top-0 w-full bg-immich-bg z-[100]">
|
||||||
<div class={`flex justify-between rounded-lg ${border} p-2 mx-2 mt-2 transition-all`}>
|
<div class={`flex justify-between rounded-lg ${border} p-2 mx-2 mt-2 transition-all`}>
|
||||||
<a sveltekit:prefetch href="/albums" title="Go Back">
|
<a sveltekit:prefetch href={backUrl} title="Go Back">
|
||||||
<button
|
<button
|
||||||
id="immich-circle-icon-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`}
|
class={`rounded-full p-3 flex place-items-center place-content-center text-gray-600 transition-all hover:bg-gray-200`}
|
@ -6,11 +6,13 @@
|
|||||||
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
|
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
|
||||||
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
|
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
|
||||||
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
||||||
import CircleIconButton from '../shared/circle-icon-button.svelte';
|
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-16 bg-black/5 flex justify-between place-items-center px-3 transition-transform duration-200 z-[9999]">
|
<div
|
||||||
|
class="h-16 bg-black/5 flex justify-between place-items-center px-3 transition-transform duration-200 z-[9999]"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<CircleIconButton logo={ArrowLeft} on:click={() => dispatch('goBack')} />
|
<CircleIconButton logo={ArrowLeft} on:click={() => dispatch('goBack')} />
|
||||||
</div>
|
</div>
|
@ -3,7 +3,7 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { api, AssetResponseDto } from '@api';
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
@ -23,9 +23,15 @@
|
|||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.serveFile(assetInfo.deviceAssetId, deviceId, false, true, {
|
const { data } = await api.assetApi.serveFile(
|
||||||
responseType: 'blob',
|
assetInfo.deviceAssetId,
|
||||||
});
|
deviceId,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
responseType: 'blob'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!(data instanceof Blob)) {
|
if (!(data instanceof Blob)) {
|
||||||
return;
|
return;
|
||||||
@ -38,7 +44,10 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div transition:fade={{ duration: 150 }} class="flex place-items-center place-content-center h-full select-none">
|
<div
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
class="flex place-items-center place-content-center h-full select-none"
|
||||||
|
>
|
||||||
{#if assetInfo}
|
{#if assetInfo}
|
||||||
{#await loadAssetData()}
|
{#await loadAssetData()}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
@ -3,7 +3,7 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import LoadingSpinner from '../shared/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { api, AssetResponseDto } from '@api';
|
import { api, AssetResponseDto } from '@api';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
@ -30,9 +30,15 @@
|
|||||||
|
|
||||||
if ($session.user) {
|
if ($session.user) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.serveFile(asset.deviceAssetId, asset.deviceId, false, true, {
|
const { data } = await api.assetApi.serveFile(
|
||||||
responseType: 'blob',
|
asset.deviceAssetId,
|
||||||
});
|
asset.deviceId,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
responseType: 'blob'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!(data instanceof Blob)) {
|
if (!(data instanceof Blob)) {
|
||||||
return;
|
return;
|
||||||
@ -57,7 +63,10 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div transition:fade={{ duration: 150 }} class="flex place-items-center place-content-center h-full select-none">
|
<div
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
class="flex place-items-center place-content-center h-full select-none"
|
||||||
|
>
|
||||||
{#if asset}
|
{#if asset}
|
||||||
<video controls class="h-full object-contain" bind:this={videoPlayerNode}>
|
<video controls class="h-full object-contain" bind:this={videoPlayerNode}>
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
@ -2,7 +2,7 @@
|
|||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import { fade, fly } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '$lib/components/asset-viewer-page/intersection-observer.svelte';
|
||||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||||
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
|
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
|
||||||
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
|
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
|
@ -6,37 +6,24 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
||||||
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
||||||
|
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||||
import SideBarButton from './side-bar-button.svelte';
|
import SideBarButton from './side-bar-button.svelte';
|
||||||
import StatusBox from '../status-box.svelte';
|
import StatusBox from '../status-box.svelte';
|
||||||
|
|
||||||
let selectedAction: AppSideBarSelection;
|
let selectedAction: AppSideBarSelection;
|
||||||
|
|
||||||
const onSidebarButtonClicked = (buttonType: CustomEvent) => {
|
|
||||||
selectedAction = buttonType.detail['actionType'] as AppSideBarSelection;
|
|
||||||
|
|
||||||
if (selectedAction == AppSideBarSelection.PHOTOS) {
|
|
||||||
if ($page.routeId != 'photos') {
|
|
||||||
goto('/photos');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedAction == AppSideBarSelection.ALBUMS) {
|
|
||||||
if ($page.routeId != 'albums') {
|
|
||||||
goto('/albums');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($page.routeId == 'albums') {
|
if ($page.routeId == 'albums') {
|
||||||
selectedAction = AppSideBarSelection.ALBUMS;
|
selectedAction = AppSideBarSelection.ALBUMS;
|
||||||
} else if ($page.routeId == 'photos') {
|
} else if ($page.routeId == 'photos') {
|
||||||
selectedAction = AppSideBarSelection.PHOTOS;
|
selectedAction = AppSideBarSelection.PHOTOS;
|
||||||
|
} else if ($page.routeId == 'sharing') {
|
||||||
|
selectedAction = AppSideBarSelection.SHARING;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="sidebar" class="flex flex-col gap-4 pt-8 pr-6">
|
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
||||||
<a sveltekit:prefetch href={$page.routeId != 'photos' ? `/photos` : null}>
|
<a sveltekit:prefetch href={$page.routeId != 'photos' ? `/photos` : null}>
|
||||||
<SideBarButton
|
<SideBarButton
|
||||||
title="Photos"
|
title="Photos"
|
||||||
@ -45,8 +32,15 @@
|
|||||||
isSelected={selectedAction === AppSideBarSelection.PHOTOS}
|
isSelected={selectedAction === AppSideBarSelection.PHOTOS}
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
|
<a sveltekit:prefetch href={$page.routeId != 'sharing' ? `/sharing` : null}>
|
||||||
<div class="text-xs ml-5">
|
<SideBarButton
|
||||||
|
title="Sharing"
|
||||||
|
logo={AccountMultipleOutline}
|
||||||
|
actionType={AppSideBarSelection.SHARING}
|
||||||
|
isSelected={selectedAction === AppSideBarSelection.SHARING}
|
||||||
|
/></a
|
||||||
|
>
|
||||||
|
<div class="text-xs ml-5 my-4">
|
||||||
<p>LIBRARY</p>
|
<p>LIBRARY</p>
|
||||||
</div>
|
</div>
|
||||||
<a sveltekit:prefetch href={$page.routeId != 'albums' ? `/albums` : null}>
|
<a sveltekit:prefetch href={$page.routeId != 'albums' ? `/albums` : null}>
|
@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
export let album: AlbumResponseDto;
|
||||||
|
export let user: UserResponseDto;
|
||||||
|
|
||||||
|
const loadImageData = async (thubmnailId: string | null) => {
|
||||||
|
if (thubmnailId == null) {
|
||||||
|
return '/no-thumbnail.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId!, ThumbnailFormat.Webp, {
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
return URL.createObjectURL(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlbumOwnerInfo = async (): Promise<UserResponseDto> => {
|
||||||
|
const { data } = await api.userApi.getUserById(album.ownerId);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex min-w-[550px] border-b border-gray-300 place-items-center py-4 gap-6 transition-all hover:border-immich-primary"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{#await loadImageData(album.albumThumbnailAssetId)}
|
||||||
|
<div
|
||||||
|
class={`bg-immich-primary/10 w-[75px] h-[75px] flex place-items-center place-content-center rounded-xl`}
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
{:then imageData}
|
||||||
|
<img
|
||||||
|
in:fade={{ duration: 250 }}
|
||||||
|
src={imageData}
|
||||||
|
alt={album.id}
|
||||||
|
class={`object-cover w-[75px] h-[75px] transition-all z-0 rounded-xl duration-300 `}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p class="font-medium text-gray-800">{album.albumName}</p>
|
||||||
|
|
||||||
|
{#await getAlbumOwnerInfo() then albumOwner}
|
||||||
|
{#if user.email == albumOwner.email}
|
||||||
|
<p class="text-xs text-gray-600">Owned</p>
|
||||||
|
{:else}
|
||||||
|
<p class="text-xs text-gray-600">Shared by {albumOwner.email}</p>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,9 +1,10 @@
|
|||||||
export enum AdminSideBarSelection {
|
export enum AdminSideBarSelection {
|
||||||
USER_MANAGEMENT = 'User management',
|
USER_MANAGEMENT = 'User management'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppSideBarSelection {
|
export enum AppSideBarSelection {
|
||||||
PHOTOS = 'Photos',
|
PHOTOS = 'Photos',
|
||||||
EXPLORE = 'Explore',
|
EXPLORE = 'Explore',
|
||||||
ALBUMS = 'Albums',
|
ALBUMS = 'Albums',
|
||||||
|
SHARING = 'Sharing'
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ export const flattenAssetGroupByDate = derived(assetsGroupByDate, ($assetsGroupB
|
|||||||
return $assetsGroupByDate.flat();
|
return $assetsGroupByDate.flat();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAssetsInfo = async (accessToken: string) => {
|
export const getAssetsInfo = async () => {
|
||||||
const { data } = await api.assetApi.getAllAssets();
|
const { data } = await api.assetApi.getAllAssets();
|
||||||
assets.set(data);
|
assets.set(data);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { url },
|
props: { url }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
import { blur, fade, slide } from 'svelte/transition';
|
import { blur, fade, slide } from 'svelte/transition';
|
||||||
|
|
||||||
import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
|
import DownloadPanel from '$lib/components/asset-viewer-page/download-panel.svelte';
|
||||||
import AnnouncementBox from '$lib/components/shared/announcement-box.svelte';
|
import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte';
|
||||||
import UploadPanel from '$lib/components/shared/upload-panel.svelte';
|
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
|
|
||||||
@ -45,7 +45,11 @@
|
|||||||
<DownloadPanel />
|
<DownloadPanel />
|
||||||
<UploadPanel />
|
<UploadPanel />
|
||||||
{#if shouldShowAnnouncement}
|
{#if shouldShowAnnouncement}
|
||||||
<AnnouncementBox {localVersion} {remoteVersion} on:close={() => (shouldShowAnnouncement = false)} />
|
<AnnouncementBox
|
||||||
|
{localVersion}
|
||||||
|
{remoteVersion}
|
||||||
|
on:close={() => (shouldShowAnnouncement = false)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
if (!session.user) {
|
if (!session.user) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
redirect: '/auth/login',
|
redirect: '/auth/login'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,8 +16,8 @@
|
|||||||
status: 200,
|
status: 200,
|
||||||
props: {
|
props: {
|
||||||
user: session.user,
|
user: session.user,
|
||||||
allUsers: data,
|
allUsers: data
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -27,13 +27,13 @@
|
|||||||
|
|
||||||
import type { ImmichUser } from '$lib/models/immich-user';
|
import type { ImmichUser } from '$lib/models/immich-user';
|
||||||
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||||
import SideBarButton from '$lib/components/shared/side-bar/side-bar-button.svelte';
|
import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
|
||||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||||
import NavigationBar from '$lib/components/shared/navigation-bar.svelte';
|
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||||
import UserManagement from '$lib/components/admin/user-management.svelte';
|
import UserManagement from '$lib/components/admin-page/user-management.svelte';
|
||||||
import FullScreenModal from '$lib/components/shared/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
||||||
import StatusBox from '$lib/components/shared/status-box.svelte';
|
import StatusBox from '$lib/components/shared-components/status-box.svelte';
|
||||||
|
|
||||||
let selectedAction: AdminSideBarSelection;
|
let selectedAction: AdminSideBarSelection;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import AlbumViewer from '$lib/components/album/album-viewer.svelte';
|
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
||||||
|
|
||||||
import NavigationBar from '$lib/components/shared/navigation-bar.svelte';
|
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||||
import { ImmichUser } from '$lib/models/immich-user';
|
import { ImmichUser } from '$lib/models/immich-user';
|
||||||
import type { Load } from '@sveltejs/kit';
|
import type { Load } from '@sveltejs/kit';
|
||||||
import SideBar from '$lib/components/shared/side-bar/side-bar.svelte';
|
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||||
import { AlbumResponseDto, api } from '@api';
|
import { AlbumResponseDto, api } from '@api';
|
||||||
|
|
||||||
export const load: Load = async ({ session }) => {
|
export const load: Load = async ({ session }) => {
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AlbumCard from '$lib/components/album/album-card.svelte';
|
import AlbumCard from '$lib/components/album-page/album-card.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let user: ImmichUser;
|
export let user: ImmichUser;
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<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>
|
||||||
<p>Albums</p>
|
<p class="font-medium">Albums</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await getAssetsInfo(session.user.accessToken);
|
await getAssetsInfo();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -26,17 +26,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ImmichUser } from '$lib/models/immich-user';
|
import type { ImmichUser } from '$lib/models/immich-user';
|
||||||
|
|
||||||
import NavigationBar from '$lib/components/shared/navigation-bar.svelte';
|
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
|
import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
|
||||||
import ImmichThumbnail from '$lib/components/shared/immich-thumbnail.svelte';
|
import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '$lib/components/asset-viewer-page/asset-viewer.svelte';
|
||||||
import { fileUploader } from '$lib/utils/file-uploader';
|
import { fileUploader } from '$lib/utils/file-uploader';
|
||||||
import { AssetResponseDto } from '@api';
|
import { AssetResponseDto } from '@api';
|
||||||
import SideBar from '$lib/components/shared/side-bar/side-bar.svelte';
|
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||||
|
|
||||||
export let user: ImmichUser;
|
export let user: ImmichUser;
|
||||||
|
|
||||||
|
89
web/src/routes/sharing/index.svelte
Normal file
89
web/src/routes/sharing/index.svelte
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
export const prerender = false;
|
||||||
|
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
import { AlbumResponseDto, api, UserResponseDto } from '@api';
|
||||||
|
|
||||||
|
export const load: Load = async ({ session }) => {
|
||||||
|
if (!session.user) {
|
||||||
|
return {
|
||||||
|
status: 302,
|
||||||
|
redirect: '/auth/login'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let sharedAlbums: AlbumResponseDto[] = [];
|
||||||
|
try {
|
||||||
|
const { data } = await api.albumApi.getAllAlbums(true);
|
||||||
|
sharedAlbums = data;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error [getAllAlbums] ', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
props: {
|
||||||
|
user: session.user,
|
||||||
|
sharedAlbums: sharedAlbums
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||||
|
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||||
|
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
||||||
|
import AlbumCard from '$lib/components/album-page/album-card.svelte';
|
||||||
|
import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
|
||||||
|
|
||||||
|
export let user: UserResponseDto;
|
||||||
|
export let sharedAlbums: AlbumResponseDto[];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Albums - Immich</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<NavigationBar {user} on:uploadClicked={() => {}} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
|
||||||
|
<SideBar />
|
||||||
|
|
||||||
|
<section class="overflow-y-auto relative">
|
||||||
|
<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
|
||||||
|
<!-- Main Section -->
|
||||||
|
<div class="px-4 flex justify-between place-items-center">
|
||||||
|
<div>
|
||||||
|
<p class="font-medium">Sharing</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<PlusBoxOutline size="18" />
|
||||||
|
</span>
|
||||||
|
<p>Create shared album</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Share Album List -->
|
||||||
|
<div class="w-full flex flex-col place-items-center">
|
||||||
|
{#each sharedAlbums as album}
|
||||||
|
<a sveltekit:prefetch href={`albums/${album.id}`}>
|
||||||
|
<SharedAlbumListTile {album} {user} /></a
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</section>
|
Loading…
Reference in New Issue
Block a user