Appearance
useGalleryQuickViewDialog Composable ​
File Information ​
- Path:
app/composables/core/components/dialogs/useGalleryQuickViewDialog.ts - Purpose: State and behaviour of
GalleryQuickViewDialog - Type: Core Composable
- Added: PR #137 (RIV-1266), 2026-10-28
Overview ​
useGalleryQuickViewDialog(props: GalleryQuickViewProps, emit: GalleryQuickViewEmits) wires useGalleryPreviewPager (what is loaded) to useGalleryPreviewNavigation (where the user is) and adds the dialog's own concerns: thumbnail slots, drag / wheel / swipe, preload, view tracking, download, open detail view and close.
Features ​
Wiring ​
The pager gets baseAssets: props.assets, pagination: props.pagination and open: modalDialog. Navigation gets the pager's merged items, startAssetId, keyboardEnabled = open, loop: props.loop !== false and a paging adapter (headLength, total, hasGap, hasTail, canLoadForward/Backward/Last, loading, canPrefetch = pager.canAutoLoad, ratio = pagination.prefetchRatio, and the three loaders).
Thumbnail slots ​
| Value | Rule |
|---|---|
thumbStep | 88 px; 64 px when window.innerWidth <= 600. Measured on open and on resize |
| Visible count | max(floor(stripWidth / thumbStep), 3), then made odd: floor(n / 2) * 2 + 1 |
| Wrap | Only when loop is on and total >= span; positions are wrapped with ((target - 1) mod total) + 1 |
| Out-of-range slot without wrap | { empty: true, index: -1 } (renders nothing) |
| Slot not loaded | asset: null, key pending-<position>; renders a skeleton |
When a non-empty slot has no asset and the pager is idle, automatic loading is allowed and no move is pending, the composable loads: right of centre loadNext(); left of centre loadPrevious() if a tail exists, else loadLast().
Gestures ​
| Handler | Rule |
|---|---|
onStripPointerDown/Move/Up | Left button only; 5 px dead zone, then pointer capture; one step per thumbStep px; moving left calls next(). A click right after a drag is ignored (drag.moved resets on the next tick) |
onStripWheel | Only when abs(deltaX) > abs(deltaY); prevents default; accumulates deltaX and steps per thumbStep |
onStagePointerDown/Up/Cancel | Off for a single asset or when the target matches button, a, input, textarea, select, video, audio, [role="slider"], .v-slide-group. Horizontal only; SWIPE_THRESHOLD = 50 px or a flick (SWIPE_FLICK_VELOCITY = 0.5 px/ms and more than 10 px). Left swipe = next |
handleKeydown | Escape closes (arrow keys are handled in navigation) |
slideDirection follows the change in display position; with loop on, a jump of more than half the total is treated as the short way round (last -> first animates as next).
Preload ​
PRELOAD_COUNT = 2 neighbours on each side are loaded into new Image() once per URL. Video types (mp4, mov, avi, mkv, mpg, webm, wmv, m4v) use thumbnail_file || preview_image; other files compress_file || display_file || thumbnail_file || preview_image.
View tracking ​
trackedViewsis cleared on every open/close; on openstartAssetIdis added, because the card click already counted that view.- When the open dialog shows an asset for
CHANGE_SETTLE_MS = 400ms,trackViewruns once per asset id:useViewAssetsCount().mutateAsync({ workspace_id: getWorkspaceId(), asset_id }), thendispatchAnalytics(payload)andtrackActivity(payload, { source: "preview" }). On error the id is removed so a later visit retries. buildViewPayload:{ asset_id: [id], event: "asset", sub_event: "viewed", environment: "dealer" }; route name containingfoldersorcategoryaddscategory_id: [String(route.params.id)], any other route addscollection_id: Number(route.params.id).
Actions ​
| Function | Behaviour |
|---|---|
openFile() | dispatchAnalytics + trackActivity with the view payload, then window.open of brand_name-files-id in a new tab |
downloadFile() | Adds the id to downloadingIds (downloadSlctd spinner), await downloadFileRef(isImage, asset, false), errors via snackbar.showError(getErrorMessage(error)) |
closeDialog() | modalDialog = false, emits update:dialog (false) and close |
Computed metadata ​
createdBy:user.name ?? external_user.name ?? created_by ?? "N/A"dateUpload:created_at;dateModified:modified_at || updated_at; bothdayjs(...).format("MMM D, YYYY")isTransparentImage:isTransparentImageFileType(file_type)(png, svg, gif) - adds.transparent-imagesto the stageisSingleAsset:total <= 1
Return Type ​
modalDialog, currentIndex, downloadSlctd, stripRef, slideDirection, imageLoading, onStageImageSettled, thumbSlots, pendingMove, file, totalCount, displayPosition, hasPrev, hasNext, isSingleAsset, isTransparentImage, createdBy, dateUpload, dateModified, processFileName, getSrcPath, goTo, goToSlot, thumbnailClass, next, prev, onStripPointerDown, onStripPointerMove, onStripPointerUp, onStripWheel, onStagePointerDown, onStagePointerUp, onStagePointerCancel, openFile, downloadFile, closeDialog.
Parameters ​
| Name | Type |
|---|---|
props | GalleryQuickViewProps - see GalleryQuickViewDialog props |
emit | GalleryQuickViewEmits - update:dialog, close |
Usage Example ​
typescript
const props = withDefaults(defineProps<GalleryQuickViewProps>(), {
startAssetId: null,
pagination: null,
hideDownload: false,
loop: true,
});
const emit = defineEmits<{ "update:dialog": [v: boolean]; close: [] }>();
const { file, thumbSlots, next, prev, closeDialog } =
useGalleryQuickViewDialog(props, emit);Related Composables ​
- useGalleryPreviewPager
- useGalleryPreviewNavigation
- useQuickViewDialog - single-asset dialog