From 6c8a1a4c394b424db2440240c10839fd5632364e Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Aug 2022 23:30:33 -0700 Subject: [PATCH 01/39] calculate total estimated length of viewport --- web/src/api/utils.ts | 31 ++- web/src/routes/photos/+page.server.ts | 8 +- web/src/routes/photos/+page.svelte | 74 ++----- web/src/routes/photos/page.bak.svelte | 304 ++++++++++++++++++++++++++ 4 files changed, 352 insertions(+), 65 deletions(-) create mode 100644 web/src/routes/photos/page.bak.svelte diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index e67896ad70cd5..75a8f6a89b6b0 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -1,12 +1,27 @@ +import { AssetCountByTimeGroupResponseDto } from '@api'; let _basePath = '/api'; export function getFileUrl(aid: string, did: string, isThumb?: boolean, isWeb?: boolean) { - const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file`); - - urlObj.searchParams.append('aid', aid); - urlObj.searchParams.append('did', did); - if (isThumb !== undefined && isThumb !== null) urlObj.searchParams.append('isThumb', `${isThumb}`); - if (isWeb !== undefined && isWeb !== null) urlObj.searchParams.append('isWeb', `${isWeb}`); - - return urlObj.href; + const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file`); + + urlObj.searchParams.append('aid', aid); + urlObj.searchParams.append('did', did); + if (isThumb !== undefined && isThumb !== null) + urlObj.searchParams.append('isThumb', `${isThumb}`); + if (isWeb !== undefined && isWeb !== null) urlObj.searchParams.append('isWeb', `${isWeb}`); + + return urlObj.href; +} + +export function calculateTimeLineTotalHeight( + assetCount: AssetCountByTimeGroupResponseDto, + viewportWidth: number +) { + const targetHeight = 235; + + const unwrappedWidth = (3 / 2) * assetCount.totalAssets * targetHeight * (7 / 10); + const rows = Math.ceil(unwrappedWidth / viewportWidth); + const height = rows * targetHeight; + + return height; } diff --git a/web/src/routes/photos/+page.server.ts b/web/src/routes/photos/+page.server.ts index 79fb1cb10576f..7d40010f188e5 100644 --- a/web/src/routes/photos/+page.server.ts +++ b/web/src/routes/photos/+page.server.ts @@ -1,6 +1,6 @@ -import { serverApi } from './../../api/api'; import type { PageServerLoad } from './$types'; import { redirect, error } from '@sveltejs/kit'; +import { calculateTimeLineTotalHeight, serverApi, TimeGroupEnum } from '@api'; export const load: PageServerLoad = async ({ parent }) => { try { @@ -9,11 +9,13 @@ export const load: PageServerLoad = async ({ parent }) => { throw error(400, 'Not logged in'); } - const { data: assets } = await serverApi.assetApi.getAllAssets(); + const { data: assetCountByTimeGroup } = await serverApi.assetApi.getAssetCountByTimeGroup({ + timeGroup: TimeGroupEnum.Month + }); return { user, - assets + assetCountByTimeGroup }; } catch (e) { throw redirect(302, '/auth/login'); diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index aca0447edc0ae..34b481ae87f81 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -12,7 +12,7 @@ import moment from 'moment'; import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; - import { api, AssetResponseDto } from '@api'; + import { api, AssetResponseDto, calculateTimeLineTotalHeight } from '@api'; import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; @@ -45,10 +45,18 @@ let isShowAssetViewer = false; let currentViewAssetIndex = 0; + let timelineViewPortWidth: number; + let estimatedTotalHeight: number; let selectedAsset: AssetResponseDto; onMount(() => { - setAssetInfo(data.assets); + estimatedTotalHeight = calculateTimeLineTotalHeight( + data.assetCountByTimeGroup, + timelineViewPortWidth + ); + + console.log(estimatedTotalHeight); + // setAssetInfo(data.assets); }); const thumbnailMouseEventHandler = (event: CustomEvent) => { @@ -244,60 +252,18 @@
-
-
-
- {#each $assetsGroupByDate as assetsInDateGroup, groupIndex} - -
(isMouseOverGroup = true)} - on:mouseleave={() => (isMouseOverGroup = false)} - > - -

- {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} -

selectAssetGroupHandler(groupIndex)} - > - {#if selectedGroup.has(groupIndex)} - - {:else if existingGroup.has(groupIndex)} - - {:else} - - {/if} -
- {/if} - - {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} -

- - -
- {#each assetsInDateGroup as asset} - {#key asset.id} - - isMultiSelectionMode - ? selectAssetHandler(asset, groupIndex) - : viewAssetHandler(event)} - on:select={() => selectAssetHandler(asset, groupIndex)} - selected={multiSelectedAssets.has(asset)} - {groupIndex} - /> - {/key} - {/each} -
-
+
+
+ {#each new Array(data.assetCountByTimeGroup.totalAssets) as data, i} +
{i}
{/each} -
+
diff --git a/web/src/routes/photos/page.bak.svelte b/web/src/routes/photos/page.bak.svelte new file mode 100644 index 0000000000000..e3656b4b19e10 --- /dev/null +++ b/web/src/routes/photos/page.bak.svelte @@ -0,0 +1,304 @@ + + + + Photos - Immich + + +
+ {#if isMultiSelectionMode} + + +

Selected {multiSelectedAssets.size}

+
+ + + +
+ {/if} + + {#if !isMultiSelectionMode} + openFileUploadDialog(UploadType.GENERAL)} + /> + {/if} +
+ +
+ + +
+
+
+ {#each $assetsGroupByDate as assetsInDateGroup, groupIndex} + +
(isMouseOverGroup = true)} + on:mouseleave={() => (isMouseOverGroup = false)} + > + +

+ {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} +

selectAssetGroupHandler(groupIndex)} + > + {#if selectedGroup.has(groupIndex)} + + {:else if existingGroup.has(groupIndex)} + + {:else} + + {/if} +
+ {/if} + + {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} +

+ + +
+ {#each assetsInDateGroup as asset} + {#key asset.id} + + isMultiSelectionMode + ? selectAssetHandler(asset, groupIndex) + : viewAssetHandler(event)} + on:select={() => selectAssetHandler(asset, groupIndex)} + selected={multiSelectedAssets.has(asset)} + {groupIndex} + /> + {/key} + {/each} +
+
+ {/each} +
+
+
+
+ + +{#if isShowAssetViewer} + +{/if} From 94a62ee5c1b466b7aa176dcca1a1a41bd82adbd9 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 27 Aug 2022 20:18:48 -0500 Subject: [PATCH 02/39] Added implementation note --- web/how-to-scroll-like-google.md | 48 +++++++++++++++++++++++++++ web/src/api/utils.ts | 13 -------- web/src/lib/utils/viewport-utils.ts | 32 ++++++++++++++++++ web/src/routes/photos/+page.server.ts | 4 +-- web/src/routes/photos/+page.svelte | 9 ++--- 5 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 web/how-to-scroll-like-google.md create mode 100644 web/src/lib/utils/viewport-utils.ts diff --git a/web/how-to-scroll-like-google.md b/web/how-to-scroll-like-google.md new file mode 100644 index 0000000000000..a9a154adfb981 --- /dev/null +++ b/web/how-to-scroll-like-google.md @@ -0,0 +1,48 @@ +# How to scroll like Google Photos + +## Glossary + +1. Section: a group of photos within a month +2. Segment: a group of photos within a day + +## Assumption + +* The photo's thumbnail is a square box with the size of 235px + +## Order of Implementation + +### Custom scroolbar + +* We need the custom scroll bar which represents the entire viewport. +* The viewport can be estimated by the total number of the photos and the width of the occupied photo's grid + +```typescript + const thumbnailHeight = 235; + + const unwrappedWidth = (3 / 2) * totalPhotoCount * thumbnailHeight * (7 / 10); + const rows = Math.ceil(unwrappedWidth / viewportWidth); + + const scrollbarHeight = rows * thumbnailHeight; +``` + +* Next, we will need to know when we click on a random position on the scroll bar, which section will fit into the page. Thus, we will need to know the section height as well. +* The section height can be calculated by the method above by putting `totalPhotoCount` as the count of the total photos within a month. We can use the following data structure to represent a list of section. + +```json +{ + [ + { + "section": "2022_08", + "count": 100, + "viewportHeight": 4000 + }, + { + "section": "2022_07", + "count": 50, + "viewportHeight": 2000 + } + ] +} +``` + +* With the known viewport height of each section and the total viewport height, we can build out the custom scrollbar with information of each section layout relatively and interactively on the scrollbar by using the percentages height. \ No newline at end of file diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index 75a8f6a89b6b0..5d7cfc4e70d19 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -12,16 +12,3 @@ export function getFileUrl(aid: string, did: string, isThumb?: boolean, isWeb?: return urlObj.href; } - -export function calculateTimeLineTotalHeight( - assetCount: AssetCountByTimeGroupResponseDto, - viewportWidth: number -) { - const targetHeight = 235; - - const unwrappedWidth = (3 / 2) * assetCount.totalAssets * targetHeight * (7 / 10); - const rows = Math.ceil(unwrappedWidth / viewportWidth); - const height = rows * targetHeight; - - return height; -} diff --git a/web/src/lib/utils/viewport-utils.ts b/web/src/lib/utils/viewport-utils.ts new file mode 100644 index 0000000000000..88eccee4ddea4 --- /dev/null +++ b/web/src/lib/utils/viewport-utils.ts @@ -0,0 +1,32 @@ +import { AssetCountByTimeGroupResponseDto } from '@api'; + +export function calculateViewportHeight( + assetCount: AssetCountByTimeGroupResponseDto, + viewportWidth: number +) { + const thumbnailHeight = 235; + + const unwrappedWidth = (3 / 2) * assetCount.totalAssets * thumbnailHeight * (7 / 10); + const rows = Math.ceil(unwrappedWidth / viewportWidth); + const height = rows * thumbnailHeight; + + return height; +} + +/** + * Calculate section height to identify which section will be loaded when clicked on the scroll bar. + * Each section is equivalent to month group + * In each section, there will be segment which is equivalent to day group + * Example: + * { + * "section": "2022_07_25" + * } + * + * + * @param assetCount + * @param viewportWidth + */ +export function calculateSectionHeight( + assetCount: AssetCountByTimeGroupResponseDto, + viewportWidth: number +) {} diff --git a/web/src/routes/photos/+page.server.ts b/web/src/routes/photos/+page.server.ts index 7d40010f188e5..cc126a000a1dc 100644 --- a/web/src/routes/photos/+page.server.ts +++ b/web/src/routes/photos/+page.server.ts @@ -1,6 +1,6 @@ import type { PageServerLoad } from './$types'; import { redirect, error } from '@sveltejs/kit'; -import { calculateTimeLineTotalHeight, serverApi, TimeGroupEnum } from '@api'; +import { serverApi, TimeGroupEnum } from '@api'; export const load: PageServerLoad = async ({ parent }) => { try { @@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ parent }) => { } const { data: assetCountByTimeGroup } = await serverApi.assetApi.getAssetCountByTimeGroup({ - timeGroup: TimeGroupEnum.Month + timeGroup: TimeGroupEnum.Day }); return { diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index 34b481ae87f81..5e340e91bad0e 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -12,7 +12,7 @@ import moment from 'moment'; import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; - import { api, AssetResponseDto, calculateTimeLineTotalHeight } from '@api'; + import { api, AssetResponseDto } from '@api'; import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; @@ -27,6 +27,7 @@ NotificationType } from '$lib/components/shared-components/notification/notification'; import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; + import { calculateViewportHeight } from '$lib/utils/viewport-utils'; export let data: PageData; @@ -50,12 +51,11 @@ let selectedAsset: AssetResponseDto; onMount(() => { - estimatedTotalHeight = calculateTimeLineTotalHeight( + estimatedTotalHeight = calculateViewportHeight( data.assetCountByTimeGroup, timelineViewPortWidth ); - console.log(estimatedTotalHeight); // setAssetInfo(data.assets); }); @@ -248,7 +248,6 @@ /> {/if}
-
@@ -259,6 +258,8 @@ bind:clientWidth={timelineViewPortWidth} style:height={estimatedTotalHeight + 'px'} > +

Estimated Height: {estimatedTotalHeight}

+
{#each new Array(data.assetCountByTimeGroup.totalAssets) as data, i}
{i}
From 1217f56d45ad1283cfad37b94eeddc36f7c6ea48 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 27 Aug 2022 22:41:20 -0500 Subject: [PATCH 03/39] Testing intersection observer --- .../shared-components/immich-thumbnail.svelte | 2 +- web/src/routes/photos/+page.svelte | 82 +++++++++++------- .../photos/{page.bak.svelte => page1.svelte} | 86 +++++++------------ 3 files changed, 85 insertions(+), 85 deletions(-) rename web/src/routes/photos/{page.bak.svelte => page1.svelte} (78%) diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte index 716b5adc75416..86d69d0a4a7ae 100644 --- a/web/src/lib/components/shared-components/immich-thumbnail.svelte +++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte @@ -143,7 +143,7 @@ }; - +
{ - estimatedTotalHeight = calculateViewportHeight( - data.assetCountByTimeGroup, - timelineViewPortWidth - ); - - // setAssetInfo(data.assets); + openWebsocketConnection(); }); const thumbnailMouseEventHandler = (event: CustomEvent) => { @@ -204,14 +196,6 @@ } }; - onMount(async () => { - openWebsocketConnection(); - - const { data: assets } = await api.assetApi.getAllAssets(); - - setAssetInfo(assets); - }); - onDestroy(() => { closeWebsocketConnection(); }); @@ -248,23 +232,63 @@ /> {/if}
+
-
-

Estimated Height: {estimatedTotalHeight}

- -
- {#each new Array(data.assetCountByTimeGroup.totalAssets) as data, i} -
{i}
+
+
+ {#each $assetsGroupByDate as assetsInDateGroup, groupIndex} + +
(isMouseOverGroup = true)} + on:mouseleave={() => (isMouseOverGroup = false)} + > + +

+ {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} +

selectAssetGroupHandler(groupIndex)} + > + {#if selectedGroup.has(groupIndex)} + + {:else if existingGroup.has(groupIndex)} + + {:else} + + {/if} +
+ {/if} + + {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} +

+ + +
+ {#each assetsInDateGroup as asset} + {#key asset.id} + + isMultiSelectionMode + ? selectAssetHandler(asset, groupIndex) + : viewAssetHandler(event)} + on:select={() => selectAssetHandler(asset, groupIndex)} + selected={multiSelectedAssets.has(asset)} + {groupIndex} + /> + {/key} + {/each} +
+
{/each} -
+
diff --git a/web/src/routes/photos/page.bak.svelte b/web/src/routes/photos/page1.svelte similarity index 78% rename from web/src/routes/photos/page.bak.svelte rename to web/src/routes/photos/page1.svelte index e3656b4b19e10..c61ed774c6d99 100644 --- a/web/src/routes/photos/page.bak.svelte +++ b/web/src/routes/photos/page1.svelte @@ -12,14 +12,14 @@ import moment from 'moment'; import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; - import { api, AssetResponseDto, calculateTimeLineTotalHeight } from '@api'; + import { api, AssetResponseDto } from '@api'; import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; import Close from 'svelte-material-icons/Close.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; - import type { PageData } from './$types'; + import type { PageData } from '../../../.svelte-kit/types/src/routes/photos./../../.svelte-kit/types/src/routes/photos/$types'; import { onMount, onDestroy } from 'svelte'; import { @@ -27,6 +27,7 @@ NotificationType } from '$lib/components/shared-components/notification/notification'; import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; + import { calculateViewportHeight } from '$lib/utils/viewport-utils'; export let data: PageData; @@ -45,10 +46,17 @@ let isShowAssetViewer = false; let currentViewAssetIndex = 0; + let timelineViewPortWidth: number; + let estimatedTotalHeight: number; let selectedAsset: AssetResponseDto; onMount(() => { - openWebsocketConnection(); + estimatedTotalHeight = calculateViewportHeight( + data.assetCountByTimeGroup, + timelineViewPortWidth + ); + + // setAssetInfo(data.assets); }); const thumbnailMouseEventHandler = (event: CustomEvent) => { @@ -196,6 +204,14 @@ } }; + onMount(async () => { + openWebsocketConnection(); + + const { data: assets } = await api.assetApi.getAllAssets(); + + setAssetInfo(assets); + }); + onDestroy(() => { closeWebsocketConnection(); }); @@ -232,63 +248,23 @@ /> {/if} -
-
-
- {#each $assetsGroupByDate as assetsInDateGroup, groupIndex} - -
(isMouseOverGroup = true)} - on:mouseleave={() => (isMouseOverGroup = false)} - > - -

- {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} -

selectAssetGroupHandler(groupIndex)} - > - {#if selectedGroup.has(groupIndex)} - - {:else if existingGroup.has(groupIndex)} - - {:else} - - {/if} -
- {/if} - - {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} -

- - -
- {#each assetsInDateGroup as asset} - {#key asset.id} - - isMultiSelectionMode - ? selectAssetHandler(asset, groupIndex) - : viewAssetHandler(event)} - on:select={() => selectAssetHandler(asset, groupIndex)} - selected={multiSelectedAssets.has(asset)} - {groupIndex} - /> - {/key} - {/each} -
-
+
+

Estimated Height: {estimatedTotalHeight}

+ +
+ {#each new Array(data.assetCountByTimeGroup.totalAssets) as data, i} +
{i}
{/each} -
+
From f793832b7c621c44c79dad1b8809f4ff1862c62b Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Sat, 27 Aug 2022 23:25:56 -0500 Subject: [PATCH 04/39] optimized scrolling by off loading unseen images --- server/apps/immich/src/api-v1/asset/asset.controller.ts | 2 ++ .../components/shared-components/immich-thumbnail.svelte | 1 + .../components/shared-components/side-bar/side-bar.svelte | 7 +++++++ web/src/routes/photos/+page.svelte | 5 ++++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 7009f1e11aa44..97df6cea3c6ae 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -15,6 +15,7 @@ import { HttpCode, BadRequestException, UploadedFile, + Header, } from '@nestjs/common'; import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard'; import { AssetService } from './asset.service'; @@ -111,6 +112,7 @@ export class AssetController { } @Get('/thumbnail/:assetId') + @Header('Cache-Control', 'max-age=300') async getAssetThumbnail( @Param('assetId') assetId: string, @Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto, diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte index 86d69d0a4a7ae..5831167783187 100644 --- a/web/src/lib/components/shared-components/immich-thumbnail.svelte +++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte @@ -212,6 +212,7 @@ {#if intersecting} { if ($page.routeId == 'albums') { selectedAction = AppSideBarSelection.ALBUMS; @@ -18,6 +20,10 @@ } else if ($page.routeId == 'sharing') { selectedAction = AppSideBarSelection.SHARING; } + + setInterval(() => { + domCount = document.getElementsByTagName('*').length; + }, 1000); }); @@ -48,6 +54,7 @@ actionType={AppSideBarSelection.ALBUMS} isSelected={selectedAction === AppSideBarSelection.ALBUMS} /> + DOM COUNT: {domCount} diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index f5c7d7135bffe..239b8d7b43058 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -47,8 +47,11 @@ let currentViewAssetIndex = 0; let selectedAsset: AssetResponseDto; - onMount(() => { + onMount(async () => { openWebsocketConnection(); + + const res = await api.assetApi.getAllAssets(); + setAssetInfo(res.data); }); const thumbnailMouseEventHandler = (event: CustomEvent) => { From 0e52ec80d20ce826335ca7ec178c6f0600b98651 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Mon, 29 Aug 2022 14:02:24 -0500 Subject: [PATCH 05/39] calculating segment height on scrollbar --- .../src/api-v1/asset/asset-repository.ts | 4 +- .../shared-components/immich-thumbnail.svelte | 16 +---- .../scrollbar/scrollbar.svelte | 68 +++++++++++++++++++ web/src/routes/photos/+page.server.ts | 2 +- web/src/routes/photos/+page.svelte | 26 ++++--- 5 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 web/src/lib/components/shared-components/scrollbar/scrollbar.svelte diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 9424750776983..8649ff1ba54e6 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -35,7 +35,7 @@ export class AssetRepository implements IAssetRepository { result = await this.assetRepository .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') - .addSelect(`to_char(date_trunc('month', "createdAt"::timestamptz), 'YYYY_MM')`, 'timeGroup') + .addSelect(`date_trunc('month', "createdAt"::timestamptz)`, 'timeGroup') .where('"userId" = :userId', { userId: userId }) .groupBy(`date_trunc('month', "createdAt"::timestamptz)`) .orderBy(`date_trunc('month', "createdAt"::timestamptz)`, 'DESC') @@ -44,7 +44,7 @@ export class AssetRepository implements IAssetRepository { result = await this.assetRepository .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') - .addSelect(`to_char(date_trunc('day', "createdAt"::timestamptz), 'YYYY_MM_DD')`, 'timeGroup') + .addSelect(`date_trunc('day', "createdAt"::timestamptz)`, 'timeGroup') .where('"userId" = :userId', { userId: userId }) .groupBy(`date_trunc('day', "createdAt"::timestamptz)`) .orderBy(`date_trunc('day', "createdAt"::timestamptz)`, 'DESC') diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte index 5831167783187..8de929fdd788b 100644 --- a/web/src/lib/components/shared-components/immich-thumbnail.svelte +++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte @@ -18,7 +18,6 @@ export let isExisted: boolean = false; let imageData: string; - // let videoData: string; let mouseOver: boolean = false; $: dispatch('mouseEvent', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex }); @@ -28,19 +27,8 @@ let isThumbnailVideoPlaying = false; let calculateVideoDurationIntervalHandler: NodeJS.Timer; let videoProgress = '00:00'; - // let videoAbortController: AbortController; let videoUrl: string; - const loadImageData = async () => { - const { data } = await api.assetApi.getAssetThumbnail(asset.id, format, { - responseType: 'blob' - }); - if (data instanceof Blob) { - imageData = URL.createObjectURL(data); - return imageData; - } - }; - const loadVideoData = async () => { isThumbnailVideoPlaying = false; @@ -215,10 +203,10 @@ id={asset.id} style:width={`${thumbnailSize}px`} style:height={`${thumbnailSize}px`} - in:fade={{ duration: 250 }} + in:fade={{ duration: 150 }} src={`/api/asset/thumbnail/${asset.id}?format=${format}`} alt={asset.id} - class={`object-cover ${getSize()} transition-all duration-100 z-0 ${getThumbnailBorderStyle()}`} + class={`object-cover ${getSize()} transition-all z-0 ${getThumbnailBorderStyle()}`} loading="lazy" /> {/if} diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte new file mode 100644 index 0000000000000..09585a74b26a4 --- /dev/null +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -0,0 +1,68 @@ + + +
+ {#each segmentData.groups as segment (segment.timeGroup)} + {@const groupDate = new Date(segment.timeGroup)} +
+ {groupDate.toLocaleString('default', { month: 'short' })} + {groupDate.getFullYear()} +
+ {/each} +
+ + diff --git a/web/src/routes/photos/+page.server.ts b/web/src/routes/photos/+page.server.ts index cc126a000a1dc..b9842a5abb242 100644 --- a/web/src/routes/photos/+page.server.ts +++ b/web/src/routes/photos/+page.server.ts @@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ parent }) => { } const { data: assetCountByTimeGroup } = await serverApi.assetApi.getAssetCountByTimeGroup({ - timeGroup: TimeGroupEnum.Day + timeGroup: TimeGroupEnum.Month }); return { diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index 239b8d7b43058..e46acad7376de 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -27,6 +27,8 @@ NotificationType } from '$lib/components/shared-components/notification/notification'; import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; + import Scrollbar from '$lib/components/shared-components/scrollbar/scrollbar.svelte'; + import { calculateViewportHeight } from '$lib/utils/viewport-utils'; export let data: PageData; @@ -46,12 +48,15 @@ let isShowAssetViewer = false; let currentViewAssetIndex = 0; let selectedAsset: AssetResponseDto; + let timelineViewportWidth = 0; + + $: viewportHeight = calculateViewportHeight(data.assetCountByTimeGroup, timelineViewportWidth); onMount(async () => { openWebsocketConnection(); - const res = await api.assetApi.getAllAssets(); - setAssetInfo(res.data); + // const res = await api.assetApi.getAllAssets(); + // setAssetInfo(res.data); }); const thumbnailMouseEventHandler = (event: CustomEvent) => { @@ -239,17 +244,21 @@
-
-
-
+
+ +
+
(isMouseOverGroup = true)} on:mouseleave={() => (isMouseOverGroup = false)} > -

{#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)}

-
{#each assetsInDateGroup as asset} {#key asset.id} @@ -291,7 +299,7 @@
{/each} -
+
-->
From c1249dd60da8294191ab65f94c7f7168254438f6 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Mon, 29 Aug 2022 16:52:40 -0500 Subject: [PATCH 06/39] Render datetime segment --- .../scrollbar/scrollbar.svelte | 80 ++++++++++++------- .../scrollbar/segment-scrollbar-layout.ts | 5 ++ 2 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 web/src/lib/components/shared-components/scrollbar/segment-scrollbar-layout.ts diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 09585a74b26a4..66cae2d22c3a0 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -3,62 +3,86 @@ import { AssetCountByTimeGroupResponseDto } from '@api'; import { onMount, onDestroy } from 'svelte'; + import { SegmentScrollbarLayout } from './segment-scrollbar-layout'; export let viewportHeight = 0; export let segmentData: AssetCountByTimeGroupResponseDto; let scrollbarHeight: number; let scrollbarHeightLeft: number; + let segmentScrollbarLayout: SegmentScrollbarLayout[] = []; + let isHover = false; + let hoveredDate: Date; const getSegmentHeight = (groupCount: number) => { - const percentage = (groupCount * 100) / segmentData.totalAssets; - return (percentage * scrollbarHeightLeft) / 100; + if (segmentData.groups.length > 0) { + const percentage = (groupCount * 100) / segmentData.totalAssets; + return Math.round((percentage * scrollbarHeightLeft) / 100); + } else { + return 0; + } }; const getLayoutDistance = () => { - let result = []; - /** - * { - * group_name: 'dec-2020', - * margin_top: '40' - * } - */ - for (const [i, segment] of segmentData.groups.entries()) { - if (i == 0) { - result.push(segment); - continue; - } - - result.push('ok'); - console.log('ok'); - } - // viewport height in pixel - // scrollbar height in pixel + let result: SegmentScrollbarLayout[] = []; + for (const segment of segmentData.groups) { + let segmentLayout = new SegmentScrollbarLayout(); - // total asset in count is equivalent to total scrollbar height -> interpolation + segmentLayout.count = segment.count; + segmentLayout.height = getSegmentHeight(segment.count); + segmentLayout.timeGroup = segment.timeGroup; - // each group asset count is used to calculate the margin from the top + result.push(segmentLayout); + } console.log(result); + return result; }; onMount(() => { scrollbarHeightLeft = scrollbarHeight; - getLayoutDistance(); + segmentScrollbarLayout = getLayoutDistance(); });
(isHover = true)} + on:mouseleave={() => (isHover = false)} > - {#each segmentData.groups as segment (segment.timeGroup)} +
+ {hoveredDate?.toLocaleString('default', { month: 'short' })} + {hoveredDate?.getFullYear()} +
+ + {#each segmentScrollbarLayout as segment, index (segment.timeGroup)} {@const groupDate = new Date(segment.timeGroup)} -
- {groupDate.toLocaleString('default', { month: 'short' })} - {groupDate.getFullYear()} + +
(hoveredDate = groupDate)} + > + + {#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()} +
+ {groupDate.getFullYear()} +
+ {:else if segment.height > 3} +
+ {/if}
{/each} + +
diff --git a/web/src/routes/photos/page1.svelte b/web/src/routes/photos/page1.svelte deleted file mode 100644 index c61ed774c6d99..0000000000000 --- a/web/src/routes/photos/page1.svelte +++ /dev/null @@ -1,280 +0,0 @@ - - - - Photos - Immich - - -
- {#if isMultiSelectionMode} - - -

Selected {multiSelectedAssets.size}

-
- - - -
- {/if} - - {#if !isMultiSelectionMode} - openFileUploadDialog(UploadType.GENERAL)} - /> - {/if} -
-
- - -
-
-

Estimated Height: {estimatedTotalHeight}

- -
- {#each new Array(data.assetCountByTimeGroup.totalAssets) as data, i} -
{i}
- {/each} -
-
-
-
- - -{#if isShowAssetViewer} - -{/if} From 521c7ec9b07ce14783fc9718c29dddd955a5eb7a Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 1 Sep 2022 12:31:34 -0500 Subject: [PATCH 14/39] Added scroll while loading --- .../asset-viewer/intersection-observer.svelte | 8 +++- web/src/routes/photos/+page.svelte | 45 +++++++++++++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/web/src/lib/components/asset-viewer/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte index f5fb6823f59b6..9f5fbda207137 100644 --- a/web/src/lib/components/asset-viewer/intersection-observer.svelte +++ b/web/src/lib/components/asset-viewer/intersection-observer.svelte @@ -1,25 +1,29 @@ @@ -316,12 +340,15 @@ style:height={timelineHeight + 'px'} > {#each assetStoreState as segment, i (i)} -
-
- {#each segment.assets as assetInfo (assetInfo.id)} - - {/each} -
+
+ +
+ {segment.segmentDate} + {#each segment.assets as assetInfo (assetInfo.id)} + + {/each} +
+
{/each}
From 1acd2217d8006c7a3e075846e5667337b2c94639 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 1 Sep 2022 15:32:42 -0500 Subject: [PATCH 15/39] Display in date group --- .../src/api-v1/asset/asset-repository.ts | 2 +- .../scrollbar/scrollbar.svelte | 46 ++++- web/src/lib/models/asset-store-state.ts | 15 +- web/src/lib/stores/assets.ts | 12 +- web/src/lib/utils/viewport-utils.ts | 1 - web/src/routes/photos/+page.svelte | 167 +++++++++++------- 6 files changed, 166 insertions(+), 77 deletions(-) diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index d66cb6c2f37a9..5e2c6bebbf5a8 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -45,7 +45,7 @@ export class AssetRepository implements IAssetRepository { .andWhere(`date_trunc('month', "createdAt"::timestamptz) IN (:...buckets)`, { buckets: [...getAssetByTimeBucketDto.timeBucket], }) - .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + // .leftJoinAndSelect('asset.exifInfo', 'exifInfo') .orderBy('asset.createdAt', 'DESC') .getMany(); } diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 4a3639e8eb4d9..6429d00ddf521 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -1,4 +1,7 @@ @@ -331,9 +325,11 @@ > +
-
- {segment.segmentDate} - {#each segment.assets as assetInfo (assetInfo.id)} - +
+ {#each segment.assetsGroupByDate as assetsInDateGroup, groupIndex} + +
(isMouseOverGroup = true)} + on:mouseleave={() => (isMouseOverGroup = false)} + > + +

+ {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} +

selectAssetGroupHandler(groupIndex)} + > + {#if selectedGroup.has(groupIndex)} + + {:else if existingGroup.has(groupIndex)} + + {:else} + + {/if} +
+ {/if} + + {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} +

+ + +
+ {#each assetsInDateGroup as asset (asset.id)} + + isMultiSelectionMode + ? selectAssetHandler(asset, groupIndex) + : viewAssetHandler(event)} + on:select={() => selectAssetHandler(asset, groupIndex)} + selected={multiSelectedAssets.has(asset)} + {groupIndex} + /> + {/each} +
+
{/each}
From 520ac0641afb8af874d26c8629543edc5c750337 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 1 Sep 2022 19:55:28 -0500 Subject: [PATCH 16/39] Refactor API name --- mobile/openapi/.openapi-generator/FILES | 8 +- mobile/openapi/README.md | 6 +- mobile/openapi/doc/AssetApi.md | 14 +- .../doc/AssetCountByTimeBucketResponseDto.md | 16 + .../doc/AssetCountByTimeGroupResponseDto.md | 4 +- .../doc/GetAssetCountByTimeBucketDto.md | 15 + mobile/openapi/lib/api.dart | 4 +- mobile/openapi/lib/api/asset_api.dart | 16 +- mobile/openapi/lib/api_client.dart | 8 +- ...set_count_by_time_bucket_response_dto.dart | 119 ++++++ ...sset_count_by_time_group_response_dto.dart | 30 +- .../get_asset_count_by_time_bucket_dto.dart | 111 +++++ ...ount_by_time_bucket_response_dto_test.dart | 32 ++ ...t_asset_count_by_time_bucket_dto_test.dart | 27 ++ .../src/api-v1/asset/asset-repository.ts | 14 +- .../src/api-v1/asset/asset.controller.ts | 10 +- .../src/api-v1/asset/asset.service.spec.ts | 2 +- .../immich/src/api-v1/asset/asset.service.ts | 14 +- ... => get-asset-count-by-time-bucket.dto.ts} | 4 +- .../asset-count-by-time-group-response.dto.ts | 14 +- server/immich-openapi-specs.json | 2 +- web/src/api/open-api/api.ts | 50 +-- .../shared-components/upload-panel.svelte | 6 +- web/src/lib/stores/assets.ts | 102 ----- web/src/routes/photos/+page.server.ts | 2 +- web/src/routes/photos/+page.svelte | 390 +----------------- 26 files changed, 432 insertions(+), 588 deletions(-) create mode 100644 mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md create mode 100644 mobile/openapi/doc/GetAssetCountByTimeBucketDto.md create mode 100644 mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart create mode 100644 mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart create mode 100644 mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart create mode 100644 mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart rename server/apps/immich/src/api-v1/asset/dto/{get-asset-count-by-time-group.dto.ts => get-asset-count-by-time-bucket.dto.ts} (78%) diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 15488bf3c9eac..ecbc00ffb223c 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -8,7 +8,7 @@ doc/AdminSignupResponseDto.md doc/AlbumApi.md doc/AlbumResponseDto.md doc/AssetApi.md -doc/AssetCountByTimeGroupDto.md +doc/AssetCountByTimeBucketResponseDto.md doc/AssetCountByTimeGroupResponseDto.md doc/AssetFileUploadResponseDto.md doc/AssetResponseDto.md @@ -30,7 +30,7 @@ doc/DeviceInfoResponseDto.md doc/DeviceTypeEnum.md doc/ExifResponseDto.md doc/GetAssetByTimeBucketDto.md -doc/GetAssetCountByTimeGroupDto.md +doc/GetAssetCountByTimeBucketDto.md doc/LoginCredentialDto.md doc/LoginResponseDto.md doc/LogoutResponseDto.md @@ -71,7 +71,7 @@ lib/model/add_assets_dto.dart lib/model/add_users_dto.dart lib/model/admin_signup_response_dto.dart lib/model/album_response_dto.dart -lib/model/asset_count_by_time_group_dto.dart +lib/model/asset_count_by_time_bucket_response_dto.dart lib/model/asset_count_by_time_group_response_dto.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_response_dto.dart @@ -91,7 +91,7 @@ lib/model/device_info_response_dto.dart lib/model/device_type_enum.dart lib/model/exif_response_dto.dart lib/model/get_asset_by_time_bucket_dto.dart -lib/model/get_asset_count_by_time_group_dto.dart +lib/model/get_asset_count_by_time_bucket_dto.dart lib/model/login_credential_dto.dart lib/model/login_response_dto.dart lib/model/logout_response_dto.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 6a19db78545b0..f900609ede2b6 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -80,7 +80,7 @@ Class | Method | HTTP request | Description *AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset | *AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} | *AssetApi* | [**getAssetByTimeBucket**](doc//AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket | -*AssetApi* | [**getAssetCountByTimeGroup**](doc//AssetApi.md#getassetcountbytimegroup) | **GET** /asset/count-by-date | +*AssetApi* | [**getAssetCountByTimeBucket**](doc//AssetApi.md#getassetcountbytimebucket) | **GET** /asset/count-by-time-bucket | *AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms | *AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} | *AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations | @@ -114,7 +114,7 @@ Class | Method | HTTP request | Description - [AddUsersDto](doc//AddUsersDto.md) - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) - [AlbumResponseDto](doc//AlbumResponseDto.md) - - [AssetCountByTimeGroupDto](doc//AssetCountByTimeGroupDto.md) + - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) - [AssetCountByTimeGroupResponseDto](doc//AssetCountByTimeGroupResponseDto.md) - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) - [AssetResponseDto](doc//AssetResponseDto.md) @@ -134,7 +134,7 @@ Class | Method | HTTP request | Description - [DeviceTypeEnum](doc//DeviceTypeEnum.md) - [ExifResponseDto](doc//ExifResponseDto.md) - [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md) - - [GetAssetCountByTimeGroupDto](doc//GetAssetCountByTimeGroupDto.md) + - [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md) - [LoginCredentialDto](doc//LoginCredentialDto.md) - [LoginResponseDto](doc//LoginResponseDto.md) - [LogoutResponseDto](doc//LogoutResponseDto.md) diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 6b718d0f18c8d..81a9738ed4c7b 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -15,7 +15,7 @@ Method | HTTP request | Description [**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset | [**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} | [**getAssetByTimeBucket**](AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket | -[**getAssetCountByTimeGroup**](AssetApi.md#getassetcountbytimegroup) | **GET** /asset/count-by-date | +[**getAssetCountByTimeBucket**](AssetApi.md#getassetcountbytimebucket) | **GET** /asset/count-by-time-bucket | [**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms | [**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} | [**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations | @@ -316,8 +316,8 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) -# **getAssetCountByTimeGroup** -> AssetCountByTimeGroupResponseDto getAssetCountByTimeGroup(getAssetCountByTimeGroupDto) +# **getAssetCountByTimeBucket** +> AssetCountByTimeGroupResponseDto getAssetCountByTimeBucket(getAssetCountByTimeBucketDto) @@ -332,13 +332,13 @@ import 'package:openapi/api.dart'; //defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); final api_instance = AssetApi(); -final getAssetCountByTimeGroupDto = GetAssetCountByTimeGroupDto(); // GetAssetCountByTimeGroupDto | +final getAssetCountByTimeBucketDto = GetAssetCountByTimeBucketDto(); // GetAssetCountByTimeBucketDto | try { - final result = api_instance.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto); + final result = api_instance.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto); print(result); } catch (e) { - print('Exception when calling AssetApi->getAssetCountByTimeGroup: $e\n'); + print('Exception when calling AssetApi->getAssetCountByTimeBucket: $e\n'); } ``` @@ -346,7 +346,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **getAssetCountByTimeGroupDto** | [**GetAssetCountByTimeGroupDto**](GetAssetCountByTimeGroupDto.md)| | + **getAssetCountByTimeBucketDto** | [**GetAssetCountByTimeBucketDto**](GetAssetCountByTimeBucketDto.md)| | ### Return type diff --git a/mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md b/mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md new file mode 100644 index 0000000000000..598462579b0d1 --- /dev/null +++ b/mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md @@ -0,0 +1,16 @@ +# openapi.model.AssetCountByTimeBucketResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**timeGroup** | **String** | | +**count** | **int** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md b/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md index 86990303a5a1b..aea5d9b44f248 100644 --- a/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md +++ b/mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md @@ -8,8 +8,8 @@ import 'package:openapi/api.dart'; ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**totalAssets** | **int** | | -**groups** | [**List**](AssetCountByTimeGroupDto.md) | | [default to const []] +**count** | **int** | | +**buckets** | [**List**](AssetCountByTimeBucketResponseDto.md) | | [default to const []] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/GetAssetCountByTimeBucketDto.md b/mobile/openapi/doc/GetAssetCountByTimeBucketDto.md new file mode 100644 index 0000000000000..365c9646f6322 --- /dev/null +++ b/mobile/openapi/doc/GetAssetCountByTimeBucketDto.md @@ -0,0 +1,15 @@ +# openapi.model.GetAssetCountByTimeBucketDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 92d88beb06214..86c4c9d8c1403 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -38,7 +38,7 @@ part 'model/add_assets_dto.dart'; part 'model/add_users_dto.dart'; part 'model/admin_signup_response_dto.dart'; part 'model/album_response_dto.dart'; -part 'model/asset_count_by_time_group_dto.dart'; +part 'model/asset_count_by_time_bucket_response_dto.dart'; part 'model/asset_count_by_time_group_response_dto.dart'; part 'model/asset_file_upload_response_dto.dart'; part 'model/asset_response_dto.dart'; @@ -58,7 +58,7 @@ part 'model/device_info_response_dto.dart'; part 'model/device_type_enum.dart'; part 'model/exif_response_dto.dart'; part 'model/get_asset_by_time_bucket_dto.dart'; -part 'model/get_asset_count_by_time_group_dto.dart'; +part 'model/get_asset_count_by_time_bucket_dto.dart'; part 'model/login_credential_dto.dart'; part 'model/login_response_dto.dart'; part 'model/logout_response_dto.dart'; diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index b2b0ae608bc77..4f95cd89cd7cf 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -348,16 +348,16 @@ class AssetApi { return null; } - /// Performs an HTTP 'GET /asset/count-by-date' operation and returns the [Response]. + /// Performs an HTTP 'GET /asset/count-by-time-bucket' operation and returns the [Response]. /// Parameters: /// - /// * [GetAssetCountByTimeGroupDto] getAssetCountByTimeGroupDto (required): - Future getAssetCountByTimeGroupWithHttpInfo(GetAssetCountByTimeGroupDto getAssetCountByTimeGroupDto,) async { + /// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required): + Future getAssetCountByTimeBucketWithHttpInfo(GetAssetCountByTimeBucketDto getAssetCountByTimeBucketDto,) async { // ignore: prefer_const_declarations - final path = r'/asset/count-by-date'; + final path = r'/asset/count-by-time-bucket'; // ignore: prefer_final_locals - Object? postBody = getAssetCountByTimeGroupDto; + Object? postBody = getAssetCountByTimeBucketDto; final queryParams = []; final headerParams = {}; @@ -379,9 +379,9 @@ class AssetApi { /// Parameters: /// - /// * [GetAssetCountByTimeGroupDto] getAssetCountByTimeGroupDto (required): - Future getAssetCountByTimeGroup(GetAssetCountByTimeGroupDto getAssetCountByTimeGroupDto,) async { - final response = await getAssetCountByTimeGroupWithHttpInfo(getAssetCountByTimeGroupDto,); + /// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required): + Future getAssetCountByTimeBucket(GetAssetCountByTimeBucketDto getAssetCountByTimeBucketDto,) async { + final response = await getAssetCountByTimeBucketWithHttpInfo(getAssetCountByTimeBucketDto,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 3232916a61611..528eeed8cd550 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -200,8 +200,8 @@ class ApiClient { return AdminSignupResponseDto.fromJson(value); case 'AlbumResponseDto': return AlbumResponseDto.fromJson(value); - case 'AssetCountByTimeGroupDto': - return AssetCountByTimeGroupDto.fromJson(value); + case 'AssetCountByTimeBucketResponseDto': + return AssetCountByTimeBucketResponseDto.fromJson(value); case 'AssetCountByTimeGroupResponseDto': return AssetCountByTimeGroupResponseDto.fromJson(value); case 'AssetFileUploadResponseDto': @@ -240,8 +240,8 @@ class ApiClient { return ExifResponseDto.fromJson(value); case 'GetAssetByTimeBucketDto': return GetAssetByTimeBucketDto.fromJson(value); - case 'GetAssetCountByTimeGroupDto': - return GetAssetCountByTimeGroupDto.fromJson(value); + case 'GetAssetCountByTimeBucketDto': + return GetAssetCountByTimeBucketDto.fromJson(value); case 'LoginCredentialDto': return LoginCredentialDto.fromJson(value); case 'LoginResponseDto': diff --git a/mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart b/mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart new file mode 100644 index 0000000000000..82f99f3639a0b --- /dev/null +++ b/mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart @@ -0,0 +1,119 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetCountByTimeBucketResponseDto { + /// Returns a new [AssetCountByTimeBucketResponseDto] instance. + AssetCountByTimeBucketResponseDto({ + required this.timeGroup, + required this.count, + }); + + String timeGroup; + + int count; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeBucketResponseDto && + other.timeGroup == timeGroup && + other.count == count; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (timeGroup.hashCode) + + (count.hashCode); + + @override + String toString() => 'AssetCountByTimeBucketResponseDto[timeGroup=$timeGroup, count=$count]'; + + Map toJson() { + final _json = {}; + _json[r'timeGroup'] = timeGroup; + _json[r'count'] = count; + return _json; + } + + /// Returns a new [AssetCountByTimeBucketResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetCountByTimeBucketResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "AssetCountByTimeBucketResponseDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "AssetCountByTimeBucketResponseDto[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return AssetCountByTimeBucketResponseDto( + timeGroup: mapValueOfType(json, r'timeGroup')!, + count: mapValueOfType(json, r'count')!, + ); + } + return null; + } + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetCountByTimeBucketResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetCountByTimeBucketResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetCountByTimeBucketResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetCountByTimeBucketResponseDto.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'timeGroup', + 'count', + }; +} + diff --git a/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart b/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart index ddd771f5186f5..6f7cd0a90ca86 100644 --- a/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart +++ b/mobile/openapi/lib/model/asset_count_by_time_group_response_dto.dart @@ -13,32 +13,32 @@ part of openapi.api; class AssetCountByTimeGroupResponseDto { /// Returns a new [AssetCountByTimeGroupResponseDto] instance. AssetCountByTimeGroupResponseDto({ - required this.totalAssets, - this.groups = const [], + required this.count, + this.buckets = const [], }); - int totalAssets; + int count; - List groups; + List buckets; @override bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeGroupResponseDto && - other.totalAssets == totalAssets && - other.groups == groups; + other.count == count && + other.buckets == buckets; @override int get hashCode => // ignore: unnecessary_parenthesis - (totalAssets.hashCode) + - (groups.hashCode); + (count.hashCode) + + (buckets.hashCode); @override - String toString() => 'AssetCountByTimeGroupResponseDto[totalAssets=$totalAssets, groups=$groups]'; + String toString() => 'AssetCountByTimeGroupResponseDto[count=$count, buckets=$buckets]'; Map toJson() { final _json = {}; - _json[r'totalAssets'] = totalAssets; - _json[r'groups'] = groups; + _json[r'count'] = count; + _json[r'buckets'] = buckets; return _json; } @@ -61,8 +61,8 @@ class AssetCountByTimeGroupResponseDto { }()); return AssetCountByTimeGroupResponseDto( - totalAssets: mapValueOfType(json, r'totalAssets')!, - groups: AssetCountByTimeGroupDto.listFromJson(json[r'groups'])!, + count: mapValueOfType(json, r'count')!, + buckets: AssetCountByTimeBucketResponseDto.listFromJson(json[r'buckets'])!, ); } return null; @@ -112,8 +112,8 @@ class AssetCountByTimeGroupResponseDto { /// The list of required keys that must be present in a JSON. static const requiredKeys = { - 'totalAssets', - 'groups', + 'count', + 'buckets', }; } diff --git a/mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart b/mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart new file mode 100644 index 0000000000000..4a75a8caae920 --- /dev/null +++ b/mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class GetAssetCountByTimeBucketDto { + /// Returns a new [GetAssetCountByTimeBucketDto] instance. + GetAssetCountByTimeBucketDto({ + required this.timeGroup, + }); + + TimeGroupEnum timeGroup; + + @override + bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeBucketDto && + other.timeGroup == timeGroup; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (timeGroup.hashCode); + + @override + String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup]'; + + Map toJson() { + final _json = {}; + _json[r'timeGroup'] = timeGroup; + return _json; + } + + /// Returns a new [GetAssetCountByTimeBucketDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static GetAssetCountByTimeBucketDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + // Ensure that the map contains the required keys. + // Note 1: the values aren't checked for validity beyond being non-null. + // Note 2: this code is stripped in release mode! + assert(() { + requiredKeys.forEach((key) { + assert(json.containsKey(key), 'Required key "GetAssetCountByTimeBucketDto[$key]" is missing from JSON.'); + assert(json[key] != null, 'Required key "GetAssetCountByTimeBucketDto[$key]" has a null value in JSON.'); + }); + return true; + }()); + + return GetAssetCountByTimeBucketDto( + timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!, + ); + } + return null; + } + + static List? listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = GetAssetCountByTimeBucketDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = GetAssetCountByTimeBucketDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of GetAssetCountByTimeBucketDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = GetAssetCountByTimeBucketDto.listFromJson(entry.value, growable: growable,); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'timeGroup', + }; +} + diff --git a/mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart b/mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart new file mode 100644 index 0000000000000..37e54caee1e80 --- /dev/null +++ b/mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart @@ -0,0 +1,32 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for AssetCountByTimeBucketResponseDto +void main() { + // final instance = AssetCountByTimeBucketResponseDto(); + + group('test AssetCountByTimeBucketResponseDto', () { + // String timeGroup + test('to test the property `timeGroup`', () async { + // TODO + }); + + // int count + test('to test the property `count`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart b/mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart new file mode 100644 index 0000000000000..83a0cc9199f5d --- /dev/null +++ b/mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart @@ -0,0 +1,27 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.12 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +import 'package:openapi/api.dart'; +import 'package:test/test.dart'; + +// tests for GetAssetCountByTimeBucketDto +void main() { + // final instance = GetAssetCountByTimeBucketDto(); + + group('test GetAssetCountByTimeBucketDto', () { + // TimeGroupEnum timeGroup + test('to test the property `timeGroup`', () async { + // TODO + }); + + + }); + +} diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 5e2c6bebbf5a8..209dad2509c59 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -6,8 +6,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm/repository/Repository'; import { CreateAssetDto } from './dto/create-asset.dto'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; -import { AssetCountByTimeGroupDto } from './response-dto/asset-count-by-time-group-response.dto'; -import { TimeGroupEnum } from './dto/get-asset-count-by-time-group.dto'; +import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; +import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; export interface IAssetRepository { @@ -24,7 +24,7 @@ export interface IAssetRepository { getLocationsByUserId(userId: string): Promise; getDetectedObjectsByUserId(userId: string): Promise; getSearchPropertiesByUserId(userId: string): Promise; - getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum): Promise; + getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise; getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise; } @@ -50,10 +50,10 @@ export class AssetRepository implements IAssetRepository { .getMany(); } - async getAssetCountByTimeGroup(userId: string, timeGroup: TimeGroupEnum) { - let result: AssetCountByTimeGroupDto[] = []; + async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) { + let result: AssetCountByTimeBucketResponseDto[] = []; - if (timeGroup === TimeGroupEnum.Month) { + if (timeBucket === TimeGroupEnum.Month) { result = await this.assetRepository .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') @@ -62,7 +62,7 @@ export class AssetRepository implements IAssetRepository { .groupBy(`date_trunc('month', "createdAt"::timestamptz)`) .orderBy(`date_trunc('month', "createdAt"::timestamptz)`, 'DESC') .getRawMany(); - } else if (timeGroup === TimeGroupEnum.Day) { + } else if (timeBucket === TimeGroupEnum.Day) { result = await this.assetRepository .createQueryBuilder('asset') .select(`COUNT(asset.id)::int`, 'count') diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index cb69a288de0cf..a8f1949006571 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -45,7 +45,7 @@ import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-res import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto'; import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto'; import { AssetCountByTimeGroupResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; -import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto'; +import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; @UseGuards(JwtAuthGuard) @@ -154,12 +154,12 @@ export class AssetController { return this.assetService.searchAsset(authUser, searchAssetDto); } - @Get('/count-by-date') - async getAssetCountByTimeGroup( + @Get('/count-by-time-bucket') + async getAssetCountByTimeBucket( @GetAuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, + @Body(ValidationPipe) getAssetCountByTimeGroupDto: GetAssetCountByTimeBucketDto, ): Promise { - return this.assetService.getAssetCountByTimeGroup(authUser, getAssetCountByTimeGroupDto); + return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto); } /** diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index c16050ea85ac0..bd8754984a783 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -54,7 +54,7 @@ describe('AssetService', () => { create: jest.fn(), getAllByUserId: jest.fn(), getAllByDeviceId: jest.fn(), - getAssetCountByTimeGroup: jest.fn(), + getAssetCountByTimeBucket: jest.fn(), getById: jest.fn(), getDetectedObjectsByUserId: jest.fn(), getLocationsByUserId: jest.fn(), diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 964e34a661b49..c20ef4cd5767a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -31,9 +31,9 @@ import { ASSET_REPOSITORY, IAssetRepository } from './asset-repository'; import { SearchPropertiesDto } from './dto/search-properties.dto'; import { AssetCountByTimeGroupResponseDto, - mapAssetCountByTimeGroupResponse, + mapAssetCountByTimeBucket, } from './response-dto/asset-count-by-time-group-response.dto'; -import { GetAssetCountByTimeGroupDto } from './dto/get-asset-count-by-time-group.dto'; +import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; const fileInfo = promisify(stat); @@ -450,16 +450,16 @@ export class AssetService { return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id); } - async getAssetCountByTimeGroup( + async getAssetCountByTimeBucket( authUser: AuthUserDto, - getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, + getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, ): Promise { - const result = await this._assetRepository.getAssetCountByTimeGroup( + const result = await this._assetRepository.getAssetCountByTimeBucket( authUser.id, - getAssetCountByTimeGroupDto.timeGroup, + getAssetCountByTimeBucketDto.timeGroup, ); - return mapAssetCountByTimeGroupResponse(result); + return mapAssetCountByTimeBucket(result); } private calculateChecksum(filePath: string): Promise { diff --git a/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts b/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts similarity index 78% rename from server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts rename to server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts index 2862d3c945190..5ef6d208b186e 100644 --- a/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-group.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts @@ -5,12 +5,12 @@ export enum TimeGroupEnum { Day = 'day', Month = 'month', } -export class GetAssetCountByTimeGroupDto { +export class GetAssetCountByTimeBucketDto { @IsNotEmpty() @ApiProperty({ type: String, enum: TimeGroupEnum, - enumName: 'TimeGroupEnum', + enumName: 'TimeBucketEnum', }) timeGroup!: TimeGroupEnum; } diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts index 2d1d60974e817..783c467f3e558 100644 --- a/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts +++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -export class AssetCountByTimeGroupDto { +export class AssetCountByTimeBucketResponseDto { @ApiProperty({ type: 'string' }) timeGroup!: string; @@ -9,15 +9,17 @@ export class AssetCountByTimeGroupDto { } export class AssetCountByTimeGroupResponseDto { - groups!: AssetCountByTimeGroupDto[]; + buckets!: AssetCountByTimeBucketResponseDto[]; @ApiProperty({ type: 'integer' }) - totalAssets!: number; + count!: number; } -export function mapAssetCountByTimeGroupResponse(result: AssetCountByTimeGroupDto[]): AssetCountByTimeGroupResponseDto { +export function mapAssetCountByTimeBucket( + result: AssetCountByTimeBucketResponseDto[], +): AssetCountByTimeGroupResponseDto { return { - groups: result, - totalAssets: result.map((group) => group.count).reduce((a, b) => a + b, 0), + buckets: result, + count: result.map((group) => group.count).reduce((a, b) => a + b, 0), }; } diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 77075d5ec555d..17e8030e55ef0 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-date":{"get":{"operationId":"getAssetCountByTimeGroup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeGroupDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeGroupResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string","nullable":true,"default":null},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeGroupDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeGroupDto":{"type":"object","properties":{"timeGroup":{"type":"string"},"count":{"type":"integer"}},"required":["timeGroup","count"]},"AssetCountByTimeGroupResponseDto":{"type":"object","properties":{"totalAssets":{"type":"integer"},"groups":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeGroupDto"}}},"required":["totalAssets","groups"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true}},"required":["successful"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer"},"diskUseRaw":{"type":"integer"},"diskAvailableRaw":{"type":"integer"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"get":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeGroupResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string","nullable":true,"default":null},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeBucketEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeBucketEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"timeGroup":{"type":"string"},"count":{"type":"integer"}},"required":["timeGroup","count"]},"AssetCountByTimeGroupResponseDto":{"type":"object","properties":{"count":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}},"required":["count","buckets"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true}},"required":["successful"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer"},"diskUseRaw":{"type":"integer"},"diskAvailableRaw":{"type":"integer"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}}}}} \ No newline at end of file diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 918d1fe5e9b84..1f17871eb1ad4 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -148,19 +148,19 @@ export interface AlbumResponseDto { /** * * @export - * @interface AssetCountByTimeGroupDto + * @interface AssetCountByTimeBucketResponseDto */ -export interface AssetCountByTimeGroupDto { +export interface AssetCountByTimeBucketResponseDto { /** * * @type {string} - * @memberof AssetCountByTimeGroupDto + * @memberof AssetCountByTimeBucketResponseDto */ 'timeGroup': string; /** * * @type {number} - * @memberof AssetCountByTimeGroupDto + * @memberof AssetCountByTimeBucketResponseDto */ 'count': number; } @@ -175,13 +175,13 @@ export interface AssetCountByTimeGroupResponseDto { * @type {number} * @memberof AssetCountByTimeGroupResponseDto */ - 'totalAssets': number; + 'count': number; /** * - * @type {Array} + * @type {Array} * @memberof AssetCountByTimeGroupResponseDto */ - 'groups': Array; + 'buckets': Array; } /** * @@ -774,13 +774,13 @@ export interface GetAssetByTimeBucketDto { /** * * @export - * @interface GetAssetCountByTimeGroupDto + * @interface GetAssetCountByTimeBucketDto */ -export interface GetAssetCountByTimeGroupDto { +export interface GetAssetCountByTimeBucketDto { /** * * @type {TimeGroupEnum} - * @memberof GetAssetCountByTimeGroupDto + * @memberof GetAssetCountByTimeBucketDto */ 'timeGroup': TimeGroupEnum; } @@ -2191,14 +2191,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * - * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByTimeGroup: async (getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAssetCountByTimeGroupDto' is not null or undefined - assertParamExists('getAssetCountByTimeGroup', 'getAssetCountByTimeGroupDto', getAssetCountByTimeGroupDto) - const localVarPath = `/asset/count-by-date`; + getAssetCountByTimeBucket: async (getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'getAssetCountByTimeBucketDto' is not null or undefined + assertParamExists('getAssetCountByTimeBucket', 'getAssetCountByTimeBucketDto', getAssetCountByTimeBucketDto) + const localVarPath = `/asset/count-by-time-bucket`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -2221,7 +2221,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeGroupDto, localVarRequestOptions, configuration) + localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeBucketDto, localVarRequestOptions, configuration) return { url: toPathString(localVarUrlObj), @@ -2624,12 +2624,12 @@ export const AssetApiFp = function(configuration?: Configuration) { }, /** * - * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options); + async getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -2785,12 +2785,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath }, /** * - * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: any): AxiosPromise { - return localVarFp.getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(axios, basePath)); + getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise { + return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath)); }, /** * @@ -2949,13 +2949,13 @@ export class AssetApi extends BaseAPI { /** * - * @param {GetAssetCountByTimeGroupDto} getAssetCountByTimeGroupDto + * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public getAssetCountByTimeGroup(getAssetCountByTimeGroupDto: GetAssetCountByTimeGroupDto, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetCountByTimeGroup(getAssetCountByTimeGroupDto, options).then((request) => request(this.axios, this.basePath)); + public getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte index 9e1bcb8ac8478..596e4f76dc852 100644 --- a/web/src/lib/components/shared-components/upload-panel.svelte +++ b/web/src/lib/components/shared-components/upload-panel.svelte @@ -5,7 +5,7 @@ import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte'; import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte'; import type { UploadAsset } from '$lib/models/upload-asset'; - import { getAssetsInfo } from '$lib/stores/assets'; + // import { getAssetsInfo } from '$lib/stores/assets'; let showDetail = true; let uploadLength = 0; @@ -83,7 +83,9 @@
getAssetsInfo()} + on:outroend={() => { + // getAssetsInfo() + }} class="absolute right-6 bottom-6 z-[10000]" > {#if showDetail} diff --git a/web/src/lib/stores/assets.ts b/web/src/lib/stores/assets.ts index 84fa06c0ac011..81e09029d8ec4 100644 --- a/web/src/lib/stores/assets.ts +++ b/web/src/lib/stores/assets.ts @@ -4,105 +4,3 @@ import _ from 'lodash'; import moment from 'moment'; import { api, AssetCountByTimeGroupResponseDto, AssetResponseDto } from '@api'; import { AssetStoreState } from '$lib/models/asset-store-state'; - -export const assets = writable([]); - -export const assetsGroupByDate = derived(assets, ($assets) => { - try { - return lodash - .chain($assets) - .groupBy((a) => moment(a.createdAt).format('ddd, MMM DD YYYY')) - .sortBy((group) => $assets.indexOf(group[0])) - .value(); - } catch (e) { - return []; - } -}); - -export const flattenAssetGroupByDate = derived(assetsGroupByDate, ($assetsGroupByDate) => { - return $assetsGroupByDate.flat(); -}); - -export const getAssetsInfo = async () => { - try { - const { data } = await api.assetApi.getAllAssets(); - } catch (error) { - console.log('Error [getAssetsInfo]'); - } -}; - -export const setAssetInfo = (data: AssetResponseDto[]) => { - assets.set(data); -}; - -function createAssetStore() { - const assets = writable([]); - - const calculatedTimelineHeight = derived(assets, ($assets) => { - return $assets.reduce((acc, cur) => { - return acc + cur.segmentHeight; - }, 0); - }); - - const calculateSegmentViewport = async ( - viewportWidth: number, - assetCountByTimeGroup: AssetCountByTimeGroupResponseDto - ) => { - const result: AssetStoreState[] = []; - - for (const segment of assetCountByTimeGroup.groups) { - const unwrappedWidth = (3 / 2) * segment.count * 235 * (7 / 10); - const rows = Math.ceil(unwrappedWidth / viewportWidth); - const height = rows * 235; - const assetStoreState = new AssetStoreState(); - - assetStoreState.assets = []; - assetStoreState.segmentHeight = height; - assetStoreState.segmentDate = segment.timeGroup; - - result.push(assetStoreState); - } - - assets.set(result); - - return result; - }; - - const getAssetsByTimeBuckets = async (timeBuckets: string[]) => { - for (const timeBucket of timeBuckets) { - const { data: assetResponseDto } = await api.assetApi.getAssetByTimeBucket({ - timeBucket: [timeBucket] - }); - - assets.update((assets) => { - const assetStoreState = assets.find((a) => a.segmentDate === timeBucket); - if (assetStoreState) { - assetStoreState.assets = assetResponseDto; - } - return assets; - }); - } - - return assets; - }; - - const updateSegmentHeight = (segmentDate: string, height: number) => { - assets.update((assets) => { - const assetStoreState = assets.find((a) => a.segmentDate === segmentDate); - if (assetStoreState) { - assetStoreState.segmentHeight = height; - } - return assets; - }); - }; - - return { - assets, - calculateSegmentViewport, - getAssetsByTimeBuckets, - updateSegmentHeight, - calculatedTimelineHeight - }; -} - -export const assetStore = createAssetStore(); diff --git a/web/src/routes/photos/+page.server.ts b/web/src/routes/photos/+page.server.ts index b9842a5abb242..59d1c16d14367 100644 --- a/web/src/routes/photos/+page.server.ts +++ b/web/src/routes/photos/+page.server.ts @@ -9,7 +9,7 @@ export const load: PageServerLoad = async ({ parent }) => { throw error(400, 'Not logged in'); } - const { data: assetCountByTimeGroup } = await serverApi.assetApi.getAssetCountByTimeGroup({ + const { data: assetCountByTimeGroup } = await serverApi.assetApi.getAssetCountByTimeBucket({ timeGroup: TimeGroupEnum.Month }); diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index 21ebe527d567c..94e629f206508 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -1,284 +1,20 @@ @@ -286,126 +22,12 @@
- {#if isMultiSelectionMode} - - -

Selected {multiSelectedAssets.size}

-
- - - -
- {/if} - - {#if !isMultiSelectionMode} - openFileUploadDialog(UploadType.GENERAL)} - /> - {/if} + openFileUploadDialog(UploadType.GENERAL)} + />
- -
- - -
- {#each assetStoreState as segment, i (i)} -
- -
- {#each segment.assetsGroupByDate as assetsInDateGroup, groupIndex} - -
(isMouseOverGroup = true)} - on:mouseleave={() => (isMouseOverGroup = false)} - > - -

- {#if (selectedGroupThumbnail === groupIndex && isMouseOverGroup) || selectedGroup.has(groupIndex)} -

selectAssetGroupHandler(groupIndex)} - > - {#if selectedGroup.has(groupIndex)} - - {:else if existingGroup.has(groupIndex)} - - {:else} - - {/if} -
- {/if} - - {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} -

- - -
- {#each assetsInDateGroup as asset (asset.id)} - - isMultiSelectionMode - ? selectAssetHandler(asset, groupIndex) - : viewAssetHandler(event)} - on:select={() => selectAssetHandler(asset, groupIndex)} - selected={multiSelectedAssets.has(asset)} - {groupIndex} - /> - {/each} -
-
- {/each} -
-
-
- {/each} -
-
- - -{#if isShowAssetViewer} - -{/if} - - From 02bb55a3949e347df6db5ee19260352235263270 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Fri, 2 Sep 2022 00:25:55 -0500 Subject: [PATCH 17/39] Intersection observer now has margin working --- .../album-page/asset-selection.svelte | 2 +- .../asset-viewer/intersection-observer.svelte | 6 ++- .../components/photos-page/asset-grid.svelte | 43 +++++++++++++++++++ .../shared-components/upload-panel.svelte | 2 +- .../lib/stores/{assets.ts => assets.store.ts} | 0 web/src/routes/photos/+page.svelte | 2 + 6 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 web/src/lib/components/photos-page/asset-grid.svelte rename web/src/lib/stores/{assets.ts => assets.store.ts} (100%) diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte index 0f3392526e7fc..6fcbb102d286e 100644 --- a/web/src/lib/components/album-page/asset-selection.svelte +++ b/web/src/lib/components/album-page/asset-selection.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher, onMount } from 'svelte'; import { quintOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; - import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets'; + import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets.store'; import CheckCircle from 'svelte-material-icons/CheckCircle.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import moment from 'moment'; diff --git a/web/src/lib/components/asset-viewer/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte index 9f5fbda207137..20c0e97f7be30 100644 --- a/web/src/lib/components/asset-viewer/intersection-observer.svelte +++ b/web/src/lib/components/asset-viewer/intersection-observer.svelte @@ -7,13 +7,14 @@ export let bottom = 0; export let left = 0; export let right = 0; + export let root: HTMLElement | null = null; let intersecting = false; let container: any; const dispatch = createEventDispatcher(); onMount(() => { if (typeof IntersectionObserver !== 'undefined') { - const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`; + const rootMargin = `${top}px ${right}px ${bottom}px ${left}px`; const observer = new IntersectionObserver( (entries) => { intersecting = entries[0].isIntersecting; @@ -26,7 +27,8 @@ } }, { - rootMargin + rootMargin, + root } ); diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte new file mode 100644 index 0000000000000..6c7ceb3d8440b --- /dev/null +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -0,0 +1,43 @@ + + +
+

Viewport height: {viewportHeight} px

+

Viewport width: {viewportWidth} px

+ + {#if assetGridElement} + +
Test
+
+ {/if} +
+ + diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte index 596e4f76dc852..31da7c00ee429 100644 --- a/web/src/lib/components/shared-components/upload-panel.svelte +++ b/web/src/lib/components/shared-components/upload-panel.svelte @@ -5,7 +5,7 @@ import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte'; import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte'; import type { UploadAsset } from '$lib/models/upload-asset'; - // import { getAssetsInfo } from '$lib/stores/assets'; + // import { getAssetsInfo } fro$lib/stores/assets.storeets'; let showDetail = true; let uploadLength = 0; diff --git a/web/src/lib/stores/assets.ts b/web/src/lib/stores/assets.store.ts similarity index 100% rename from web/src/lib/stores/assets.ts rename to web/src/lib/stores/assets.store.ts diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index 94e629f206508..e5377b75433f5 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -5,6 +5,7 @@ import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte'; import { onMount } from 'svelte'; import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; + import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; export let data: PageData; @@ -30,4 +31,5 @@
+
From 895760527a4691968712e581b0cfe93925999e8f Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Fri, 2 Sep 2022 08:39:38 -0500 Subject: [PATCH 18/39] merge upstream --- .../asset-viewer/intersection-observer.svelte | 1 + .../scrollbar/scrollbar.svelte | 78 +++++++------------ 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/web/src/lib/components/asset-viewer/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte index 20c0e97f7be30..75fe0da610353 100644 --- a/web/src/lib/components/asset-viewer/intersection-observer.svelte +++ b/web/src/lib/components/asset-viewer/intersection-observer.svelte @@ -8,6 +8,7 @@ export let left = 0; export let right = 0; export let root: HTMLElement | null = null; + let intersecting = false; let container: any; const dispatch = createEventDispatcher(); diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 6429d00ddf521..2930baada54f3 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -1,6 +1,5 @@
-

Viewport height: {viewportHeight} px

-

Viewport width: {viewportWidth} px

- {#if assetGridElement} - -
Test
-
+
+ + + {#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)} + +
+ {#if intersecting} + + {bucket.bucketDate} + {/if} +
+
+ {/each} +
{/if}
diff --git a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte index 2930baada54f3..c2e7dce27c775 100644 --- a/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte +++ b/web/src/lib/components/shared-components/scrollbar/scrollbar.svelte @@ -1,16 +1,11 @@ + +
+ {#each assetsGroupByDate as assetsInDateGroup, groupIndex} + +
(isMouseOverGroup = true)} + on:mouseleave={() => (isMouseOverGroup = false)} + > + +

+ + + {moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')} +

+ + +
+ {#each assetsInDateGroup as asset} + {#key asset.id} + + {/key} + {/each} +
+
+ {/each} +
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index d42c36feef3c8..9a70dbc24ee08 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -6,6 +6,7 @@ import { assetGridState, assetStore } from '$lib/stores/assets.store'; import { AssetCountByTimeBucketResponseDto } from '@api'; import { assets } from '$app/paths'; + import AssetDateGroup from './asset-date-group.svelte'; let viewportHeight = 0; let viewportWidth = 0; @@ -41,8 +42,6 @@ > {#if assetGridElement}
- - {#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
{#if intersecting} - - {bucket.bucketDate} + + {/if}
diff --git a/web/src/lib/models/asset-grid-state.ts b/web/src/lib/models/asset-grid-state.ts index 371fc793218d0..c0f59b56b0721 100644 --- a/web/src/lib/models/asset-grid-state.ts +++ b/web/src/lib/models/asset-grid-state.ts @@ -1,6 +1,4 @@ import { AssetResponseDto } from '@api'; -import lodash from 'lodash-es'; -import moment from 'moment'; export class AssetBucket { /** @@ -38,9 +36,4 @@ export class AssetGridState { * Total assets that have been loaded */ assets: AssetResponseDto[] = []; - - /** - * Group assets by date - */ - assetsGroupByDate: AssetResponseDto[][] = []; } diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 78182172bcabd..029243cb46a71 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -37,8 +37,7 @@ function createAssetStore() { bucketHeight: calculateViewportHeightByNumberOfAsset(d.count, viewportWidth), assets: [] })), - assets: [], - assetsGroupByDate: [] + assets: [] }); }; @@ -62,11 +61,6 @@ function createAssetStore() { const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket); state.buckets[bucketIndex].assets = assets; state.assets = lodash.flatMap(state.buckets, (b) => b.assets); - state.assetsGroupByDate = lodash - .chain(state.assets) - .groupBy((a) => moment(a.createdAt).format('ddd, MMM DD YYYY')) - .sortBy((group) => state.assets.indexOf(group[0])) - .value(); return state; }); @@ -76,9 +70,17 @@ function createAssetStore() { } }; + const updateBucketHeight = (bucket: string, height: number) => { + assetGridState.update((state) => { + const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket); + state.buckets[bucketIndex].bucketHeight = height; + return state; + }); + }; return { setInitialState, - getAssetsByBucket + getAssetsByBucket, + updateBucketHeight }; } From 011969582cb8693d3f1b6a72083d483d18c3caae Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 2 Sep 2022 23:10:46 -0500 Subject: [PATCH 22/39] adding viewing functionality --- .../src/api-v1/asset/asset-repository.ts | 2 +- .../asset-viewer/asset-viewer.svelte | 6 +- .../photos-page/asset-date-group.svelte | 77 +++++++++++++------ .../components/photos-page/asset-grid.svelte | 64 +++++++++++---- .../shared-components/portal/portal.svelte | 60 +++++++++++++++ .../scrollbar/scrollbar.svelte | 2 - web/src/lib/stores/asset-grid.store.ts | 33 ++++++++ web/src/lib/stores/assets.store.ts | 1 - 8 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 web/src/lib/components/shared-components/portal/portal.svelte create mode 100644 web/src/lib/stores/asset-grid.store.ts diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 3adada5be158e..02d9116da70c0 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -45,7 +45,7 @@ export class AssetRepository implements IAssetRepository { .andWhere(`date_trunc('month', "createdAt"::timestamptz) IN (:...buckets)`, { buckets: [...getAssetByTimeBucketDto.timeBucket], }) - // .leftJoinAndSelect('asset.exifInfo', 'exifInfo') + .andWhere('asset.resizePath is not NULL') .orderBy('asset.createdAt', 'DESC') .getMany(); } diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 980314cf6f626..b934797890311 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -130,7 +130,7 @@
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 9a70dbc24ee08..f8b7bfbb84303 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -5,8 +5,14 @@ import IntersectionObserver from '../asset-viewer/intersection-observer.svelte'; import { assetGridState, assetStore } from '$lib/stores/assets.store'; import { AssetCountByTimeBucketResponseDto } from '@api'; - import { assets } from '$app/paths'; import AssetDateGroup from './asset-date-group.svelte'; + import Portal from '../shared-components/portal/portal.svelte'; + import AssetViewer from '../asset-viewer/asset-viewer.svelte'; + import { + assetGridStore, + isViewingAssetStoreState, + viewingAssetStoreState + } from '$lib/stores/asset-grid.store'; let viewportHeight = 0; let viewportWidth = 0; @@ -15,7 +21,23 @@ onMount(() => { assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket); - assetStore.getAssetsByBucket('2022-08-01T00:00:00.000Z'); + + // Get asset bucket if bucket height is smaller than viewport height + let bucketsToFetchInitially: string[] = []; + let initialBucketsHeight = 0; + $assetGridState.buckets.every((bucket) => { + if (initialBucketsHeight < viewportHeight) { + initialBucketsHeight += bucket.bucketHeight; + bucketsToFetchInitially.push(bucket.bucketDate); + return true; + } else { + return false; + } + }); + + bucketsToFetchInitially.forEach((bucketDate) => { + assetStore.getAssetsByBucket(bucketDate); + }); }); function intersectedHandler(event: CustomEvent) { @@ -28,8 +50,12 @@ } } - const fetchData = () => { - assetStore.getAssetsByBucket('2022-08-01T00:00:00.000Z'); + const navigateAssetBackwardHandler = () => { + // assetStore.navigateAssetBackward(); + }; + + const navigateAssetForwardHandler = () => { + // assetStore.navigateAssetForward(); }; @@ -46,18 +72,17 @@ -
+
{#if intersecting} - - + {/if}
@@ -66,6 +91,19 @@ {/if}
+ + {#if $isViewingAssetStoreState} + { + assetGridStore.setIsViewingAsset(false); + }} + /> + {/if} + +