Simula_r
2025-10-02 13:58:47 -07:00
committed by GitHub
parent 3818ba5d17
commit 0d3d258995
13 changed files with 370 additions and 43 deletions

View File

@@ -83,6 +83,7 @@ const specDescriptor = computed<{
const allowUpload =
image_upload === true ||
animated_image_upload === true ||
video_upload === true ||
audio_upload === true
return {
kind,

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { computed, provide, ref, watch } from 'vue'
import { useWidgetValue } from '@/composables/graph/useWidgetValue'
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
@@ -16,10 +16,11 @@ import {
} from '@/utils/widgetPropFilter'
import FormDropdown from './form/dropdown/FormDropdown.vue'
import type {
DropdownItem,
FilterOption,
SelectedKey
import {
AssetKindKey,
type DropdownItem,
type FilterOption,
type SelectedKey
} from './form/dropdown/types'
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
@@ -31,6 +32,11 @@ const props = defineProps<{
uploadFolder?: ResultItemType
}>()
provide(
AssetKindKey,
computed(() => props.assetKind)
)
const emit = defineEmits<{
'update:modelValue': [value: string | number | undefined]
}>()
@@ -69,7 +75,7 @@ const inputItems = computed<DropdownItem[]>(() => {
return values.map((value: string, index: number) => ({
id: `input-${index}`,
imageSrc: getMediaUrl(value, 'input'),
mediaSrc: getMediaUrl(value, 'input'),
name: value,
metadata: ''
}))
@@ -99,7 +105,7 @@ const outputItems = computed<DropdownItem[]>(() => {
return Array.from(outputs).map((output, index) => ({
id: `output-${index}`,
imageSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
mediaSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
name: output,
metadata: ''
}))
@@ -145,6 +151,21 @@ const mediaPlaceholder = computed(() => {
const uploadable = computed(() => props.allowUpload === true)
const acceptTypes = computed(() => {
// Be permissive with accept types because backend uses libraries
// that can handle a wide range of formats
switch (props.assetKind) {
case 'image':
return 'image/*'
case 'video':
return 'video/*'
case 'audio':
return 'audio/*'
default:
return undefined // model or unknown
}
})
watch(
localValue,
(currentValue) => {
@@ -269,6 +290,7 @@ function getMediaUrl(
:placeholder="mediaPlaceholder"
:multiple="false"
:uploadable="uploadable"
:accept="acceptTypes"
:filter-options="filterOptions"
v-bind="combinedProps"
class="w-full"

View File

@@ -29,6 +29,7 @@ interface Props {
uploadable?: boolean
disabled?: boolean
accept?: string
filterOptions?: FilterOption[]
sortOptions?: SortOption[]
isSelected?: (
@@ -195,6 +196,7 @@ function handleSelection(item: DropdownItem, index: number) {
:selected="selected"
:uploadable="uploadable"
:disabled="disabled"
:accept="accept"
@select-click="toggleDropdown"
@file-change="handleFileChange"
/>

View File

@@ -15,6 +15,7 @@ interface Props {
maxSelectable: number
uploadable: boolean
disabled: boolean
accept?: string
}
const props = withDefaults(defineProps<Props>(), {
@@ -92,6 +93,7 @@ const theButtonStyle = computed(() => [
class="opacity-0 absolute inset-0 -z-1"
:multiple="maxSelectable > 1"
:disabled="disabled"
:accept="accept"
@change="emit('file-change', $event)"
/>
</label>

View File

@@ -84,7 +84,7 @@ const searchQuery = defineModel<string>('searchQuery')
:key="item.id"
:index="index"
:selected="isSelected(item, index)"
:image-src="item.imageSrc"
:media-src="item.mediaSrc"
:name="item.name"
:metadata="item.metadata"
:layout="layoutMode"

View File

@@ -1,14 +1,14 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, inject, ref } from 'vue'
import { cn } from '@/utils/tailwindUtil'
import type { LayoutMode } from './types'
import { AssetKindKey, type LayoutMode } from './types'
interface Props {
index: number
selected: boolean
imageSrc: string
mediaSrc: string
name: string
metadata?: string
layout?: LayoutMode
@@ -18,23 +18,36 @@ const props = defineProps<Props>()
const emit = defineEmits<{
click: [index: number]
imageLoad: [event: Event]
mediaLoad: [event: Event]
}>()
const actualDimensions = ref<string | null>(null)
const assetKind = inject(AssetKindKey)
const isVideo = computed(() => assetKind?.value === 'video')
function handleClick() {
emit('click', props.index)
}
function handleImageLoad(event: Event) {
emit('imageLoad', event)
emit('mediaLoad', event)
if (!event.target || !(event.target instanceof HTMLImageElement)) return
const img = event.target
if (img.naturalWidth && img.naturalHeight) {
actualDimensions.value = `${img.naturalWidth} x ${img.naturalHeight}`
}
}
function handleVideoLoad(event: Event) {
emit('mediaLoad', event)
if (!event.target || !(event.target instanceof HTMLVideoElement)) return
const video = event.target
if (video.videoWidth && video.videoHeight) {
actualDimensions.value = `${video.videoWidth} x ${video.videoHeight}`
}
}
</script>
<template>
@@ -81,9 +94,17 @@ function handleImageLoad(event: Event) {
>
<i-lucide:check class="size-3 text-white -translate-y-[0.5px]" />
</div>
<video
v-if="mediaSrc && isVideo"
:src="mediaSrc"
class="size-full object-cover"
preload="metadata"
muted
@loadeddata="handleVideoLoad"
/>
<img
v-if="imageSrc"
:src="imageSrc"
v-else-if="mediaSrc"
:src="mediaSrc"
class="size-full object-cover"
@load="handleImageLoad"
/>

View File

@@ -1,9 +1,13 @@
import type { ComputedRef, InjectionKey } from 'vue'
import type { AssetKind } from '@/types/widgetTypes'
export type OptionId = string | number | symbol
export type SelectedKey = OptionId
export interface DropdownItem {
id: SelectedKey
imageSrc: string
mediaSrc: string // URL for image, video, or other media
name: string
metadata: string
}
@@ -19,3 +23,6 @@ export interface FilterOption {
}
export type LayoutMode = 'list' | 'grid' | 'list-small'
export const AssetKindKey: InjectionKey<ComputedRef<AssetKind | undefined>> =
Symbol('assetKind')