mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +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 StreamWithViewer from '$lib/components/timeline/StreamWithViewer.svelte';
|
||||||
import Skeleton from '$lib/elements/Skeleton.svelte';
|
import Skeleton from '$lib/elements/Skeleton.svelte';
|
||||||
import { SearchResultsManager } from '$lib/managers/searchresults-manager/SearchResultsManager.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 { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
import { LoadingSpinner } from '@immich/ui';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -92,5 +94,12 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</SelectableSegment>
|
</SelectableSegment>
|
||||||
{/snippet}
|
{/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>
|
</Photostream>
|
||||||
</StreamWithViewer>
|
</StreamWithViewer>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
>;
|
>;
|
||||||
|
segmentFooter: Snippet<
|
||||||
|
[
|
||||||
|
{
|
||||||
|
segment: PhotostreamSegment;
|
||||||
|
},
|
||||||
|
]
|
||||||
|
>;
|
||||||
skeleton: Snippet<
|
skeleton: Snippet<
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
@ -59,6 +66,8 @@
|
||||||
|
|
||||||
let {
|
let {
|
||||||
segment,
|
segment,
|
||||||
|
segmentFooter,
|
||||||
|
skeleton,
|
||||||
|
|
||||||
enableRouting,
|
enableRouting,
|
||||||
timelineManager = $bindable(),
|
timelineManager = $bindable(),
|
||||||
|
|
@ -72,7 +81,7 @@
|
||||||
isShowDeleteConfirmation = $bindable(false),
|
isShowDeleteConfirmation = $bindable(false),
|
||||||
|
|
||||||
children,
|
children,
|
||||||
skeleton,
|
|
||||||
empty,
|
empty,
|
||||||
header,
|
header,
|
||||||
handleTimelineScroll = () => {},
|
handleTimelineScroll = () => {},
|
||||||
|
|
@ -89,7 +98,6 @@
|
||||||
let { gridScrollTarget } = assetViewingStore;
|
let { gridScrollTarget } = assetViewingStore;
|
||||||
|
|
||||||
let element: HTMLElement | undefined = $state();
|
let element: HTMLElement | undefined = $state();
|
||||||
let timelineElement: HTMLElement | undefined = $state();
|
|
||||||
|
|
||||||
const maxMd = $derived(mobileDevice.maxMd);
|
const maxMd = $derived(mobileDevice.maxMd);
|
||||||
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
|
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
|
||||||
|
|
@ -97,6 +105,9 @@
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const layoutOptions = maxMd ? smallHeaderHeight : largeHeaderHeight;
|
const layoutOptions = maxMd ? smallHeaderHeight : largeHeaderHeight;
|
||||||
timelineManager.setLayoutOptions(layoutOptions);
|
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) => {
|
const scrollTo = (top: number) => {
|
||||||
|
|
@ -205,7 +216,6 @@
|
||||||
onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())}
|
onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())}
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
bind:this={timelineElement}
|
|
||||||
id="virtual-timeline"
|
id="virtual-timeline"
|
||||||
class:relative={true}
|
class:relative={true}
|
||||||
class:invisible={showSkeleton}
|
class:invisible={showSkeleton}
|
||||||
|
|
@ -245,6 +255,15 @@
|
||||||
})}
|
})}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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}
|
{/each}
|
||||||
<!-- spacer for lead-out -->
|
<!-- spacer for lead-out -->
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ export abstract class PhotostreamManager {
|
||||||
const changed = value !== this.#viewportWidth;
|
const changed = value !== this.#viewportWidth;
|
||||||
this.#viewportWidth = value;
|
this.#viewportWidth = value;
|
||||||
this.suspendTransitions = true;
|
this.suspendTransitions = true;
|
||||||
void this.updateViewportGeometry(changed);
|
this.updateViewportGeometry(changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewportWidth() {
|
get viewportWidth() {
|
||||||
|
|
@ -143,7 +143,7 @@ export abstract class PhotostreamManager {
|
||||||
set viewportHeight(value: number) {
|
set viewportHeight(value: number) {
|
||||||
this.#viewportHeight = value;
|
this.#viewportHeight = value;
|
||||||
this.#suspendTransitions = true;
|
this.#suspendTransitions = true;
|
||||||
void this.updateViewportGeometry(false);
|
this.updateViewportGeometry(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewportHeight() {
|
get viewportHeight() {
|
||||||
|
|
@ -151,10 +151,8 @@ export abstract class PhotostreamManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSlidingWindow(scrollTop: number) {
|
updateSlidingWindow(scrollTop: number) {
|
||||||
if (this.#scrollTop !== scrollTop) {
|
this.#scrollTop = scrollTop;
|
||||||
this.#scrollTop = scrollTop;
|
this.updateIntersections();
|
||||||
this.updateIntersections();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIntersections() {
|
updateIntersections() {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export class SearchResultsSegment extends PhotostreamSegment {
|
||||||
#id: string;
|
#id: string;
|
||||||
#searchTerms: SearchTerms;
|
#searchTerms: SearchTerms;
|
||||||
#currentPage: string | null = null;
|
#currentPage: string | null = null;
|
||||||
#nextPage: string | null = null;
|
#nextPage: string | null = $state(null);
|
||||||
|
|
||||||
#viewerAssets: ViewerAsset[] = $state([]);
|
#viewerAssets: ViewerAsset[] = $state([]);
|
||||||
|
|
||||||
|
|
@ -76,6 +76,10 @@ export class SearchResultsSegment extends PhotostreamSegment {
|
||||||
return this.#viewerAssets;
|
return this.#viewerAssets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasNextPage() {
|
||||||
|
return this.#nextPage !== null;
|
||||||
|
}
|
||||||
|
|
||||||
findAssetAbsolutePosition(assetId: string) {
|
findAssetAbsolutePosition(assetId: string) {
|
||||||
const viewerAsset = this.#viewerAssets.find((viewAsset) => viewAsset.id === assetId);
|
const viewerAsset = this.#viewerAssets.find((viewAsset) => viewAsset.id === assetId);
|
||||||
if (viewerAsset) {
|
if (viewerAsset) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue