mirror of
https://github.com/immich-app/immich.git
synced 2026-03-26 20:00:44 +03:00
Merge branch 'main' of https://github.com/immich-app/immich into feat/library-offline-stats
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "2.6.1",
|
||||
"version": "2.6.2",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -72,10 +72,10 @@
|
||||
"@koddsson/eslint-plugin-tscompat": "^0.2.0",
|
||||
"@socket.io/component-emitter": "^3.1.0",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/enhanced-img": "^0.10.0",
|
||||
"@sveltejs/enhanced-img": "^0.10.4",
|
||||
"@sveltejs/kit": "^2.27.1",
|
||||
"@sveltejs/vite-plugin-svelte": "6.2.4",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
@@ -100,13 +100,13 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^6.0.0",
|
||||
"svelte": "5.53.7",
|
||||
"svelte": "5.53.13",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-eslint-parser": "^1.3.3",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"vite": "^7.1.2",
|
||||
"vite": "^8.0.0",
|
||||
"vitest": "^4.0.0"
|
||||
},
|
||||
"volta": {
|
||||
|
||||
@@ -212,12 +212,12 @@
|
||||
bottom: `${rootHeight - top}px`,
|
||||
left: `${left}px`,
|
||||
width: `${boundary.width}px`,
|
||||
maxHeight: maxHeight(top - dropdownOffset),
|
||||
maxHeight: maxHeight(boundary.top - dropdownOffset),
|
||||
};
|
||||
}
|
||||
|
||||
const viewportHeight = visualViewport?.height || rootHeight;
|
||||
const availableHeight = modalBounds ? rootHeight - bottom : viewportHeight - boundary.bottom;
|
||||
const viewportHeight = visualViewport?.height || window.innerHeight;
|
||||
const availableHeight = viewportHeight - boundary.bottom;
|
||||
return {
|
||||
top: `${bottom}px`,
|
||||
left: `${left}px`,
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
let { onClose }: Props = $props();
|
||||
|
||||
onMount(async () => {
|
||||
albums = await getAllAlbums({});
|
||||
// TODO the server should *really* just return all albums (paginated ideally)
|
||||
const ownedAlbums = await getAllAlbums({ shared: false });
|
||||
ownedAlbums.push.apply(ownedAlbums, await getAllAlbums({ shared: true }));
|
||||
albums = ownedAlbums;
|
||||
recentAlbums = albums.sort((a, b) => (new Date(a.updatedAt) > new Date(b.updatedAt) ? -1 : 1)).slice(0, 3);
|
||||
loading = false;
|
||||
});
|
||||
|
||||
@@ -8,17 +8,16 @@ import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||
import { user as authUser, preferences } from '$lib/stores/user.store';
|
||||
import type { AssetControlContext } from '$lib/types';
|
||||
import { getSharedLink, sleep } from '$lib/utils';
|
||||
import { getAssetMediaUrl, getSharedLink, sleep } from '$lib/utils';
|
||||
import { downloadUrl } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { asQueryString } from '$lib/utils/shared-links';
|
||||
import {
|
||||
AssetJobName,
|
||||
AssetMediaSize,
|
||||
AssetTypeEnum,
|
||||
AssetVisibility,
|
||||
getAssetInfo,
|
||||
getBaseUrl,
|
||||
runAssetJobs,
|
||||
updateAsset,
|
||||
type AssetJobsDto,
|
||||
@@ -308,6 +307,7 @@ export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: {
|
||||
{
|
||||
filename: asset.originalFileName,
|
||||
id: asset.id,
|
||||
cacheKey: asset.thumbhash,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -321,13 +321,12 @@ export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: {
|
||||
assets.push({
|
||||
filename: motionAsset.originalFileName,
|
||||
id: asset.livePhotoVideoId,
|
||||
cacheKey: motionAsset.thumbhash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queryParams = asQueryString(authManager.params);
|
||||
|
||||
for (const [i, { filename, id }] of assets.entries()) {
|
||||
for (const [i, { filename, id, cacheKey }] of assets.entries()) {
|
||||
if (i !== 0) {
|
||||
// play nice with Safari
|
||||
await sleep(500);
|
||||
@@ -335,12 +334,7 @@ export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: {
|
||||
|
||||
try {
|
||||
toastManager.primary($t('downloading_asset_filename', { values: { filename } }));
|
||||
downloadUrl(
|
||||
getBaseUrl() +
|
||||
`/assets/${id}/original` +
|
||||
(queryParams ? `?${queryParams}&edited=${edited}` : `?edited=${edited}`),
|
||||
filename,
|
||||
);
|
||||
downloadUrl(getAssetMediaUrl({ id, size: AssetMediaSize.Original, edited, cacheKey }), filename);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.error_downloading', { values: { filename } }));
|
||||
}
|
||||
|
||||
@@ -80,7 +80,34 @@ function createUploadStore() {
|
||||
};
|
||||
|
||||
const removeItem = (id: string) => {
|
||||
uploadAssets.update((uploadingAsset) => uploadingAsset.filter((a) => a.id != id));
|
||||
uploadAssets.update((uploadingAsset) => {
|
||||
const assetToRemove = uploadingAsset.find((a) => a.id === id);
|
||||
if (assetToRemove) {
|
||||
stats.update((stats) => {
|
||||
switch (assetToRemove.state) {
|
||||
case UploadState.DONE: {
|
||||
stats.success--;
|
||||
break;
|
||||
}
|
||||
|
||||
case UploadState.DUPLICATED: {
|
||||
stats.duplicates--;
|
||||
break;
|
||||
}
|
||||
|
||||
case UploadState.ERROR: {
|
||||
stats.errors--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stats.total--;
|
||||
return stats;
|
||||
});
|
||||
}
|
||||
|
||||
return uploadingAsset.filter((a) => a.id != id);
|
||||
});
|
||||
};
|
||||
|
||||
const dismissErrors = () =>
|
||||
|
||||
@@ -216,7 +216,7 @@ async function fileUploader({
|
||||
uploadAssetsStore.track('success');
|
||||
}
|
||||
|
||||
if (albumId) {
|
||||
if (albumId && !authManager.isSharedLink) {
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_adding_to_album') });
|
||||
await addAssetsToAlbums([albumId], [responseData.id], { notify: false });
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_added_to_album') });
|
||||
|
||||
@@ -287,7 +287,11 @@
|
||||
}
|
||||
};
|
||||
|
||||
const onAlbumAddAssets = async () => {
|
||||
const onAlbumAddAssets = async ({ albumIds }: { albumIds: string[] }) => {
|
||||
if (!albumIds.includes(album.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await refreshAlbum();
|
||||
timelineInteraction.clearMultiselect();
|
||||
await setModeToView();
|
||||
@@ -472,13 +476,6 @@
|
||||
<ChangeDate menuItem />
|
||||
<ChangeDescription menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
{#if assetInteraction.selectedAssets.length === 1}
|
||||
<MenuOption
|
||||
text={$t('set_as_album_cover')}
|
||||
icon={mdiImageOutline}
|
||||
onClick={() => updateThumbnailUsingCurrentSelection()}
|
||||
/>
|
||||
{/if}
|
||||
<ArchiveAction
|
||||
menuItem
|
||||
unarchive={assetInteraction.isAllArchived}
|
||||
@@ -486,6 +483,13 @@
|
||||
/>
|
||||
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||
{/if}
|
||||
{#if assetInteraction.selectedAssets.length === 1}
|
||||
<MenuOption
|
||||
text={$t('set_as_album_cover')}
|
||||
icon={mdiImageOutline}
|
||||
onClick={() => updateThumbnailUsingCurrentSelection()}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||
<TagAction menuItem />
|
||||
|
||||
@@ -178,19 +178,7 @@
|
||||
|
||||
const handleFirst = () => navigateToIndex(0);
|
||||
const handlePrevious = () => navigateToIndex(Math.max(duplicatesIndex - 1, 0));
|
||||
const handlePreviousShortcut = async () => {
|
||||
if ($showAssetViewer) {
|
||||
return;
|
||||
}
|
||||
await handlePrevious();
|
||||
};
|
||||
const handleNext = async () => navigateToIndex(Math.min(duplicatesIndex + 1, duplicates.length - 1));
|
||||
const handleNextShortcut = async () => {
|
||||
if ($showAssetViewer) {
|
||||
return;
|
||||
}
|
||||
await handleNext();
|
||||
};
|
||||
const handleLast = () => navigateToIndex(duplicates.length - 1);
|
||||
|
||||
const navigateToIndex = async (index: number) =>
|
||||
@@ -198,10 +186,12 @@
|
||||
</script>
|
||||
|
||||
<svelte:document
|
||||
use:shortcuts={[
|
||||
{ shortcut: { key: 'ArrowLeft' }, onShortcut: handlePreviousShortcut },
|
||||
{ shortcut: { key: 'ArrowRight' }, onShortcut: handleNextShortcut },
|
||||
]}
|
||||
use:shortcuts={$showAssetViewer
|
||||
? []
|
||||
: [
|
||||
{ shortcut: { key: 'ArrowLeft' }, onShortcut: handlePrevious },
|
||||
{ shortcut: { key: 'ArrowRight' }, onShortcut: handleNext },
|
||||
]}
|
||||
/>
|
||||
|
||||
<UserPageLayout title={data.meta.title + ` (${duplicates.length.toLocaleString($locale)})`} scrollbar={true}>
|
||||
|
||||
Reference in New Issue
Block a user