From 17f34788dc221078b8dec53652e0530e3f0bcb00 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Sat, 21 Feb 2026 01:43:14 -0800 Subject: [PATCH] fix: disable inspect for non-previewable assets (#8989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Prevent text/other assets from opening a blank fullscreen viewer by restricting inspect/zoom to previewable media kinds. ## Changes - Add `isPreviewableMediaType` helper in shared `formatUtil`. - Gate inspect/zoom actions in `AssetsSidebarTab`, `MediaAssetCard`, and `MediaAssetContextMenu` using an allowlist (`image`, `video`, `audio`, `3D`). - Build gallery items from previewable assets only. - Add unit tests for `isPreviewableMediaType`. ## Why `ResultGallery` only renders image/video/audio; text/other assets could previously enter fullscreen with no renderable content. ## Review Focus - Verify text/other assets no longer show Inspect and do not open fullscreen. - Verify image/video/audio behavior is unchanged. - Verify 3D still opens the 3D viewer dialog. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8989-fix-disable-inspect-for-non-previewable-assets-30c6d73d36508103a9b9da4fe50236ea) by [Unito](https://www.unito.io) --------- Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: GitHub Action --- .../src/formatUtil.test.ts | 15 +++++++++++ .../shared-frontend-utils/src/formatUtil.ts | 9 +++++++ .../sidebar/tabs/AssetsSidebarTab.vue | 27 ++++++++++++++----- .../assets/components/MediaAssetCard.vue | 8 ++++-- .../components/MediaAssetContextMenu.vue | 5 ++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/packages/shared-frontend-utils/src/formatUtil.test.ts b/packages/shared-frontend-utils/src/formatUtil.test.ts index a10d1d790..e9748b4ca 100644 --- a/packages/shared-frontend-utils/src/formatUtil.test.ts +++ b/packages/shared-frontend-utils/src/formatUtil.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' import { getMediaTypeFromFilename, highlightQuery, + isPreviewableMediaType, truncateFilename } from './formatUtil' @@ -196,4 +197,18 @@ describe('formatUtil', () => { ) }) }) + + describe('isPreviewableMediaType', () => { + it('returns true for image/video/audio/3D', () => { + expect(isPreviewableMediaType('image')).toBe(true) + expect(isPreviewableMediaType('video')).toBe(true) + expect(isPreviewableMediaType('audio')).toBe(true) + expect(isPreviewableMediaType('3D')).toBe(true) + }) + + it('returns false for text/other', () => { + expect(isPreviewableMediaType('text')).toBe(false) + expect(isPreviewableMediaType('other')).toBe(false) + }) + }) }) diff --git a/packages/shared-frontend-utils/src/formatUtil.ts b/packages/shared-frontend-utils/src/formatUtil.ts index f610a0a8e..bb9d2ff18 100644 --- a/packages/shared-frontend-utils/src/formatUtil.ts +++ b/packages/shared-frontend-utils/src/formatUtil.ts @@ -583,3 +583,12 @@ export function getMediaTypeFromFilename( return 'other' } + +export function isPreviewableMediaType(mediaType: MediaType): boolean { + return ( + mediaType === 'image' || + mediaType === 'video' || + mediaType === 'audio' || + mediaType === '3D' + ) +} diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue index 9cfbcdf93..05fc47608 100644 --- a/src/components/sidebar/tabs/AssetsSidebarTab.vue +++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue @@ -244,7 +244,11 @@ import { resolveOutputAssetItems } from '@/platform/assets/utils/outputAssetUtil import { isCloud } from '@/platform/distribution/types' import { useDialogStore } from '@/stores/dialogStore' import { ResultItemImpl } from '@/stores/queueStore' -import { formatDuration, getMediaTypeFromFilename } from '@/utils/formatUtil' +import { + formatDuration, + getMediaTypeFromFilename, + isPreviewableMediaType +} from '@/utils/formatUtil' import { cn } from '@/utils/tailwindUtil' const { t } = useI18n() @@ -405,6 +409,12 @@ const visibleAssets = computed(() => { return listViewSelectableAssets.value }) +const previewableVisibleAssets = computed(() => + visibleAssets.value.filter((asset) => + isPreviewableMediaType(getMediaTypeFromFilename(asset.name)) + ) +) + const selectedAssets = computed(() => getSelectedAssets(visibleAssets.value)) const isBulkMode = computed( @@ -430,12 +440,10 @@ watch(visibleAssets, (newAssets) => { // so selection stays consistent with what this view can act on. reconcileSelection(newAssets) if (currentGalleryAssetId.value && galleryActiveIndex.value !== -1) { - const newIndex = newAssets.findIndex( + const newIndex = previewableVisibleAssets.value.findIndex( (asset) => asset.id === currentGalleryAssetId.value ) - if (newIndex !== -1) { - galleryActiveIndex.value = newIndex - } + galleryActiveIndex.value = newIndex } }) @@ -446,7 +454,7 @@ watch(galleryActiveIndex, (index) => { }) const galleryItems = computed(() => { - return visibleAssets.value.map((asset) => { + return previewableVisibleAssets.value.map((asset) => { const mediaType = getMediaTypeFromFilename(asset.name) const resultItem = new ResultItemImpl({ filename: asset.name, @@ -552,6 +560,9 @@ const handleDeleteSelected = async () => { const handleZoomClick = (asset: AssetItem) => { const mediaType = getMediaTypeFromFilename(asset.name) + if (!isPreviewableMediaType(mediaType)) { + return + } if (mediaType === '3D') { const dialogStore = useDialogStore() @@ -571,7 +582,9 @@ const handleZoomClick = (asset: AssetItem) => { } currentGalleryAssetId.value = asset.id - const index = visibleAssets.value.findIndex((a) => a.id === asset.id) + const index = previewableVisibleAssets.value.findIndex( + (a) => a.id === asset.id + ) if (index !== -1) { galleryActiveIndex.value = index } diff --git a/src/platform/assets/components/MediaAssetCard.vue b/src/platform/assets/components/MediaAssetCard.vue index 7d86fd2de..00362de2f 100644 --- a/src/platform/assets/components/MediaAssetCard.vue +++ b/src/platform/assets/components/MediaAssetCard.vue @@ -59,6 +59,7 @@ >