mirror of
https://github.com/immich-app/immich.git
synced 2025-01-12 15:32:36 +02:00
refactor(web): use ImmichApi to create urls (#2435)
This commit is contained in:
parent
15fa8250cb
commit
4524aa0d06
@ -2,6 +2,7 @@ import {
|
|||||||
AlbumApi,
|
AlbumApi,
|
||||||
APIKeyApi,
|
APIKeyApi,
|
||||||
AssetApi,
|
AssetApi,
|
||||||
|
AssetApiFp,
|
||||||
AuthenticationApi,
|
AuthenticationApi,
|
||||||
Configuration,
|
Configuration,
|
||||||
ConfigurationParameters,
|
ConfigurationParameters,
|
||||||
@ -11,11 +12,12 @@ import {
|
|||||||
ServerInfoApi,
|
ServerInfoApi,
|
||||||
ShareApi,
|
ShareApi,
|
||||||
SystemConfigApi,
|
SystemConfigApi,
|
||||||
ThumbnailFormat,
|
UserApi,
|
||||||
UserApi
|
UserApiFp
|
||||||
} from './open-api';
|
} from './open-api';
|
||||||
import { BASE_PATH } from './open-api/base';
|
import { BASE_PATH } from './open-api/base';
|
||||||
import { DUMMY_BASE_URL, toPathString } from './open-api/common';
|
import { DUMMY_BASE_URL, toPathString } from './open-api/common';
|
||||||
|
import type { ApiParams } from './types';
|
||||||
|
|
||||||
export class ImmichApi {
|
export class ImmichApi {
|
||||||
public userApi: UserApi;
|
public userApi: UserApi;
|
||||||
@ -75,15 +77,24 @@ export class ImmichApi {
|
|||||||
this.config.basePath = baseUrl;
|
this.config.basePath = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAssetFileUrl(assetId: string, isThumb?: boolean, isWeb?: boolean, key?: string) {
|
public getAssetFileUrl(
|
||||||
|
...[assetId, isThumb, isWeb, key]: ApiParams<typeof AssetApiFp, 'serveFile'>
|
||||||
|
) {
|
||||||
const path = `/asset/file/${assetId}`;
|
const path = `/asset/file/${assetId}`;
|
||||||
return this.createUrl(path, { isThumb, isWeb, key });
|
return this.createUrl(path, { isThumb, isWeb, key });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAssetThumbnailUrl(assetId: string, format?: ThumbnailFormat, key?: string) {
|
public getAssetThumbnailUrl(
|
||||||
|
...[assetId, format, key]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>
|
||||||
|
) {
|
||||||
const path = `/asset/thumbnail/${assetId}`;
|
const path = `/asset/thumbnail/${assetId}`;
|
||||||
return this.createUrl(path, { format, key });
|
return this.createUrl(path, { format, key });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
|
||||||
|
const path = `/user/profile-image/${userId}`;
|
||||||
|
return this.createUrl(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = new ImmichApi({ basePath: '/api' });
|
export const api = new ImmichApi({ basePath: '/api' });
|
||||||
|
12
web/src/api/types.ts
Normal file
12
web/src/api/types.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type { Configuration } from './open-api';
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
export type ApiFp = (configuration: Configuration) => Record<any, (...args: any) => any>;
|
||||||
|
|
||||||
|
export type OmitLast<T extends readonly unknown[]> = T extends readonly [...infer U, any?]
|
||||||
|
? U
|
||||||
|
: [...T];
|
||||||
|
|
||||||
|
export type ApiParams<F extends ApiFp, K extends keyof ReturnType<F>> = OmitLast<
|
||||||
|
Parameters<ReturnType<F>[K]>
|
||||||
|
>;
|
@ -2,19 +2,6 @@ import { AxiosError, AxiosPromise } from 'axios';
|
|||||||
import { api } from './api';
|
import { api } from './api';
|
||||||
import { UserResponseDto } from './open-api';
|
import { UserResponseDto } from './open-api';
|
||||||
|
|
||||||
const _basePath = '/api';
|
|
||||||
|
|
||||||
export function getFileUrl(assetId: string, isThumb?: boolean, isWeb?: boolean, key?: string) {
|
|
||||||
const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file/${assetId}`);
|
|
||||||
|
|
||||||
if (isThumb !== undefined && isThumb !== null)
|
|
||||||
urlObj.searchParams.append('isThumb', `${isThumb}`);
|
|
||||||
if (isWeb !== undefined && isWeb !== null) urlObj.searchParams.append('isWeb', `${isWeb}`);
|
|
||||||
|
|
||||||
if (key !== undefined && key !== null) urlObj.searchParams.append('key', key);
|
|
||||||
return urlObj.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiError = AxiosError<{ message: string }>;
|
export type ApiError = AxiosError<{ message: string }>;
|
||||||
|
|
||||||
export const oauth = {
|
export const oauth = {
|
||||||
|
@ -79,11 +79,6 @@ describe('AlbumCard component', () => {
|
|||||||
const albumImgElement = sut.getByTestId('album-image');
|
const albumImgElement = sut.getByTestId('album-image');
|
||||||
const albumNameElement = sut.getByTestId('album-name');
|
const albumNameElement = sut.getByTestId('album-name');
|
||||||
const albumDetailsElement = sut.getByTestId('album-details');
|
const albumDetailsElement = sut.getByTestId('album-details');
|
||||||
// TODO: is this expected?
|
|
||||||
expect(albumImgElement).toHaveAttribute(
|
|
||||||
'src',
|
|
||||||
'/api/asset/thumbnail/thumbnailIdOne?format=WEBP'
|
|
||||||
);
|
|
||||||
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
||||||
|
|
||||||
await waitFor(() => expect(albumImgElement).toHaveAttribute('src', thumbnailUrl));
|
await waitFor(() => expect(albumImgElement).toHaveAttribute('src', thumbnailUrl));
|
||||||
|
@ -23,10 +23,9 @@
|
|||||||
export let isSharingView = false;
|
export let isSharingView = false;
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
|
|
||||||
let imageData = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`;
|
$: imageData = album.albumThumbnailAssetId
|
||||||
if (!album.albumThumbnailAssetId) {
|
? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
|
||||||
imageData = noThumbnailUrl;
|
: noThumbnailUrl;
|
||||||
}
|
|
||||||
|
|
||||||
const dispatchClick = createEventDispatcher<OnClick>();
|
const dispatchClick = createEventDispatcher<OnClick>();
|
||||||
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
|
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlbumResponseDto, ThumbnailFormat } from '@api';
|
import { AlbumResponseDto, ThumbnailFormat, api } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
const dispatcher = createEventDispatcher();
|
const dispatcher = createEventDispatcher();
|
||||||
@ -29,7 +29,8 @@
|
|||||||
>
|
>
|
||||||
<div class="h-12 w-12">
|
<div class="h-12 w-12">
|
||||||
<img
|
<img
|
||||||
src={`/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`}
|
src={album.albumThumbnailAssetId &&
|
||||||
|
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
|
||||||
alt={album.albumName}
|
alt={album.albumName}
|
||||||
class={`object-cover h-full w-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`}
|
class={`object-cover h-full w-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`}
|
||||||
data-testid="album-image"
|
data-testid="album-image"
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
import CameraIris from 'svelte-material-icons/CameraIris.svelte';
|
||||||
import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
|
import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { AssetResponseDto, AlbumResponseDto, api } from '@api';
|
import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
|
||||||
import { asByteUnitString } from '../../utils/byte-units';
|
import { asByteUnitString } from '../../utils/byte-units';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@ -241,7 +241,8 @@
|
|||||||
<img
|
<img
|
||||||
alt={album.albumName}
|
alt={album.albumName}
|
||||||
class="w-[50px] h-[50px] object-cover rounded"
|
class="w-[50px] h-[50px] object-cover rounded"
|
||||||
src={`/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=JPEG`}
|
src={album.albumThumbnailAssetId &&
|
||||||
|
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { api } from '@api';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { getFileUrl } from '@api';
|
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let publicSharedKey = '';
|
export let publicSharedKey: string | undefined = undefined;
|
||||||
|
|
||||||
let isVideoLoading = true;
|
let isVideoLoading = true;
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
@ -31,7 +31,7 @@
|
|||||||
on:canplay={handleCanPlay}
|
on:canplay={handleCanPlay}
|
||||||
on:ended={() => dispatch('onVideoEnded')}
|
on:ended={() => dispatch('onVideoEnded')}
|
||||||
>
|
>
|
||||||
<source src={getFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
|
<source src={api.getAssetFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { UserResponseDto } from '@api';
|
import { UserResponseDto, api } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import Cog from 'svelte-material-icons/Cog.svelte';
|
import Cog from 'svelte-material-icons/Cog.svelte';
|
||||||
import Logout from 'svelte-material-icons/Logout.svelte';
|
import Logout from 'svelte-material-icons/Logout.svelte';
|
||||||
@ -35,7 +34,7 @@
|
|||||||
<img
|
<img
|
||||||
transition:fade={{ duration: 100 }}
|
transition:fade={{ duration: 100 }}
|
||||||
class:hidden={showProfilePictureFallback}
|
class:hidden={showProfilePictureFallback}
|
||||||
src={`${$page.url.origin}/api/user/profile-image/${user.id}`}
|
src={api.getProfileImageUrl(user.id)}
|
||||||
alt="profile-img"
|
alt="profile-img"
|
||||||
class="inline rounded-full h-20 w-20 object-cover shadow-md border-2 border-immich-primary dark:border-immich-dark-primary"
|
class="inline rounded-full h-20 w-20 object-cover shadow-md border-2 border-immich-primary dark:border-immich-dark-primary"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
@ -126,7 +126,7 @@
|
|||||||
{#if user.profileImagePath}
|
{#if user.profileImagePath}
|
||||||
<img
|
<img
|
||||||
class:hidden={showProfilePictureFallback}
|
class:hidden={showProfilePictureFallback}
|
||||||
src={`${$page.url.origin}/api/user/profile-image/${user.id}`}
|
src={api.getProfileImageUrl(user.id)}
|
||||||
alt="profile-img"
|
alt="profile-img"
|
||||||
class="inline rounded-full h-12 w-12 object-cover shadow-md border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-all"
|
class="inline rounded-full h-12 w-12 object-cover shadow-md border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-all"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, AssetResponseDto, SharedLinkResponseDto, SharedLinkType } from '@api';
|
import {
|
||||||
|
api,
|
||||||
|
AssetResponseDto,
|
||||||
|
SharedLinkResponseDto,
|
||||||
|
SharedLinkType,
|
||||||
|
ThumbnailFormat
|
||||||
|
} from '@api';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import OpenInNew from 'svelte-material-icons/OpenInNew.svelte';
|
import OpenInNew from 'svelte-material-icons/OpenInNew.svelte';
|
||||||
import Delete from 'svelte-material-icons/TrashCanOutline.svelte';
|
import Delete from 'svelte-material-icons/TrashCanOutline.svelte';
|
||||||
@ -69,7 +75,7 @@
|
|||||||
{:then asset}
|
{:then asset}
|
||||||
<img
|
<img
|
||||||
id={asset.id}
|
id={asset.id}
|
||||||
src={`/api/asset/thumbnail/${asset.id}?format=WEBP`}
|
src={api.getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
|
||||||
alt={asset.id}
|
alt={asset.id}
|
||||||
class="object-cover w-[100px] h-[100px] rounded-lg"
|
class="object-cover w-[100px] h-[100px] rounded-lg"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
@ -1,18 +1,10 @@
|
|||||||
import { api, AddAssetsResponseDto, AssetResponseDto, ThumbnailFormat } from '@api';
|
import { api, AddAssetsResponseDto, AssetResponseDto } from '@api';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { downloadAssets } from '$lib/stores/download';
|
import { downloadAssets } from '$lib/stores/download';
|
||||||
|
|
||||||
export const getThumbnailUrl = (assetId: string, format: ThumbnailFormat, key?: string) => {
|
|
||||||
let url = `/api/asset/thumbnail/${assetId}?format=${format}`;
|
|
||||||
if (key) {
|
|
||||||
url += `&key=${key}`;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addAssetsToAlbum = async (
|
export const addAssetsToAlbum = async (
|
||||||
albumId: string,
|
albumId: string,
|
||||||
assetIds: Array<string>,
|
assetIds: Array<string>,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import { getThumbnailUrl } from '$lib/utils/asset-utils';
|
import { ThumbnailFormat, api as clientApi } from '@api';
|
||||||
import { ThumbnailFormat } from '@api';
|
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import featurePanelUrl from '$lib/assets/feature-panel.png';
|
import featurePanelUrl from '$lib/assets/feature-panel.png';
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ export const load = (async ({ params, locals: { api } }) => {
|
|||||||
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
|
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
|
||||||
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
||||||
imageUrl: assetId
|
imageUrl: assetId
|
||||||
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
? clientApi.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
||||||
: featurePanelUrl
|
: featurePanelUrl
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user