mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-11 02:20:08 +00:00
refactor(BaseModalLayout): convert right panel state to defineModel
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="base-widget-layout rounded-2xl overflow-hidden relative">
|
||||
<Button
|
||||
v-show="!isRightPanelOpen && hasRightPanel"
|
||||
v-show="!isRightPanelOpen && showRightPanelButton"
|
||||
size="icon"
|
||||
:class="
|
||||
cn('absolute top-4 right-18 z-10', 'transition-opacity duration-200', {
|
||||
'opacity-0 pointer-events-none': isRightPanelOpen || !hasRightPanel
|
||||
'opacity-0 pointer-events-none':
|
||||
isRightPanelOpen || !showRightPanelButton
|
||||
})
|
||||
"
|
||||
@click="toggleRightPanel"
|
||||
@@ -58,12 +59,14 @@
|
||||
:class="
|
||||
cn(
|
||||
'flex justify-end gap-2 w-0',
|
||||
hasRightPanel && !isRightPanelOpen ? 'min-w-22' : 'min-w-10'
|
||||
showRightPanelButton && !isRightPanelOpen
|
||||
? 'min-w-22'
|
||||
: 'min-w-10'
|
||||
)
|
||||
"
|
||||
>
|
||||
<Button
|
||||
v-if="isRightPanelOpen && hasRightPanel"
|
||||
v-if="isRightPanelOpen && showRightPanelButton"
|
||||
size="icon"
|
||||
@click="toggleRightPanel"
|
||||
>
|
||||
@@ -92,7 +95,7 @@
|
||||
v-if="hasRightPanel && isRightPanelOpen"
|
||||
class="w-1/4 min-w-40 max-w-80"
|
||||
>
|
||||
<slot name="rightPanel"></slot>
|
||||
<slot name="rightPanel" />
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,6 +114,21 @@ const { contentTitle } = defineProps<{
|
||||
contentTitle: string
|
||||
}>()
|
||||
|
||||
const isRightPanelOpen = defineModel<boolean>('rightPanelOpen', {
|
||||
default: false
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
const hasRightPanel = computed(() => !!slots.rightPanel)
|
||||
|
||||
const hideRightPanelButton = defineModel<boolean>('hideRightPanelButton', {
|
||||
default: false
|
||||
})
|
||||
|
||||
const showRightPanelButton = computed(
|
||||
() => hasRightPanel.value && !hideRightPanelButton.value
|
||||
)
|
||||
|
||||
const BREAKPOINTS = { md: 880 }
|
||||
const PANEL_SIZES = {
|
||||
width: 'w-1/3',
|
||||
@@ -118,18 +136,14 @@ const PANEL_SIZES = {
|
||||
maxWidth: 'max-w-56'
|
||||
}
|
||||
|
||||
const slots = useSlots()
|
||||
const closeDialog = inject(OnCloseKey, () => {})
|
||||
|
||||
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||
const notMobile = breakpoints.greater('md')
|
||||
|
||||
const isLeftPanelOpen = ref<boolean>(true)
|
||||
const isRightPanelOpen = ref<boolean>(false)
|
||||
const mobileMenuOpen = ref<boolean>(false)
|
||||
|
||||
const hasRightPanel = computed(() => !!slots.rightPanel)
|
||||
|
||||
watch(notMobile, (isDesktop) => {
|
||||
if (!isDesktop) {
|
||||
mobileMenuOpen.value = false
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<BaseModalLayout
|
||||
:hide-right-panel-button="true"
|
||||
:right-panel-open="!!focusedAsset"
|
||||
data-component-id="AssetBrowserModal"
|
||||
class="size-full max-h-full max-w-full min-w-0"
|
||||
:content-title="displayTitle"
|
||||
@@ -56,13 +58,16 @@
|
||||
<AssetGrid
|
||||
:assets="filteredAssets"
|
||||
:loading="isLoading"
|
||||
:focused-asset-id="focusedAsset?.id"
|
||||
@asset-focus="handleAssetFocus"
|
||||
@asset-blur="focusedAsset = null"
|
||||
@asset-select="handleAssetSelectAndEmit"
|
||||
@asset-deleted="refreshAssets"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="selectedAsset" #rightPanel>
|
||||
<ModelInfoPanel :asset="selectedAsset" />
|
||||
<template #rightPanel>
|
||||
<ModelInfoPanel v-if="focusedAsset" :asset="focusedAsset" />
|
||||
</template>
|
||||
</BaseModalLayout>
|
||||
</template>
|
||||
@@ -155,7 +160,7 @@ const {
|
||||
updateFilters
|
||||
} = useAssetBrowser(fetchedAssets)
|
||||
|
||||
const selectedAsset = ref<AssetDisplayItem | null>(null)
|
||||
const focusedAsset = ref<AssetDisplayItem | null>(null)
|
||||
|
||||
const primaryCategoryTag = computed(() => {
|
||||
const assets = fetchedAssets.value ?? []
|
||||
@@ -198,11 +203,12 @@ function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function handleAssetFocus(asset: AssetDisplayItem) {
|
||||
focusedAsset.value = asset
|
||||
}
|
||||
|
||||
function handleAssetSelectAndEmit(asset: AssetDisplayItem) {
|
||||
selectedAsset.value = asset
|
||||
emit('asset-select', asset)
|
||||
// onSelect callback is provided by dialog composable layer
|
||||
// It handles the appropriate transformation (filename extraction or full asset)
|
||||
props.onSelect?.(asset)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
ref="card"
|
||||
data-component-id="AssetCard"
|
||||
:data-asset-id="asset.id"
|
||||
:aria-labelledby="titleId"
|
||||
@@ -12,22 +13,20 @@
|
||||
'group appearance-none bg-transparent m-0 outline-none text-left hover:bg-secondary-background focus:bg-secondary-background border-none focus:outline-solid outline-base-foreground outline-4'
|
||||
)
|
||||
"
|
||||
@click.stop="interactive && $emit('focus', asset)"
|
||||
@dblclick="interactive && $emit('select', asset)"
|
||||
@keydown.enter.self="interactive && $emit('select', asset)"
|
||||
>
|
||||
<div class="relative aspect-square w-full overflow-hidden rounded-xl">
|
||||
<div
|
||||
v-if="isLoading || error"
|
||||
class="flex size-full cursor-pointer items-center justify-center bg-gradient-to-br from-smoke-400 via-smoke-800 to-charcoal-400"
|
||||
role="button"
|
||||
@click.self="interactive && $emit('select', asset)"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
:src="asset.preview_url"
|
||||
:alt="displayName"
|
||||
class="size-full object-cover cursor-pointer"
|
||||
role="button"
|
||||
@click.self="interactive && $emit('select', asset)"
|
||||
/>
|
||||
|
||||
<AssetBadgeGroup :badges="asset.badges" />
|
||||
@@ -115,8 +114,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useImage } from '@vueuse/core'
|
||||
import { computed, ref, toValue, useId, useTemplateRef } from 'vue'
|
||||
import { onClickOutside, useImage } from '@vueuse/core'
|
||||
import { computed, ref, toValue, useId, useTemplateRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconGroup from '@/components/button/IconGroup.vue'
|
||||
@@ -133,12 +132,19 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { asset, interactive } = defineProps<{
|
||||
const {
|
||||
asset,
|
||||
interactive,
|
||||
focused = false
|
||||
} = defineProps<{
|
||||
asset: AssetDisplayItem
|
||||
interactive?: boolean
|
||||
focused?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
focus: [asset: AssetDisplayItem]
|
||||
blur: []
|
||||
select: [asset: AssetDisplayItem]
|
||||
deleted: [asset: AssetDisplayItem]
|
||||
}>()
|
||||
@@ -149,10 +155,28 @@ const { closeDialog } = useDialogStore()
|
||||
const { flags } = useFeatureFlags()
|
||||
const toastStore = useToastStore()
|
||||
|
||||
const cardRef = useTemplateRef<HTMLDivElement>('card')
|
||||
const dropdownMenuButton = useTemplateRef<InstanceType<typeof MoreButton>>(
|
||||
'dropdown-menu-button'
|
||||
)
|
||||
|
||||
const stopClickOutside = ref<(() => void) | null>(null)
|
||||
|
||||
watch(
|
||||
() => focused,
|
||||
(isFocused) => {
|
||||
stopClickOutside.value?.()
|
||||
stopClickOutside.value = null
|
||||
|
||||
if (isFocused && cardRef.value) {
|
||||
stopClickOutside.value = onClickOutside(cardRef, () => {
|
||||
emit('blur')
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const titleId = useId()
|
||||
const descId = useId()
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<AssetCard
|
||||
:asset="item"
|
||||
:interactive="true"
|
||||
:focused="item.id === focusedAssetId"
|
||||
@focus="$emit('assetFocus', $event)"
|
||||
@blur="$emit('assetBlur')"
|
||||
@select="$emit('assetSelect', $event)"
|
||||
@deleted="$emit('assetDeleted', $event)"
|
||||
/>
|
||||
@@ -50,12 +53,15 @@ import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
import AssetCard from '@/platform/assets/components/AssetCard.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
|
||||
const { assets } = defineProps<{
|
||||
const { assets, focusedAssetId } = defineProps<{
|
||||
assets: AssetDisplayItem[]
|
||||
loading?: boolean
|
||||
focusedAssetId?: string | null
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
assetFocus: [asset: AssetDisplayItem]
|
||||
assetBlur: []
|
||||
assetSelect: [asset: AssetDisplayItem]
|
||||
assetDeleted: [asset: AssetDisplayItem]
|
||||
}>()
|
||||
|
||||
Reference in New Issue
Block a user