Skip to content

Commit

Permalink
Merge branch 'main' into improved-shared-link-viewing
Browse files Browse the repository at this point in the history
  • Loading branch information
TapuCosmo authored Sep 3, 2024
2 parents a71bebc + 7c97857 commit 692540d
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 102 deletions.
4 changes: 2 additions & 2 deletions docs/docs/features/libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ The `immich-server` container will need access to the gallery. Modify your docke
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/nas/christmas-trip:/mnt/nas/christmas-trip:ro
+ - /home/user/old-pics:/home/user/old-pics:ro
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
+ - /mnt/media/videos2:/mnt/media/videos2 # the files in this folder can be deleted, as it does not end with :ro
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
Expand Down
4 changes: 2 additions & 2 deletions mobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"
android:largeHeap="true" android:enableOnBackInvokedCallback="true">
android:largeHeap="true" android:enableOnBackInvokedCallback="false">

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
Expand Down Expand Up @@ -94,4 +94,4 @@
<data android:scheme="geo" />
</intent>
</queries>
</manifest>
</manifest>
1 change: 1 addition & 0 deletions mobile/lib/services/background.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ class BackgroundService {
Future<bool> _onAssetsChanged() async {
final Isar db = await loadDb();

HttpOverrides.global = HttpSSLCertOverride();
ApiService apiService = ApiService();
apiService.setAccessToken(Store.get(StoreKey.accessToken));
AppSettingsService settingService = AppSettingsService();
Expand Down
21 changes: 21 additions & 0 deletions server/src/migrations/1725258039306-UpsertMissingAssetJobStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class UpsertMissingAssetJobStatus1725258039306 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`INSERT INTO "asset_job_status" ("assetId", "facesRecognizedAt", "metadataExtractedAt", "duplicatesDetectedAt", "previewAt", "thumbnailAt") SELECT "assetId", NULL, NULL, NULL, NULL, NULL FROM "asset_files" f WHERE "f"."path" IS NOT NULL ON CONFLICT DO NOTHING`,
);

await queryRunner.query(
`UPDATE "asset_job_status" SET "previewAt" = NOW() FROM "asset_files" f WHERE "previewAt" IS NULL AND "asset_job_status"."assetId" = "f"."assetId" AND "f"."type" = 'preview' AND "f"."path" IS NOT NULL`,
);

await queryRunner.query(
`UPDATE "asset_job_status" SET "thumbnailAt" = NOW() FROM "asset_files" f WHERE "thumbnailAt" IS NULL AND "asset_job_status"."assetId" = "f"."assetId" AND "f"."type" = 'thumbnail' AND "f"."path" IS NOT NULL`,
);
}

public async down(): Promise<void> {
// do nothing
}
}
2 changes: 1 addition & 1 deletion server/src/repositories/asset.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export class AssetRepository implements IAssetRepository {

switch (property) {
case WithoutProperty.THUMBNAIL: {
relations = { jobStatus: true };
relations = { jobStatus: true, files: true };
where = [
{ jobStatus: { previewAt: IsNull() }, isVisible: true },
{ jobStatus: { thumbnailAt: IsNull() }, isVisible: true },
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/components/asset-viewer/detail-panel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@
<section class="px-6 pt-6 dark:text-immich-dark-fg">
<p class="pb-4 text-sm">{$t('appears_in').toUpperCase()}</p>
{#each albums as album}
<a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
<a href="{AppRoute.ALBUMS}/{album.id}">
<div class="flex gap-4 pt-2 hover:cursor-pointer items-center">
<div>
<img
Expand Down
15 changes: 13 additions & 2 deletions web/src/lib/components/asset-viewer/panorama-viewer.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script lang="ts">
import { getAssetOriginalUrl, getKey } from '$lib/utils';
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
import { AssetMediaSize, AssetTypeEnum, viewAsset, type AssetResponseDto } from '@immich/sdk';
import type { AdapterConstructor, PluginConstructor } from '@photo-sphere-viewer/core';
import { fade } from 'svelte/transition';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { t } from 'svelte-i18n';
export let asset: Pick<AssetResponseDto, 'id' | 'type'>;
export let asset: { id: string; type: AssetTypeEnum.Video } | AssetResponseDto;
const photoSphereConfigs =
asset.type === AssetTypeEnum.Video
Expand All @@ -27,14 +28,24 @@
const url = URL.createObjectURL(data);
return url;
};
const originalImageUrl =
asset.type === AssetTypeEnum.Image && isWebCompatibleImage(asset) ? getAssetOriginalUrl(asset.id) : null;
</script>

<div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
<!-- the photo sphere viewer is quite large, so lazy load it in parallel with loading the data -->
{#await Promise.all([loadAssetData(), import('./photo-sphere-viewer-adapter.svelte'), ...photoSphereConfigs])}
<LoadingSpinner />
{:then [data, module, adapter, plugins, navbar]}
<svelte:component this={module.default} panorama={data} plugins={plugins ?? undefined} {navbar} {adapter} />
<svelte:component
this={module.default}
panorama={data}
plugins={plugins ?? undefined}
{navbar}
{adapter}
{originalImageUrl}
/>
{:catch}
{$t('errors.failed_to_load_asset')}
{/await}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import {
Viewer,
events,
EquirectangularAdapter,
type PluginConstructor,
type AdapterConstructor,
Expand All @@ -9,6 +10,7 @@
import { onDestroy, onMount } from 'svelte';
export let panorama: string | { source: string };
export let originalImageUrl: string | null;
export let adapter: AdapterConstructor | [AdapterConstructor, unknown] = EquirectangularAdapter;
export let plugins: (PluginConstructor | [PluginConstructor, unknown])[] = [];
export let navbar = false;
Expand All @@ -28,6 +30,20 @@
maxFov: 180,
fisheye: true,
});
if (originalImageUrl) {
const zoomHandler = ({ zoomLevel }: events.ZoomUpdatedEvent) => {
// zoomLevel range: [0, 100]
if (Math.round(zoomLevel) >= 75) {
// Replace the preview with the original
viewer.setPanorama(originalImageUrl, { showLoader: false, speed: 150 }).catch(() => {
viewer.setPanorama(panorama, { showLoader: false, speed: 0 }).catch(() => {});
});
viewer.removeEventListener(events.ZoomUpdatedEvent.type, zoomHandler);
}
};
viewer.addEventListener(events.ZoomUpdatedEvent.type, zoomHandler);
}
});
onDestroy(() => {
Expand Down
29 changes: 15 additions & 14 deletions web/src/lib/components/photos-page/asset-grid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -762,20 +762,21 @@
{#if showShortcuts}
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
{/if}

<Scrubber
invisible={showSkeleton}
{assetStore}
height={safeViewport.height}
timelineTopOffset={topSectionHeight}
timelineBottomOffset={bottomSectionHeight}
{leadout}
{scrubOverallPercent}
{scrubBucketPercent}
{scrubBucket}
{onScrub}
{stopScrub}
/>
{#if assetStore.buckets.length > 0}
<Scrubber
invisible={showSkeleton}
{assetStore}
height={safeViewport.height}
timelineTopOffset={topSectionHeight}
timelineBottomOffset={bottomSectionHeight}
{leadout}
{scrubOverallPercent}
{scrubBucketPercent}
{scrubBucket}
{onScrub}
{stopScrub}
/>
{/if}

<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
<section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// re-visit with svelte 5. runes will make this better.
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
newContent;
if (textarea) {
if (textarea && newContent.length > 0) {
void tick().then(() => autoGrowHeight(textarea));
}
}
Expand Down
59 changes: 59 additions & 0 deletions web/src/lib/components/shared-components/tree/breadcrumbs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiArrowUpLeft, mdiChevronRight } from '@mdi/js';
import { t } from 'svelte-i18n';
export let pathSegments: string[] = [];
export let getLink: (path: string) => string;
export let title: string;
export let icon: string;
$: isRoot = pathSegments.length === 0;
</script>

<nav class="flex items-center py-2">
{#if !isRoot}
<div>
<CircleIconButton
icon={mdiArrowUpLeft}
title={$t('to_parent')}
href={getLink(pathSegments.slice(0, -1).join('/'))}
class="mr-2"
padding="2"
/>
</div>
{/if}

<div
class="bg-gray-50 dark:bg-immich-dark-gray/50 w-full p-2 rounded-2xl border border-gray-100 dark:border-gray-900 overflow-y-auto immich-scrollbar"
>
<ol class="flex gap-2 items-center">
<li>
<CircleIconButton
{icon}
href={getLink('')}
{title}
size="1.25em"
padding="2"
aria-current={isRoot ? 'page' : undefined}
/>
</li>
{#each pathSegments as segment, index}
{@const isLastSegment = index === pathSegments.length - 1}
<li
class="flex gap-2 items-center font-mono text-sm text-nowrap text-immich-primary dark:text-immich-dark-primary"
>
<Icon path={mdiChevronRight} class="text-gray-500 dark:text-gray-300" size={16} ariaHidden />
{#if isLastSegment}
<p class="cursor-default">{segment}</p>
{:else}
<a class="underline hover:font-semibold" href={getLink(pathSegments.slice(0, index + 1).join('/'))}>
{segment}
</a>
{/if}
</li>
{/each}
</ol>
</div>
</nav>
2 changes: 1 addition & 1 deletion web/src/lib/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@
"expired": "Verfallen",
"expires_date": "Läuft am {date} ab",
"explore": "Erkunden",
"explorer": "Entdeccker",
"explorer": "Explorer",
"export": "Exportieren",
"export_as_json": "Als JSON exportieren",
"extension": "Erweiterung",
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1191,7 +1191,7 @@
"to_change_password": "Change password",
"to_favorite": "Favorite",
"to_login": "Login",
"to_root": "To root",
"to_parent": "Go to parent",
"to_trash": "Trash",
"toggle_settings": "Toggle settings",
"toggle_theme": "Toggle dark theme",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore } from '$lib/stores/assets.store';
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { user } from '$lib/stores/user.store';
import { preferences, user } from '$lib/stores/user.store';
import { handlePromiseError } from '$lib/utils';
import { downloadAlbum } from '$lib/utils/asset-utils';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
Expand Down Expand Up @@ -85,6 +85,7 @@
import { t } from 'svelte-i18n';
import { onDestroy } from 'svelte';
import { confirmAlbumDelete } from '$lib/utils/album-utils';
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
export let data: PageData;
Expand Down Expand Up @@ -458,6 +459,11 @@
{/if}
<ArchiveAction menuItem unarchive={isAllArchived} onArchive={() => assetStore.triggerUpdate()} />
{/if}

{#if $preferences.tags.enabled && isAllUserOwned}
<TagAction menuItem />
{/if}

{#if isOwned || isAllUserOwned}
<RemoveFromAlbum menuItem bind:album onRemove={handleRemoveAssets} />
{/if}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
Expand All @@ -13,10 +11,11 @@
import { foldersStore } from '$lib/stores/folders.store';
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
import { type AssetResponseDto } from '@immich/sdk';
import { mdiArrowUpLeft, mdiChevronRight, mdiFolder, mdiFolderHome, mdiFolderOutline } from '@mdi/js';
import { mdiFolder, mdiFolderHome, mdiFolderOutline } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
export let data: PageData;
Expand All @@ -35,20 +34,11 @@
await navigateToView(normalizeTreePath(`${data.path || ''}/${folderName}`));
};
const handleBackNavigation = async () => {
if (data.path) {
const parentPath = data.path.split('/').slice(0, -1).join('/');
await navigateToView(parentPath);
}
};
const handleBreadcrumbNavigation = async (targetPath: string) => {
await navigateToView(targetPath);
};
const getLink = (path: string) => {
const url = new URL(AppRoute.FOLDERS, window.location.href);
url.searchParams.set(QueryParameter.PATH, path);
if (path) {
url.searchParams.set(QueryParameter.PATH, path);
}
return url.href;
};
Expand All @@ -70,33 +60,7 @@
</section>
</SideBarSection>

<section id="path-summary" class="text-immich-primary dark:text-immich-dark-primary rounded-xl flex">
{#if data.path}
<CircleIconButton icon={mdiArrowUpLeft} title="Back" on:click={handleBackNavigation} class="mr-2" padding="2" />
{/if}

<div
class="flex place-items-center gap-2 bg-gray-50 dark:bg-immich-dark-gray/50 w-full py-2 px-4 rounded-2xl border border-gray-100 dark:border-gray-900"
>
<a href={`${AppRoute.FOLDERS}`} title={$t('to_root')}>
<Icon path={mdiFolderHome} class="text-immich-primary dark:text-immich-dark-primary mr-2" size={28} />
</a>
{#each pathSegments as segment, index}
<button
class="text-sm font-mono underline hover:font-semibold"
on:click={() => handleBreadcrumbNavigation(pathSegments.slice(0, index + 1).join('/'))}
type="button"
>
{segment}
</button>
<p class="text-gray-500">
{#if index < pathSegments.length - 1}
<Icon path={mdiChevronRight} class="text-gray-500 dark:text-gray-300" size={16} />
{/if}
</p>
{/each}
</div>
</section>
<Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} {getLink} />

<section class="mt-2">
<TreeItemThumbnails items={data.currentFolders} icon={mdiFolder} onClick={handleNavigation} />
Expand Down
Loading

0 comments on commit 692540d

Please sign in to comment.