mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat: add spinner to SearchResults
This commit is contained in:
parent
90ab75f968
commit
592874208b
4 changed files with 40 additions and 10 deletions
|
|
@ -9,8 +9,10 @@
|
|||
import StreamWithViewer from '$lib/components/timeline/StreamWithViewer.svelte';
|
||||
import Skeleton from '$lib/elements/Skeleton.svelte';
|
||||
import { SearchResultsManager } from '$lib/managers/searchresults-manager/SearchResultsManager.svelte';
|
||||
import { SearchResultsSegment } from '$lib/managers/searchresults-manager/SearchResultsSegment.svelte';
|
||||
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { LoadingSpinner } from '@immich/ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -92,5 +94,12 @@
|
|||
{/snippet}
|
||||
</SelectableSegment>
|
||||
{/snippet}
|
||||
{#snippet segmentFooter({ segment })}
|
||||
{#if (segment as SearchResultsSegment).hasNextPage}
|
||||
<div class="w-full flex justify-center items-center h-50">
|
||||
<LoadingSpinner size="giant" />
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Photostream>
|
||||
</StreamWithViewer>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@
|
|||
},
|
||||
]
|
||||
>;
|
||||
segmentFooter: Snippet<
|
||||
[
|
||||
{
|
||||
segment: PhotostreamSegment;
|
||||
},
|
||||
]
|
||||
>;
|
||||
skeleton: Snippet<
|
||||
[
|
||||
{
|
||||
|
|
@ -59,6 +66,8 @@
|
|||
|
||||
let {
|
||||
segment,
|
||||
segmentFooter,
|
||||
skeleton,
|
||||
|
||||
enableRouting,
|
||||
timelineManager = $bindable(),
|
||||
|
|
@ -72,7 +81,7 @@
|
|||
isShowDeleteConfirmation = $bindable(false),
|
||||
|
||||
children,
|
||||
skeleton,
|
||||
|
||||
empty,
|
||||
header,
|
||||
handleTimelineScroll = () => {},
|
||||
|
|
@ -89,7 +98,6 @@
|
|||
let { gridScrollTarget } = assetViewingStore;
|
||||
|
||||
let element: HTMLElement | undefined = $state();
|
||||
let timelineElement: HTMLElement | undefined = $state();
|
||||
|
||||
const maxMd = $derived(mobileDevice.maxMd);
|
||||
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
|
||||
|
|
@ -97,6 +105,9 @@
|
|||
$effect(() => {
|
||||
const layoutOptions = maxMd ? smallHeaderHeight : largeHeaderHeight;
|
||||
timelineManager.setLayoutOptions(layoutOptions);
|
||||
// this next line is important in order to ensure that the reactive signals of ViewerAsset.#intersecting
|
||||
// are marked as dependencies of timeline.#scrollTop.
|
||||
updateSlidingWindow();
|
||||
});
|
||||
|
||||
const scrollTo = (top: number) => {
|
||||
|
|
@ -205,7 +216,6 @@
|
|||
onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())}
|
||||
>
|
||||
<section
|
||||
bind:this={timelineElement}
|
||||
id="virtual-timeline"
|
||||
class:relative={true}
|
||||
class:invisible={showSkeleton}
|
||||
|
|
@ -245,6 +255,15 @@
|
|||
})}
|
||||
{/if}
|
||||
</div>
|
||||
{#if segmentFooter}
|
||||
<div
|
||||
style:position="absolute"
|
||||
style:transform={`translate3d(0,${absoluteHeight + monthGroup.height}px,0)`}
|
||||
style:width="100%"
|
||||
>
|
||||
{@render segmentFooter?.({ segment: monthGroup })}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- spacer for lead-out -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export abstract class PhotostreamManager {
|
|||
const changed = value !== this.#viewportWidth;
|
||||
this.#viewportWidth = value;
|
||||
this.suspendTransitions = true;
|
||||
void this.updateViewportGeometry(changed);
|
||||
this.updateViewportGeometry(changed);
|
||||
}
|
||||
|
||||
get viewportWidth() {
|
||||
|
|
@ -143,7 +143,7 @@ export abstract class PhotostreamManager {
|
|||
set viewportHeight(value: number) {
|
||||
this.#viewportHeight = value;
|
||||
this.#suspendTransitions = true;
|
||||
void this.updateViewportGeometry(false);
|
||||
this.updateViewportGeometry(false);
|
||||
}
|
||||
|
||||
get viewportHeight() {
|
||||
|
|
@ -151,11 +151,9 @@ export abstract class PhotostreamManager {
|
|||
}
|
||||
|
||||
updateSlidingWindow(scrollTop: number) {
|
||||
if (this.#scrollTop !== scrollTop) {
|
||||
this.#scrollTop = scrollTop;
|
||||
this.updateIntersections();
|
||||
}
|
||||
}
|
||||
|
||||
updateIntersections() {
|
||||
if (this.#updatingIntersections || !this.isInitialized || this.visibleWindow.bottom === this.visibleWindow.top) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class SearchResultsSegment extends PhotostreamSegment {
|
|||
#id: string;
|
||||
#searchTerms: SearchTerms;
|
||||
#currentPage: string | null = null;
|
||||
#nextPage: string | null = null;
|
||||
#nextPage: string | null = $state(null);
|
||||
|
||||
#viewerAssets: ViewerAsset[] = $state([]);
|
||||
|
||||
|
|
@ -76,6 +76,10 @@ export class SearchResultsSegment extends PhotostreamSegment {
|
|||
return this.#viewerAssets;
|
||||
}
|
||||
|
||||
get hasNextPage() {
|
||||
return this.#nextPage !== null;
|
||||
}
|
||||
|
||||
findAssetAbsolutePosition(assetId: string) {
|
||||
const viewerAsset = this.#viewerAssets.find((viewAsset) => viewAsset.id === assetId);
|
||||
if (viewerAsset) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue