mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
refactor(BaseModalLayout): convert right panel state to defineModel
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="base-widget-layout rounded-2xl overflow-hidden relative">
|
<div class="base-widget-layout rounded-2xl overflow-hidden relative">
|
||||||
<Button
|
<Button
|
||||||
v-show="!isRightPanelOpen && hasRightPanel"
|
v-show="!isRightPanelOpen && showRightPanelButton"
|
||||||
size="lg"
|
size="icon"
|
||||||
:class="
|
:class="
|
||||||
cn('absolute top-4 right-18 z-10', 'transition-opacity duration-200', {
|
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"
|
@click="toggleRightPanel"
|
||||||
@@ -58,13 +59,15 @@
|
|||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'flex justify-end gap-2 w-0',
|
'flex justify-end gap-2 w-0',
|
||||||
hasRightPanel && !isRightPanelOpen ? 'min-w-22' : 'min-w-10'
|
showRightPanelButton && !isRightPanelOpen
|
||||||
|
? 'min-w-22'
|
||||||
|
: 'min-w-10'
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="isRightPanelOpen && hasRightPanel"
|
v-if="isRightPanelOpen && showRightPanelButton"
|
||||||
size="lg"
|
size="icon"
|
||||||
@click="toggleRightPanel"
|
@click="toggleRightPanel"
|
||||||
>
|
>
|
||||||
<i class="icon-[lucide--panel-right-close]" />
|
<i class="icon-[lucide--panel-right-close]" />
|
||||||
@@ -92,7 +95,7 @@
|
|||||||
v-if="hasRightPanel && isRightPanelOpen"
|
v-if="hasRightPanel && isRightPanelOpen"
|
||||||
class="w-1/4 min-w-40 max-w-80 pt-16 pb-8"
|
class="w-1/4 min-w-40 max-w-80 pt-16 pb-8"
|
||||||
>
|
>
|
||||||
<slot name="rightPanel"></slot>
|
<slot name="rightPanel" />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,6 +118,17 @@ const isRightPanelOpen = defineModel<boolean>('rightPanelOpen', {
|
|||||||
default: false
|
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 BREAKPOINTS = { md: 880 }
|
||||||
const PANEL_SIZES = {
|
const PANEL_SIZES = {
|
||||||
width: 'w-1/3',
|
width: 'w-1/3',
|
||||||
@@ -122,7 +136,6 @@ const PANEL_SIZES = {
|
|||||||
maxWidth: 'max-w-56'
|
maxWidth: 'max-w-56'
|
||||||
}
|
}
|
||||||
|
|
||||||
const slots = useSlots()
|
|
||||||
const closeDialog = inject(OnCloseKey, () => {})
|
const closeDialog = inject(OnCloseKey, () => {})
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(BREAKPOINTS)
|
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||||
@@ -131,8 +144,6 @@ const notMobile = breakpoints.greater('md')
|
|||||||
const isLeftPanelOpen = ref<boolean>(true)
|
const isLeftPanelOpen = ref<boolean>(true)
|
||||||
const mobileMenuOpen = ref<boolean>(false)
|
const mobileMenuOpen = ref<boolean>(false)
|
||||||
|
|
||||||
const hasRightPanel = computed(() => !!slots.rightPanel)
|
|
||||||
|
|
||||||
watch(notMobile, (isDesktop) => {
|
watch(notMobile, (isDesktop) => {
|
||||||
if (!isDesktop) {
|
if (!isDesktop) {
|
||||||
mobileMenuOpen.value = false
|
mobileMenuOpen.value = false
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseModalLayout
|
<BaseModalLayout
|
||||||
|
:hide-right-panel-button="true"
|
||||||
|
:right-panel-open="!!focusedAsset"
|
||||||
data-component-id="AssetBrowserModal"
|
data-component-id="AssetBrowserModal"
|
||||||
class="size-full max-h-full max-w-full min-w-0"
|
class="size-full max-h-full max-w-full min-w-0"
|
||||||
:content-title="displayTitle"
|
:content-title="displayTitle"
|
||||||
@@ -56,13 +58,16 @@
|
|||||||
<AssetGrid
|
<AssetGrid
|
||||||
:assets="filteredAssets"
|
:assets="filteredAssets"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
|
:focused-asset-id="focusedAsset?.id"
|
||||||
|
@asset-focus="handleAssetFocus"
|
||||||
|
@asset-blur="focusedAsset = null"
|
||||||
@asset-select="handleAssetSelectAndEmit"
|
@asset-select="handleAssetSelectAndEmit"
|
||||||
@asset-deleted="refreshAssets"
|
@asset-deleted="refreshAssets"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="selectedAsset" #rightPanel>
|
<template #rightPanel>
|
||||||
<ModelInfoPanel :asset="selectedAsset" />
|
<ModelInfoPanel v-if="focusedAsset" :asset="focusedAsset" />
|
||||||
</template>
|
</template>
|
||||||
</BaseModalLayout>
|
</BaseModalLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -155,7 +160,7 @@ const {
|
|||||||
updateFilters
|
updateFilters
|
||||||
} = useAssetBrowser(fetchedAssets)
|
} = useAssetBrowser(fetchedAssets)
|
||||||
|
|
||||||
const selectedAsset = ref<AssetDisplayItem | null>(null)
|
const focusedAsset = ref<AssetDisplayItem | null>(null)
|
||||||
|
|
||||||
const primaryCategoryTag = computed(() => {
|
const primaryCategoryTag = computed(() => {
|
||||||
const assets = fetchedAssets.value ?? []
|
const assets = fetchedAssets.value ?? []
|
||||||
@@ -198,11 +203,12 @@ function handleClose() {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAssetFocus(asset: AssetDisplayItem) {
|
||||||
|
focusedAsset.value = asset
|
||||||
|
}
|
||||||
|
|
||||||
function handleAssetSelectAndEmit(asset: AssetDisplayItem) {
|
function handleAssetSelectAndEmit(asset: AssetDisplayItem) {
|
||||||
selectedAsset.value = asset
|
|
||||||
emit('asset-select', 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)
|
props.onSelect?.(asset)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
ref="card"
|
||||||
data-component-id="AssetCard"
|
data-component-id="AssetCard"
|
||||||
:data-asset-id="asset.id"
|
:data-asset-id="asset.id"
|
||||||
:aria-labelledby="titleId"
|
: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'
|
'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)"
|
@keydown.enter.self="interactive && $emit('select', asset)"
|
||||||
>
|
>
|
||||||
<div class="relative aspect-square w-full overflow-hidden rounded-xl">
|
<div class="relative aspect-square w-full overflow-hidden rounded-xl">
|
||||||
<div
|
<div
|
||||||
v-if="isLoading || error"
|
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"
|
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
|
<img
|
||||||
v-else
|
v-else
|
||||||
:src="asset.preview_url"
|
:src="asset.preview_url"
|
||||||
:alt="displayName"
|
:alt="displayName"
|
||||||
class="size-full object-cover cursor-pointer"
|
class="size-full object-cover cursor-pointer"
|
||||||
role="button"
|
|
||||||
@click.self="interactive && $emit('select', asset)"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AssetBadgeGroup :badges="asset.badges" />
|
<AssetBadgeGroup :badges="asset.badges" />
|
||||||
@@ -115,8 +114,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useImage } from '@vueuse/core'
|
import { onClickOutside, useImage } from '@vueuse/core'
|
||||||
import { computed, ref, toValue, useId, useTemplateRef } from 'vue'
|
import { computed, ref, toValue, useId, useTemplateRef, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import IconGroup from '@/components/button/IconGroup.vue'
|
import IconGroup from '@/components/button/IconGroup.vue'
|
||||||
@@ -133,12 +132,19 @@ import { useToastStore } from '@/platform/updates/common/toastStore'
|
|||||||
import { useDialogStore } from '@/stores/dialogStore'
|
import { useDialogStore } from '@/stores/dialogStore'
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
const { asset, interactive } = defineProps<{
|
const {
|
||||||
|
asset,
|
||||||
|
interactive,
|
||||||
|
focused = false
|
||||||
|
} = defineProps<{
|
||||||
asset: AssetDisplayItem
|
asset: AssetDisplayItem
|
||||||
interactive?: boolean
|
interactive?: boolean
|
||||||
|
focused?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
focus: [asset: AssetDisplayItem]
|
||||||
|
blur: []
|
||||||
select: [asset: AssetDisplayItem]
|
select: [asset: AssetDisplayItem]
|
||||||
deleted: [asset: AssetDisplayItem]
|
deleted: [asset: AssetDisplayItem]
|
||||||
}>()
|
}>()
|
||||||
@@ -149,10 +155,28 @@ const { closeDialog } = useDialogStore()
|
|||||||
const { flags } = useFeatureFlags()
|
const { flags } = useFeatureFlags()
|
||||||
const toastStore = useToastStore()
|
const toastStore = useToastStore()
|
||||||
|
|
||||||
|
const cardRef = useTemplateRef<HTMLDivElement>('card')
|
||||||
const dropdownMenuButton = useTemplateRef<InstanceType<typeof MoreButton>>(
|
const dropdownMenuButton = useTemplateRef<InstanceType<typeof MoreButton>>(
|
||||||
'dropdown-menu-button'
|
'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 titleId = useId()
|
||||||
const descId = useId()
|
const descId = useId()
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,9 @@
|
|||||||
<AssetCard
|
<AssetCard
|
||||||
:asset="item"
|
:asset="item"
|
||||||
:interactive="true"
|
:interactive="true"
|
||||||
|
:focused="item.id === focusedAssetId"
|
||||||
|
@focus="$emit('assetFocus', $event)"
|
||||||
|
@blur="$emit('assetBlur')"
|
||||||
@select="$emit('assetSelect', $event)"
|
@select="$emit('assetSelect', $event)"
|
||||||
@deleted="$emit('assetDeleted', $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 AssetCard from '@/platform/assets/components/AssetCard.vue'
|
||||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||||
|
|
||||||
const { assets } = defineProps<{
|
const { assets, focusedAssetId } = defineProps<{
|
||||||
assets: AssetDisplayItem[]
|
assets: AssetDisplayItem[]
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
focusedAssetId?: string | null
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
|
assetFocus: [asset: AssetDisplayItem]
|
||||||
|
assetBlur: []
|
||||||
assetSelect: [asset: AssetDisplayItem]
|
assetSelect: [asset: AssetDisplayItem]
|
||||||
assetDeleted: [asset: AssetDisplayItem]
|
assetDeleted: [asset: AssetDisplayItem]
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
Reference in New Issue
Block a user