mirror of
https://github.com/immich-app/immich.git
synced 2025-01-07 20:36:48 +01:00
refactor(web): shared link key auth (#3855)
This commit is contained in:
parent
10c2bda3a9
commit
9bbef4a97b
21 changed files with 115 additions and 108 deletions
|
@ -39,6 +39,11 @@ export class ImmichApi {
|
||||||
public userApi: UserApi;
|
public userApi: UserApi;
|
||||||
|
|
||||||
private config: Configuration;
|
private config: Configuration;
|
||||||
|
private key?: string;
|
||||||
|
|
||||||
|
get isSharedLink() {
|
||||||
|
return !!this.key;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(params: ConfigurationParameters) {
|
constructor(params: ConfigurationParameters) {
|
||||||
this.config = new Configuration(params);
|
this.config = new Configuration(params);
|
||||||
|
@ -73,6 +78,14 @@ export class ImmichApi {
|
||||||
return (this.config.basePath || BASE_PATH) + toPathString(url);
|
return (this.config.basePath || BASE_PATH) + toPathString(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setKey(key: string) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKey(): string | undefined {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
public setAccessToken(accessToken: string) {
|
public setAccessToken(accessToken: string) {
|
||||||
this.config.accessToken = accessToken;
|
this.config.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
@ -85,14 +98,14 @@ export class ImmichApi {
|
||||||
this.config.basePath = baseUrl;
|
this.config.basePath = baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAssetFileUrl(...[assetId, isThumb, isWeb, key]: ApiParams<typeof AssetApiFp, 'serveFile'>) {
|
public getAssetFileUrl(...[assetId, isThumb, isWeb]: 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: this.getKey() });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAssetThumbnailUrl(...[assetId, format, key]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>) {
|
public getAssetThumbnailUrl(...[assetId, format]: 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: this.getKey() });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
|
public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
|
||||||
|
|
|
@ -27,13 +27,13 @@
|
||||||
|
|
||||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
const assetStore = new AssetStore({ size: TimeBucketSize.Month, albumId: album.id, key: sharedLink.key });
|
const assetStore = new AssetStore({ size: TimeBucketSize.Month, albumId: album.id });
|
||||||
const assetInteractionStore = createAssetInteractionStore();
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
||||||
|
|
||||||
dragAndDropFilesStore.subscribe((value) => {
|
dragAndDropFilesStore.subscribe((value) => {
|
||||||
if (value.isDragging && value.files.length > 0) {
|
if (value.isDragging && value.files.length > 0) {
|
||||||
fileUploadHandler(value.files, album.id, sharedLink.key);
|
fileUploadHandler(value.files, album.id);
|
||||||
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
dragAndDropFilesStore.set({ isDragging: false, files: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadAlbum = async () => {
|
const downloadAlbum = async () => {
|
||||||
await downloadArchive(`${album.albumName}.zip`, { albumId: album.id }, sharedLink.key);
|
await downloadArchive(`${album.albumName}.zip`, { albumId: album.id });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||||
{#if sharedLink.allowDownload}
|
{#if sharedLink.allowDownload}
|
||||||
<DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink.key} />
|
<DownloadAction filename="{album.albumName}.zip" />
|
||||||
{/if}
|
{/if}
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
{#if sharedLink.allowUpload}
|
{#if sharedLink.allowUpload}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
title="Add Photos"
|
title="Add Photos"
|
||||||
on:click={() => openFileUploadDialog(album.id, sharedLink.key)}
|
on:click={() => openFileUploadDialog(album.id)}
|
||||||
logo={FileImagePlusOutline}
|
logo={FileImagePlusOutline}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||||
>
|
>
|
||||||
<AssetGrid {assetStore} {assetInteractionStore} publicSharedKey={sharedLink.key}>
|
<AssetGrid {assetStore} {assetInteractionStore}>
|
||||||
<section class="pt-24">
|
<section class="pt-24">
|
||||||
<!-- ALBUM TITLE -->
|
<!-- ALBUM TITLE -->
|
||||||
<p
|
<p
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
|
|
||||||
export let assetStore: AssetStore | null = null;
|
export let assetStore: AssetStore | null = null;
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
|
||||||
export let showNavigation = true;
|
export let showNavigation = true;
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
||||||
|
@ -72,6 +71,10 @@
|
||||||
$: asset.id && !sharedLink && getAllAlbums(); // Update the album information when the asset ID changes
|
$: asset.id && !sharedLink && getAllAlbums(); // Update the album information when the asset ID changes
|
||||||
|
|
||||||
const getAllAlbums = async () => {
|
const getAllAlbums = async () => {
|
||||||
|
if (api.isSharedLink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
|
const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
|
||||||
appearsInAlbums = data;
|
appearsInAlbums = data;
|
||||||
|
@ -84,7 +87,9 @@
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'a':
|
case 'a':
|
||||||
case 'A':
|
case 'A':
|
||||||
if (shiftKey) toggleArchive();
|
if (shiftKey) {
|
||||||
|
toggleArchive();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
navigateAssetBackward();
|
navigateAssetBackward();
|
||||||
|
@ -94,7 +99,9 @@
|
||||||
return;
|
return;
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'D':
|
case 'D':
|
||||||
if (shiftKey) downloadFile(asset, publicSharedKey);
|
if (shiftKey) {
|
||||||
|
downloadFile(asset);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case 'Delete':
|
case 'Delete':
|
||||||
isShowDeleteConfirmation = true;
|
isShowDeleteConfirmation = true;
|
||||||
|
@ -272,7 +279,7 @@
|
||||||
showDownloadButton={shouldShowDownloadButton}
|
showDownloadButton={shouldShowDownloadButton}
|
||||||
on:goBack={closeViewer}
|
on:goBack={closeViewer}
|
||||||
on:showDetail={showDetailInfoHandler}
|
on:showDetail={showDetailInfoHandler}
|
||||||
on:download={() => downloadFile(asset, publicSharedKey)}
|
on:download={() => downloadFile(asset)}
|
||||||
on:delete={() => (isShowDeleteConfirmation = true)}
|
on:delete={() => (isShowDeleteConfirmation = true)}
|
||||||
on:favorite={toggleFavorite}
|
on:favorite={toggleFavorite}
|
||||||
on:addToAlbum={() => openAlbumPicker(false)}
|
on:addToAlbum={() => openAlbumPicker(false)}
|
||||||
|
@ -304,7 +311,6 @@
|
||||||
{:else if asset.type === AssetTypeEnum.Image}
|
{:else if asset.type === AssetTypeEnum.Image}
|
||||||
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
{publicSharedKey}
|
|
||||||
assetId={asset.livePhotoVideoId}
|
assetId={asset.livePhotoVideoId}
|
||||||
on:close={closeViewer}
|
on:close={closeViewer}
|
||||||
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
||||||
|
@ -312,12 +318,12 @@
|
||||||
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || asset.originalPath
|
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || asset.originalPath
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.endsWith('.insp')}
|
.endsWith('.insp')}
|
||||||
<PanoramaViewer {publicSharedKey} {asset} />
|
<PanoramaViewer {asset} />
|
||||||
{:else}
|
{:else}
|
||||||
<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
|
<PhotoViewer {asset} on:close={closeViewer} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
|
<VideoViewer assetId={asset.id} on:close={closeViewer} />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
@ -338,7 +344,6 @@
|
||||||
<DetailPanel
|
<DetailPanel
|
||||||
{asset}
|
{asset}
|
||||||
albums={appearsInAlbums}
|
albums={appearsInAlbums}
|
||||||
{sharedLink}
|
|
||||||
on:close={() => ($isShowDetail = false)}
|
on:close={() => ($isShowDetail = false)}
|
||||||
on:close-viewer={handleCloseViewer}
|
on:close-viewer={handleCloseViewer}
|
||||||
on:description-focus-in={disableKeyDownEvent}
|
on:description-focus-in={disableKeyDownEvent}
|
||||||
|
|
|
@ -9,21 +9,20 @@
|
||||||
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
|
import ImageOutline from 'svelte-material-icons/ImageOutline.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, ThumbnailFormat, SharedLinkResponseDto } from '@api';
|
import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
|
||||||
import { asByteUnitString } from '../../utils/byte-units';
|
import { asByteUnitString } from '../../utils/byte-units';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import { getAssetFilename } from '$lib/utils/asset-utils';
|
import { getAssetFilename } from '$lib/utils/asset-utils';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let albums: AlbumResponseDto[] = [];
|
export let albums: AlbumResponseDto[] = [];
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
|
||||||
|
|
||||||
let textarea: HTMLTextAreaElement;
|
let textarea: HTMLTextAreaElement;
|
||||||
let description: string;
|
let description: string;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// Get latest description from server
|
// Get latest description from server
|
||||||
if (asset.id && !sharedLink) {
|
if (asset.id && !api.isSharedLink) {
|
||||||
api.assetApi.getAssetById({ id: asset.id }).then((res) => {
|
api.assetApi.getAssetById({ id: asset.id }).then((res) => {
|
||||||
people = res.data?.people || [];
|
people = res.data?.people || [];
|
||||||
textarea.value = res.data?.exifInfo?.description || '';
|
textarea.value = res.data?.exifInfo?.description || '';
|
||||||
|
@ -91,13 +90,15 @@
|
||||||
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
|
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="mx-4 mt-10">
|
<section
|
||||||
|
class="mx-4 mt-10"
|
||||||
|
style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
|
||||||
|
>
|
||||||
<textarea
|
<textarea
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
class="max-h-[500px]
|
class="max-h-[500px]
|
||||||
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
||||||
placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
|
placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
|
||||||
style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
|
|
||||||
on:focusin={handleFocusIn}
|
on:focusin={handleFocusIn}
|
||||||
on:focusout={handleFocusOut}
|
on:focusout={handleFocusOut}
|
||||||
on:input={autoGrowHeight}
|
on:input={autoGrowHeight}
|
||||||
|
@ -106,7 +107,7 @@
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if people.length > 0}
|
{#if !api.isSharedLink && people.length > 0}
|
||||||
<section class="px-4 py-4 text-sm">
|
<section class="px-4 py-4 text-sm">
|
||||||
<h2>PEOPLE</h2>
|
<h2>PEOPLE</h2>
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,16 @@
|
||||||
import { api, AssetResponseDto } from '@api';
|
import { api, AssetResponseDto } from '@api';
|
||||||
import View360, { EquirectProjection } from '@egjs/svelte-view360';
|
import View360, { EquirectProjection } from '@egjs/svelte-view360';
|
||||||
import './panorama-viewer.css';
|
import './panorama-viewer.css';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
|
||||||
let dataUrl = '';
|
let dataUrl = '';
|
||||||
let errorMessage = '';
|
let errorMessage = '';
|
||||||
|
|
||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.serveFile(
|
const { data } = await api.assetApi.serveFile(
|
||||||
{ id: asset.id, isThumb: false, isWeb: false, key: publicSharedKey },
|
{ id: asset.id, isThumb: false, isWeb: false, key: api.getKey() },
|
||||||
{ responseType: 'blob' },
|
{ responseType: 'blob' },
|
||||||
);
|
);
|
||||||
if (data instanceof Blob) {
|
if (data instanceof Blob) {
|
||||||
|
|
|
@ -8,12 +8,10 @@
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
|
||||||
export let element: HTMLDivElement | undefined = undefined;
|
export let element: HTMLDivElement | undefined = undefined;
|
||||||
|
|
||||||
let imgElement: HTMLDivElement;
|
let imgElement: HTMLDivElement;
|
||||||
|
|
||||||
let assetData: string;
|
let assetData: string;
|
||||||
|
|
||||||
let copyImageToClipboard: (src: string) => Promise<Blob>;
|
let copyImageToClipboard: (src: string) => Promise<Blob>;
|
||||||
let canCopyImagesToClipboard: () => boolean;
|
let canCopyImagesToClipboard: () => boolean;
|
||||||
|
|
||||||
|
@ -28,7 +26,7 @@
|
||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.serveFile(
|
const { data } = await api.assetApi.serveFile(
|
||||||
{ id: asset.id, isThumb: false, isWeb: true, key: publicSharedKey },
|
{ id: asset.id, isThumb: false, isWeb: true, key: api.getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let publicSharedKey: string | undefined = undefined;
|
|
||||||
|
|
||||||
let isVideoLoading = true;
|
let isVideoLoading = true;
|
||||||
const dispatch = createEventDispatcher<{ onVideoEnded: void }>();
|
const dispatch = createEventDispatcher<{ onVideoEnded: void }>();
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
bind:volume={$videoViewerVolume}
|
bind:volume={$videoViewerVolume}
|
||||||
poster={api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg)}
|
poster={api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg)}
|
||||||
>
|
>
|
||||||
<source src={api.getAssetFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
|
<source src={api.getAssetFileUrl(assetId, false, true)} type="video/mp4" />
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
export let selectionCandidate = false;
|
export let selectionCandidate = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let readonly = false;
|
export let readonly = false;
|
||||||
export let publicSharedKey: string | undefined = undefined;
|
|
||||||
export let showArchiveIcon = false;
|
export let showArchiveIcon = false;
|
||||||
|
|
||||||
let mouseOver = false;
|
let mouseOver = false;
|
||||||
|
@ -118,13 +117,13 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Favorite asset star -->
|
<!-- Favorite asset star -->
|
||||||
{#if asset.isFavorite && !publicSharedKey}
|
{#if !api.isSharedLink && asset.isFavorite}
|
||||||
<div class="absolute bottom-2 left-2 z-10">
|
<div class="absolute bottom-2 left-2 z-10">
|
||||||
<Heart size="24" class="text-white" />
|
<Heart size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showArchiveIcon && asset.isArchived}
|
{#if !api.isSharedLink && showArchiveIcon && asset.isArchived}
|
||||||
<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
|
<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
|
||||||
<ArchiveArrowDownOutline size="24" class="text-white" />
|
<ArchiveArrowDownOutline size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -140,7 +139,7 @@
|
||||||
|
|
||||||
{#if asset.resized}
|
{#if asset.resized}
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
|
url={api.getAssetThumbnailUrl(asset.id, format)}
|
||||||
altText={asset.originalFileName}
|
altText={asset.originalFileName}
|
||||||
widthStyle="{width}px"
|
widthStyle="{width}px"
|
||||||
heightStyle="{height}px"
|
heightStyle="{height}px"
|
||||||
|
@ -156,7 +155,7 @@
|
||||||
{#if asset.type === AssetTypeEnum.Video}
|
{#if asset.type === AssetTypeEnum.Video}
|
||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
url={api.getAssetFileUrl(asset.id, false, true, publicSharedKey)}
|
url={api.getAssetFileUrl(asset.id, false, true)}
|
||||||
enablePlayback={mouseOver}
|
enablePlayback={mouseOver}
|
||||||
curve={selected}
|
curve={selected}
|
||||||
durationInSeconds={timeToSeconds(asset.duration)}
|
durationInSeconds={timeToSeconds(asset.duration)}
|
||||||
|
@ -167,7 +166,7 @@
|
||||||
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true, publicSharedKey)}
|
url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true)}
|
||||||
pauseIcon={MotionPauseOutline}
|
pauseIcon={MotionPauseOutline}
|
||||||
playIcon={MotionPlayOutline}
|
playIcon={MotionPlayOutline}
|
||||||
showTime={false}
|
showTime={false}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let filename = 'immich.zip';
|
export let filename = 'immich.zip';
|
||||||
export let sharedLinkKey: string | undefined = undefined;
|
|
||||||
export let menuItem = false;
|
export let menuItem = false;
|
||||||
|
|
||||||
const { getAssets, clearSelect } = getAssetControlContext();
|
const { getAssets, clearSelect } = getAssetControlContext();
|
||||||
|
@ -15,12 +14,12 @@
|
||||||
const assets = Array.from(getAssets());
|
const assets = Array.from(getAssets());
|
||||||
if (assets.length === 1) {
|
if (assets.length === 1) {
|
||||||
clearSelect();
|
clearSelect();
|
||||||
await downloadFile(assets[0], sharedLinkKey);
|
await downloadFile(assets[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelect();
|
clearSelect();
|
||||||
await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }, sharedLinkKey);
|
await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
assetIdsDto: {
|
assetIdsDto: {
|
||||||
assetIds: Array.from(getAssets()).map((asset) => asset.id),
|
assetIds: Array.from(getAssets()).map((asset) => asset.id),
|
||||||
},
|
},
|
||||||
key: sharedLink.key,
|
key: api.getKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
export let isSelectionMode = false;
|
export let isSelectionMode = false;
|
||||||
export let viewport: Viewport;
|
export let viewport: Viewport;
|
||||||
export let singleSelect = false;
|
export let singleSelect = false;
|
||||||
export let publicSharedKey: string | undefined = undefined;
|
|
||||||
|
|
||||||
export let assetStore: AssetStore;
|
export let assetStore: AssetStore;
|
||||||
export let assetInteractionStore: AssetInteractionStore;
|
export let assetInteractionStore: AssetInteractionStore;
|
||||||
|
@ -96,7 +95,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assetViewingStore.setAssetId(asset.id, publicSharedKey);
|
assetViewingStore.setAssetId(asset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
|
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
|
||||||
|
@ -189,7 +188,6 @@
|
||||||
disabled={$assetStore.albumAssets.has(asset.id)}
|
disabled={$assetStore.albumAssets.has(asset.id)}
|
||||||
thumbnailWidth={box.width}
|
thumbnailWidth={box.width}
|
||||||
thumbnailHeight={box.height}
|
thumbnailHeight={box.height}
|
||||||
{publicSharedKey}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
export let assetStore: AssetStore;
|
export let assetStore: AssetStore;
|
||||||
export let assetInteractionStore: AssetInteractionStore;
|
export let assetInteractionStore: AssetInteractionStore;
|
||||||
export let removeAction: AssetAction | null = null;
|
export let removeAction: AssetAction | null = null;
|
||||||
export let publicSharedKey: string | undefined = undefined;
|
|
||||||
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
|
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
|
||||||
assetInteractionStore;
|
assetInteractionStore;
|
||||||
const viewport: Viewport = { width: 0, height: 0 };
|
const viewport: Viewport = { width: 0, height: 0 };
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
const handlePrevious = async () => {
|
const handlePrevious = async () => {
|
||||||
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
||||||
if (previousAsset) {
|
if (previousAsset) {
|
||||||
assetViewingStore.setAssetId(previousAsset, publicSharedKey);
|
assetViewingStore.setAssetId(previousAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!previousAsset;
|
return !!previousAsset;
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
||||||
if (nextAsset) {
|
if (nextAsset) {
|
||||||
assetViewingStore.setAssetId(nextAsset, publicSharedKey);
|
assetViewingStore.setAssetId(nextAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!nextAsset;
|
return !!nextAsset;
|
||||||
|
@ -349,7 +349,6 @@
|
||||||
bucketDate={bucket.bucketDate}
|
bucketDate={bucket.bucketDate}
|
||||||
bucketHeight={bucket.bucketHeight}
|
bucketHeight={bucket.bucketHeight}
|
||||||
{viewport}
|
{viewport}
|
||||||
{publicSharedKey}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -371,7 +370,6 @@
|
||||||
on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
|
on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
|
||||||
on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
|
on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
|
||||||
on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
|
on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
|
||||||
{publicSharedKey}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Portal>
|
</Portal>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
import SelectAll from 'svelte-material-icons/SelectAll.svelte';
|
import SelectAll from 'svelte-material-icons/SelectAll.svelte';
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
|
|
||||||
|
@ -35,23 +34,23 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloadAssets = async () => {
|
const downloadAssets = async () => {
|
||||||
await downloadArchive(`immich-shared.zip`, { assetIds: assets.map((asset) => asset.id) }, sharedLink.key);
|
await downloadArchive(`immich-shared.zip`, { assetIds: assets.map((asset) => asset.id) });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadAssets = async (files: File[] = []) => {
|
const handleUploadAssets = async (files: File[] = []) => {
|
||||||
try {
|
try {
|
||||||
let results: (string | undefined)[] = [];
|
let results: (string | undefined)[] = [];
|
||||||
if (!files || files.length === 0 || !Array.isArray(files)) {
|
if (!files || files.length === 0 || !Array.isArray(files)) {
|
||||||
results = await openFileUploadDialog(undefined, sharedLink.key);
|
results = await openFileUploadDialog(undefined);
|
||||||
} else {
|
} else {
|
||||||
results = await fileUploadHandler(files, undefined, sharedLink.key);
|
results = await fileUploadHandler(files, undefined);
|
||||||
}
|
}
|
||||||
const { data } = await api.sharedLinkApi.addSharedLinkAssets({
|
const { data } = await api.sharedLinkApi.addSharedLinkAssets({
|
||||||
id: sharedLink.id,
|
id: sharedLink.id,
|
||||||
assetIdsDto: {
|
assetIdsDto: {
|
||||||
assetIds: results.filter((id) => !!id) as string[],
|
assetIds: results.filter((id) => !!id) as string[],
|
||||||
},
|
},
|
||||||
key: sharedLink.key,
|
key: api.getKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const added = data.filter((item) => item.success).length;
|
const added = data.filter((item) => item.success).length;
|
||||||
|
@ -75,7 +74,7 @@
|
||||||
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
|
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
|
||||||
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
|
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
|
||||||
{#if sharedLink?.allowDownload}
|
{#if sharedLink?.allowDownload}
|
||||||
<DownloadAction filename="immich-shared.zip" sharedLinkKey={sharedLink.key} />
|
<DownloadAction filename="immich-shared.zip" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if isOwned}
|
{#if isOwned}
|
||||||
<RemoveFromSharedLink bind:sharedLink />
|
<RemoveFromSharedLink bind:sharedLink />
|
||||||
|
@ -106,6 +105,6 @@
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="my-[160px] flex flex-col px-6 sm:px-12 md:px-24 lg:px-40">
|
<section class="my-[160px] flex flex-col px-6 sm:px-12 md:px-24 lg:px-40">
|
||||||
<GalleryViewer {assets} {sharedLink} bind:selectedAssets />
|
<GalleryViewer {assets} bind:selectedAssets />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
|
import { AssetResponseDto, ThumbnailFormat } from '@api';
|
||||||
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { getThumbnailSize } from '$lib/utils/thumbnail-util';
|
import { getThumbnailSize } from '$lib/utils/thumbnail-util';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
||||||
export let assets: AssetResponseDto[];
|
export let assets: AssetResponseDto[];
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
|
||||||
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
export let disableAssetSelect = false;
|
export let disableAssetSelect = false;
|
||||||
export let showArchiveIcon = false;
|
export let showArchiveIcon = false;
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
{asset}
|
{asset}
|
||||||
{thumbnailSize}
|
{thumbnailSize}
|
||||||
readonly={disableAssetSelect}
|
readonly={disableAssetSelect}
|
||||||
publicSharedKey={sharedLink?.key}
|
|
||||||
format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
|
format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
|
||||||
on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
|
on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
|
||||||
on:select={selectAssetHandler}
|
on:select={selectAssetHandler}
|
||||||
|
@ -106,8 +104,6 @@
|
||||||
{#if $showAssetViewer}
|
{#if $showAssetViewer}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={selectedAsset}
|
asset={selectedAsset}
|
||||||
publicSharedKey={sharedLink?.key}
|
|
||||||
{sharedLink}
|
|
||||||
on:previous={navigateAssetBackward}
|
on:previous={navigateAssetBackward}
|
||||||
on:next={navigateAssetForward}
|
on:next={navigateAssetForward}
|
||||||
on:close={closeViewer}
|
on:close={closeViewer}
|
||||||
|
|
|
@ -5,8 +5,8 @@ function createAssetViewingStore() {
|
||||||
const viewingAssetStoreState = writable<AssetResponseDto>();
|
const viewingAssetStoreState = writable<AssetResponseDto>();
|
||||||
const viewState = writable<boolean>(false);
|
const viewState = writable<boolean>(false);
|
||||||
|
|
||||||
const setAssetId = async (id: string, key?: string) => {
|
const setAssetId = async (id: string) => {
|
||||||
const { data } = await api.assetApi.getAssetById({ id, key });
|
const { data } = await api.assetApi.getAssetById({ id, key: api.getKey() });
|
||||||
viewingAssetStoreState.set(data);
|
viewingAssetStoreState.set(data);
|
||||||
viewState.set(true);
|
viewState.set(true);
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,7 +58,10 @@ export class AssetStore {
|
||||||
this.assetToBucket = {};
|
this.assetToBucket = {};
|
||||||
this.albumAssets = new Set();
|
this.albumAssets = new Set();
|
||||||
|
|
||||||
const { data: buckets } = await api.assetApi.getTimeBuckets(this.options);
|
const { data: buckets } = await api.assetApi.getTimeBuckets({
|
||||||
|
...this.options,
|
||||||
|
key: api.getKey(),
|
||||||
|
});
|
||||||
|
|
||||||
this.buckets = buckets.map((bucket) => {
|
this.buckets = buckets.map((bucket) => {
|
||||||
const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10);
|
const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10);
|
||||||
|
@ -107,7 +110,11 @@ export class AssetStore {
|
||||||
bucket.cancelToken = new AbortController();
|
bucket.cancelToken = new AbortController();
|
||||||
|
|
||||||
const { data: assets } = await api.assetApi.getByTimeBucket(
|
const { data: assets } = await api.assetApi.getByTimeBucket(
|
||||||
{ ...this.options, timeBucket: bucketDate },
|
{
|
||||||
|
...this.options,
|
||||||
|
timeBucket: bucketDate,
|
||||||
|
key: api.getKey(),
|
||||||
|
},
|
||||||
{ signal: bucket.cancelToken.signal },
|
{ signal: bucket.cancelToken.signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -117,7 +124,7 @@ export class AssetStore {
|
||||||
albumId: this.albumId,
|
albumId: this.albumId,
|
||||||
timeBucket: bucketDate,
|
timeBucket: bucketDate,
|
||||||
size: this.options.size,
|
size: this.options.size,
|
||||||
key: this.options.key,
|
key: api.getKey(),
|
||||||
},
|
},
|
||||||
{ signal: bucket.cancelToken.signal },
|
{ signal: bucket.cancelToken.signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,12 +3,14 @@ import { downloadManager } from '$lib/stores/download';
|
||||||
import { api, BulkIdResponseDto, AssetResponseDto, DownloadResponseDto, DownloadInfoDto } from '@api';
|
import { api, BulkIdResponseDto, AssetResponseDto, DownloadResponseDto, DownloadInfoDto } from '@api';
|
||||||
import { handleError } from './handle-error';
|
import { handleError } from './handle-error';
|
||||||
|
|
||||||
export const addAssetsToAlbum = async (
|
export const addAssetsToAlbum = async (albumId: string, assetIds: Array<string>): Promise<BulkIdResponseDto[]> =>
|
||||||
albumId: string,
|
api.albumApi
|
||||||
assetIds: Array<string>,
|
.addAssetsToAlbum({
|
||||||
key: string | undefined = undefined,
|
id: albumId,
|
||||||
): Promise<BulkIdResponseDto[]> =>
|
bulkIdsDto: { ids: assetIds },
|
||||||
api.albumApi.addAssetsToAlbum({ id: albumId, bulkIdsDto: { ids: assetIds }, key }).then(({ data: results }) => {
|
key: api.getKey(),
|
||||||
|
})
|
||||||
|
.then(({ data: results }) => {
|
||||||
const count = results.filter(({ success }) => success).length;
|
const count = results.filter(({ success }) => success).length;
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
|
@ -32,11 +34,11 @@ const downloadBlob = (data: Blob, filename: string) => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadArchive = async (fileName: string, options: DownloadInfoDto, key?: string) => {
|
export const downloadArchive = async (fileName: string, options: DownloadInfoDto) => {
|
||||||
let downloadInfo: DownloadResponseDto | null = null;
|
let downloadInfo: DownloadResponseDto | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.getDownloadInfo({ downloadInfoDto: options, key });
|
const { data } = await api.assetApi.getDownloadInfo({ downloadInfoDto: options, key: api.getKey() });
|
||||||
downloadInfo = data;
|
downloadInfo = data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to download files');
|
handleError(error, 'Unable to download files');
|
||||||
|
@ -61,7 +63,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.downloadArchive(
|
const { data } = await api.assetApi.downloadArchive(
|
||||||
{ assetIdsDto: { assetIds: archive.assetIds }, key },
|
{ assetIdsDto: { assetIds: archive.assetIds }, key: api.getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
|
@ -80,7 +82,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
|
export const downloadFile = async (asset: AssetResponseDto) => {
|
||||||
const assets = [
|
const assets = [
|
||||||
{
|
{
|
||||||
filename: `${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`,
|
filename: `${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`,
|
||||||
|
@ -104,7 +106,7 @@ export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
|
||||||
downloadManager.add(downloadKey, size, abort);
|
downloadManager.add(downloadKey, size, abort);
|
||||||
|
|
||||||
const { data } = await api.assetApi.downloadFile(
|
const { data } = await api.assetApi.downloadFile(
|
||||||
{ id, key },
|
{ id, key: api.getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
onDownloadProgress: (event: ProgressEvent) => {
|
onDownloadProgress: (event: ProgressEvent) => {
|
||||||
|
|
|
@ -14,10 +14,7 @@ const getExtensions = async () => {
|
||||||
return _extensions;
|
return _extensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openFileUploadDialog = async (
|
export const openFileUploadDialog = async (albumId: string | undefined = undefined) => {
|
||||||
albumId: string | undefined = undefined,
|
|
||||||
sharedKey: string | undefined = undefined,
|
|
||||||
) => {
|
|
||||||
const extensions = await getExtensions();
|
const extensions = await getExtensions();
|
||||||
|
|
||||||
return new Promise<(string | undefined)[]>((resolve, reject) => {
|
return new Promise<(string | undefined)[]>((resolve, reject) => {
|
||||||
|
@ -34,7 +31,7 @@ export const openFileUploadDialog = async (
|
||||||
}
|
}
|
||||||
const files = Array.from(target.files);
|
const files = Array.from(target.files);
|
||||||
|
|
||||||
resolve(fileUploadHandler(files, albumId, sharedKey));
|
resolve(fileUploadHandler(files, albumId));
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSelector.click();
|
fileSelector.click();
|
||||||
|
@ -45,18 +42,14 @@ export const openFileUploadDialog = async (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fileUploadHandler = async (
|
export const fileUploadHandler = async (files: File[], albumId: string | undefined = undefined) => {
|
||||||
files: File[],
|
|
||||||
albumId: string | undefined = undefined,
|
|
||||||
sharedKey: string | undefined = undefined,
|
|
||||||
) => {
|
|
||||||
const extensions = await getExtensions();
|
const extensions = await getExtensions();
|
||||||
const iterable = {
|
const iterable = {
|
||||||
files: files.filter((file) => extensions.some((ext) => file.name.toLowerCase().endsWith(ext)))[Symbol.iterator](),
|
files: files.filter((file) => extensions.some((ext) => file.name.toLowerCase().endsWith(ext)))[Symbol.iterator](),
|
||||||
|
|
||||||
async *[Symbol.asyncIterator]() {
|
async *[Symbol.asyncIterator]() {
|
||||||
for (const file of this.files) {
|
for (const file of this.files) {
|
||||||
yield fileUploader(file, albumId, sharedKey);
|
yield fileUploader(file, albumId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -78,11 +71,7 @@ const fromAsync = async function <T>(iterable: AsyncIterable<T>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: should probably use the @api SDK
|
// TODO: should probably use the @api SDK
|
||||||
async function fileUploader(
|
async function fileUploader(asset: File, albumId: string | undefined = undefined): Promise<string | undefined> {
|
||||||
asset: File,
|
|
||||||
albumId: string | undefined = undefined,
|
|
||||||
sharedKey: string | undefined = undefined,
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const fileCreatedAt = new Date(asset.lastModified).toISOString();
|
const fileCreatedAt = new Date(asset.lastModified).toISOString();
|
||||||
const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
|
const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
|
||||||
|
@ -103,9 +92,7 @@ async function fileUploader(
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await axios.post('/api/asset/upload', formData, {
|
const response = await axios.post('/api/asset/upload', formData, {
|
||||||
params: {
|
params: { key: api.getKey() },
|
||||||
key: sharedKey,
|
|
||||||
},
|
|
||||||
onUploadProgress: (event) => {
|
onUploadProgress: (event) => {
|
||||||
const percentComplete = Math.floor((event.loaded / event.total) * 100);
|
const percentComplete = Math.floor((event.loaded / event.total) * 100);
|
||||||
uploadAssetsStore.updateProgress(deviceAssetId, percentComplete);
|
uploadAssetsStore.updateProgress(deviceAssetId, percentComplete);
|
||||||
|
@ -120,7 +107,7 @@ async function fileUploader(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumId && res.id) {
|
if (albumId && res.id) {
|
||||||
await addAssetsToAlbum(albumId, [res.id], sharedKey);
|
await addAssetsToAlbum(albumId, [res.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -174,7 +174,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectFromComputer = async () => {
|
const handleSelectFromComputer = async () => {
|
||||||
await openFileUploadDialog(album.id, '');
|
await openFileUploadDialog(album.id);
|
||||||
timelineInteractionStore.clearMultiselect();
|
timelineInteractionStore.clearMultiselect();
|
||||||
viewMode = ViewMode.VIEW;
|
viewMode = ViewMode.VIEW;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
{#if data.asset && data.key}
|
{#if data.asset && data.key}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={data.asset}
|
asset={data.asset}
|
||||||
publicSharedKey={data.key}
|
|
||||||
showNavigation={false}
|
showNavigation={false}
|
||||||
on:previous={() => null}
|
on:previous={() => null}
|
||||||
on:next={() => null}
|
on:next={() => null}
|
||||||
|
|
|
@ -17,11 +17,16 @@
|
||||||
import { loadFeatureFlags } from '$lib/stores/feature-flags.store';
|
import { loadFeatureFlags } from '$lib/stores/feature-flags.store';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
|
import { api } from '@api';
|
||||||
|
|
||||||
let showNavigationLoadingBar = false;
|
let showNavigationLoadingBar = false;
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
let albumId: string | undefined;
|
let albumId: string | undefined;
|
||||||
|
|
||||||
|
if ($page.route.id?.startsWith('/(user)/share/[key]')) {
|
||||||
|
api.setKey($page.params.key);
|
||||||
|
}
|
||||||
|
|
||||||
beforeNavigate(() => {
|
beforeNavigate(() => {
|
||||||
showNavigationLoadingBar = true;
|
showNavigationLoadingBar = true;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue