feat(share): inline media preview on single-file share landing
The public share page only rendered media previews for FOLDER shares —
a single-file share got a bare icon + download button, even for images
and videos the browser can play natively.
Backend: resolve the shared file's mime_type + size at read time and
expose them on ShareDto (meta + password-verify endpoints, one shared
enrichment helper). Display-only enrichment: a failed file lookup
leaves the fields None instead of failing the response — the download
endpoint still surfaces the real error.
Frontend: the 'file' view now reuses the folder grid's lazyVideo
(poster-seek + retry) for video and imageRetry for images, with
Range-aware streaming already provided by /api/s/{token}/file/{id}.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,16 @@ import type { ItemType } from '$lib/api/types';
|
||||
export interface ShareMeta {
|
||||
item_type: ItemType;
|
||||
item_name: string;
|
||||
/** The shared item's id — file shares use it to build the preview src. */
|
||||
item_id: string;
|
||||
/**
|
||||
* File shares only: resolved by the server at read time so the landing
|
||||
* page can inline a media preview (video player / image) instead of a
|
||||
* bare download button. Absent for folder shares.
|
||||
*/
|
||||
mime_type?: string;
|
||||
/** File shares only: the shared file's size in bytes. */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface ShareFolderEntry {
|
||||
|
||||
@@ -287,7 +287,29 @@
|
||||
</form>
|
||||
{:else if view === 'file'}
|
||||
<div class="share__center">
|
||||
<Icon name="file" class="share__big-icon" />
|
||||
{#if meta && mediaKind(meta.mime_type) === 'video'}
|
||||
<!-- Inline player: Range-aware endpoint streams the video, so the
|
||||
timeline seeks without downloading the whole file first.
|
||||
lazyVideo defers the load, seeks a few frames in for a poster
|
||||
and retries once on error (same behaviour as the folder grid). -->
|
||||
<video
|
||||
class="share__media"
|
||||
data-testid="public-share-file-video"
|
||||
use:lazyVideo={shareFileUrl(token, meta.item_id)}
|
||||
controls
|
||||
playsinline
|
||||
></video>
|
||||
{:else if meta && mediaKind(meta.mime_type) === 'image'}
|
||||
<img
|
||||
class="share__media"
|
||||
data-testid="public-share-file-image"
|
||||
src={shareFileUrl(token, meta.item_id)}
|
||||
alt={meta.item_name}
|
||||
use:imageRetry
|
||||
/>
|
||||
{:else}
|
||||
<Icon name="file" class="share__big-icon" />
|
||||
{/if}
|
||||
<h1>{meta?.item_name}</h1>
|
||||
<a
|
||||
class="share__btn"
|
||||
@@ -486,6 +508,13 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.share__media {
|
||||
max-width: 100%;
|
||||
max-height: min(70vh, 40rem);
|
||||
border-radius: var(--radius-2xl);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
:global(.share__big-icon) {
|
||||
font-size: 3rem;
|
||||
color: var(--color-text-muted);
|
||||
|
||||
Reference in New Issue
Block a user