mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(web): add asset and album count info (#623)
* Get asset and album count * Generate APIs * Added asset count for each type * Added api on the web * Added info button for asset and album count to trigger getting info on hover * Remove websocket event from photo page
This commit is contained in:
@@ -84,6 +84,31 @@ export interface AdminSignupResponseDto {
|
||||
*/
|
||||
'createdAt': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AlbumCountResponseDto
|
||||
*/
|
||||
export interface AlbumCountResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AlbumCountResponseDto
|
||||
*/
|
||||
'owned': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AlbumCountResponseDto
|
||||
*/
|
||||
'shared': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AlbumCountResponseDto
|
||||
*/
|
||||
'sharing': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -183,6 +208,25 @@ export interface AssetCountByTimeBucketResponseDto {
|
||||
*/
|
||||
'buckets': Array<AssetCountByTimeBucket>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AssetCountByUserIdResponseDto
|
||||
*/
|
||||
export interface AssetCountByUserIdResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetCountByUserIdResponseDto
|
||||
*/
|
||||
'photos': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetCountByUserIdResponseDto
|
||||
*/
|
||||
'videos': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -1408,6 +1452,39 @@ export const AlbumApiAxiosParamCreator = 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 {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAlbumCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/album/count-by-user-id`;
|
||||
// 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;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@@ -1676,6 +1753,15 @@ export const AlbumApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(albumId, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getAlbumCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumCountResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumCountByUserId(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
@@ -1778,6 +1864,14 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath
|
||||
deleteAlbum(albumId: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.deleteAlbum(albumId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAlbumCountByUserId(options?: any): AxiosPromise<AlbumCountResponseDto> {
|
||||
return localVarFp.getAlbumCountByUserId(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
@@ -1883,6 +1977,16 @@ export class AlbumApi extends BaseAPI {
|
||||
return AlbumApiFp(this.configuration).deleteAlbum(albumId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AlbumApi
|
||||
*/
|
||||
public getAlbumCountByUserId(options?: AxiosRequestConfig) {
|
||||
return AlbumApiFp(this.configuration).getAlbumCountByUserId(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} albumId
|
||||
@@ -2236,6 +2340,39 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/count-by-user-id`;
|
||||
// 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;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@@ -2640,6 +2777,15 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetCountByUserIdResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByUserId(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@@ -2800,6 +2946,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||
getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise<AssetCountByTimeBucketResponseDto> {
|
||||
return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAssetCountByUserId(options?: any): AxiosPromise<AssetCountByUserIdResponseDto> {
|
||||
return localVarFp.getAssetCountByUserId(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
@@ -2966,6 +3120,16 @@ export class AssetApi extends BaseAPI {
|
||||
return AssetApiFp(this.configuration).getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public getAssetCountByUserId(options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getAssetCountByUserId(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte';
|
||||
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
|
||||
import SideBarButton from './side-bar-button.svelte';
|
||||
import StatusBox from '../status-box.svelte';
|
||||
import { AlbumCountResponseDto, api, AssetCountByUserIdResponseDto } from '@api';
|
||||
import { fade } from 'svelte/transition';
|
||||
import LoadingSpinner from '../loading-spinner.svelte';
|
||||
|
||||
let selectedAction: AppSideBarSelection;
|
||||
|
||||
let showAssetCount: boolean = false;
|
||||
let showSharingCount = false;
|
||||
let showAlbumsCount = false;
|
||||
|
||||
onMount(async () => {
|
||||
if ($page.routeId == 'albums') {
|
||||
selectedAction = AppSideBarSelection.ALBUMS;
|
||||
@@ -19,35 +27,130 @@
|
||||
selectedAction = AppSideBarSelection.SHARING;
|
||||
}
|
||||
});
|
||||
|
||||
const getAssetCount = async () => {
|
||||
const { data: assetCount } = await api.assetApi.getAssetCountByUserId();
|
||||
|
||||
return {
|
||||
videos: assetCount.videos,
|
||||
photos: assetCount.photos
|
||||
};
|
||||
};
|
||||
|
||||
const getAlbumCount = async () => {
|
||||
const { data: albumCount } = await api.albumApi.getAlbumCountByUserId();
|
||||
return {
|
||||
shared: albumCount.shared,
|
||||
sharing: albumCount.sharing,
|
||||
owned: albumCount.owned
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
||||
<a sveltekit:prefetch sveltekit:noscroll href={$page.routeId !== 'photos' ? `/photos` : null}>
|
||||
<a
|
||||
sveltekit:prefetch
|
||||
sveltekit:noscroll
|
||||
href={$page.routeId !== 'photos' ? `/photos` : null}
|
||||
class="relative"
|
||||
>
|
||||
<SideBarButton
|
||||
title="Photos"
|
||||
title={`Photos`}
|
||||
logo={ImageOutline}
|
||||
actionType={AppSideBarSelection.PHOTOS}
|
||||
isSelected={selectedAction === AppSideBarSelection.PHOTOS}
|
||||
/></a
|
||||
>
|
||||
<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null}>
|
||||
/>
|
||||
<div
|
||||
id="asset-count-info"
|
||||
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||
on:mouseenter={() => (showAssetCount = true)}
|
||||
on:mouseleave={() => (showAssetCount = false)}
|
||||
>
|
||||
<InformationOutline size={18} color="#4250af" />
|
||||
{#if showAssetCount}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
id="asset-count-info-detail"
|
||||
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||
>
|
||||
{#await getAssetCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.videos} Videos</p>
|
||||
<p>{data.photos} Photos</p>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null} class="relative">
|
||||
<SideBarButton
|
||||
title="Sharing"
|
||||
logo={AccountMultipleOutline}
|
||||
actionType={AppSideBarSelection.SHARING}
|
||||
isSelected={selectedAction === AppSideBarSelection.SHARING}
|
||||
/></a
|
||||
>
|
||||
/>
|
||||
<div
|
||||
id="sharing-count-info"
|
||||
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||
on:mouseenter={() => (showSharingCount = true)}
|
||||
on:mouseleave={() => (showSharingCount = false)}
|
||||
>
|
||||
<InformationOutline size={18} color="#4250af" />
|
||||
{#if showSharingCount}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
id="asset-count-info-detail"
|
||||
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||
>
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.shared + data.sharing} albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
<div class="text-xs ml-5 my-4">
|
||||
<p>LIBRARY</p>
|
||||
</div>
|
||||
<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null}>
|
||||
<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null} class="relative">
|
||||
<SideBarButton
|
||||
title="Albums"
|
||||
logo={ImageAlbum}
|
||||
actionType={AppSideBarSelection.ALBUMS}
|
||||
isSelected={selectedAction === AppSideBarSelection.ALBUMS}
|
||||
/>
|
||||
|
||||
<div
|
||||
id="album-count-info"
|
||||
class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help"
|
||||
on:mouseenter={() => (showAlbumsCount = true)}
|
||||
on:mouseleave={() => (showAlbumsCount = false)}
|
||||
>
|
||||
<InformationOutline size={18} color="#4250af" />
|
||||
{#if showAlbumsCount}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
id="asset-count-info-detail"
|
||||
class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center"
|
||||
>
|
||||
{#await getAlbumCount()}
|
||||
<LoadingSpinner />
|
||||
{:then data}
|
||||
<div>
|
||||
<p>{data.owned} albums</p>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Status Box -->
|
||||
|
||||
@@ -26,14 +26,6 @@
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
onMount(async () => {
|
||||
openWebsocketConnection();
|
||||
|
||||
return () => {
|
||||
closeWebsocketConnection();
|
||||
};
|
||||
});
|
||||
|
||||
const deleteSelectedAssetHandler = async () => {
|
||||
try {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user