mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 21:22:07 +00:00
[refactor] Migrate SettingDialog to BaseModalLayout design system (#8270)
This commit is contained in:
200
src/platform/settings/components/SettingDialog.vue
Normal file
200
src/platform/settings/components/SettingDialog.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<BaseModalLayout content-title="" data-testid="settings-dialog" size="md">
|
||||
<template #leftPanelHeaderTitle>
|
||||
<i class="icon-[lucide--settings]" />
|
||||
<h2 class="text-neutral text-base">{{ $t('g.settings') }}</h2>
|
||||
</template>
|
||||
|
||||
<template #leftPanel>
|
||||
<div class="px-3">
|
||||
<SearchBox
|
||||
v-model:model-value="searchQuery"
|
||||
size="md"
|
||||
:placeholder="$t('g.searchSettings') + '...'"
|
||||
:debounce-time="128"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<nav
|
||||
ref="navRef"
|
||||
class="scrollbar-hide flex flex-1 flex-col gap-1 overflow-y-auto px-3 py-4"
|
||||
>
|
||||
<div
|
||||
v-for="(group, index) in navGroups"
|
||||
:key="index"
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<NavTitle :title="group.title" />
|
||||
<NavItem
|
||||
v-for="item in group.items"
|
||||
:key="item.id"
|
||||
:data-nav-id="item.id"
|
||||
:icon="item.icon"
|
||||
:badge="item.badge"
|
||||
:active="activeCategoryKey === item.id"
|
||||
@click="onNavItemClick(item.id)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NavItem>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<template #header />
|
||||
|
||||
<template #content>
|
||||
<template v-if="inSearch">
|
||||
<SettingsPanel :setting-groups="searchResults" />
|
||||
</template>
|
||||
<template v-else-if="activeSettingCategory">
|
||||
<CurrentUserMessage v-if="activeSettingCategory.label === 'Comfy'" />
|
||||
<ColorPaletteMessage
|
||||
v-if="activeSettingCategory.label === 'Appearance'"
|
||||
/>
|
||||
<SettingsPanel :setting-groups="sortedGroups(activeSettingCategory)" />
|
||||
</template>
|
||||
<template v-else-if="activePanel">
|
||||
<Suspense>
|
||||
<component :is="activePanel.component" v-bind="activePanel.props" />
|
||||
<template #fallback>
|
||||
<div>
|
||||
{{ $t('g.loadingPanel', { panel: activePanel.node.label }) }}
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
</template>
|
||||
</BaseModalLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, provide, ref, watch } from 'vue'
|
||||
|
||||
import SearchBox from '@/components/common/SearchBox.vue'
|
||||
import CurrentUserMessage from '@/components/dialog/content/setting/CurrentUserMessage.vue'
|
||||
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
|
||||
import NavItem from '@/components/widget/nav/NavItem.vue'
|
||||
import NavTitle from '@/components/widget/nav/NavTitle.vue'
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import ColorPaletteMessage from '@/platform/settings/components/ColorPaletteMessage.vue'
|
||||
import SettingsPanel from '@/platform/settings/components/SettingsPanel.vue'
|
||||
import { useSettingSearch } from '@/platform/settings/composables/useSettingSearch'
|
||||
import { useSettingUI } from '@/platform/settings/composables/useSettingUI'
|
||||
import type { SettingTreeNode } from '@/platform/settings/settingStore'
|
||||
import type {
|
||||
ISettingGroup,
|
||||
SettingPanelType,
|
||||
SettingParams
|
||||
} from '@/platform/settings/types'
|
||||
import { OnCloseKey } from '@/types/widgetTypes'
|
||||
import { flattenTree } from '@/utils/treeUtil'
|
||||
|
||||
const { onClose, defaultPanel, scrollToSettingId } = defineProps<{
|
||||
onClose: () => void
|
||||
defaultPanel?: SettingPanelType
|
||||
scrollToSettingId?: string
|
||||
}>()
|
||||
|
||||
provide(OnCloseKey, onClose)
|
||||
|
||||
const {
|
||||
defaultCategory,
|
||||
settingCategories,
|
||||
navGroups,
|
||||
findCategoryByKey,
|
||||
findPanelByKey
|
||||
} = useSettingUI(defaultPanel, scrollToSettingId)
|
||||
|
||||
const {
|
||||
searchQuery,
|
||||
inSearch,
|
||||
searchResultsCategories,
|
||||
handleSearch: handleSearchBase,
|
||||
getSearchResults
|
||||
} = useSettingSearch()
|
||||
|
||||
const authActions = useFirebaseAuthActions()
|
||||
|
||||
const navRef = ref<HTMLElement | null>(null)
|
||||
const activeCategoryKey = ref<string | null>(defaultCategory.value?.key ?? null)
|
||||
|
||||
watch(searchResultsCategories, (categories) => {
|
||||
if (!inSearch.value || categories.size === 0) return
|
||||
const firstMatch = navGroups.value
|
||||
.flatMap((g) => g.items)
|
||||
.find((item) => {
|
||||
const node = findCategoryByKey(item.id)
|
||||
return node && categories.has(node.label)
|
||||
})
|
||||
activeCategoryKey.value = firstMatch?.id ?? null
|
||||
})
|
||||
|
||||
const activeSettingCategory = computed<SettingTreeNode | null>(() => {
|
||||
if (!activeCategoryKey.value) return null
|
||||
return (
|
||||
settingCategories.value.find((c) => c.key === activeCategoryKey.value) ??
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
const activePanel = computed(() => {
|
||||
if (!activeCategoryKey.value) return null
|
||||
return findPanelByKey(activeCategoryKey.value)
|
||||
})
|
||||
|
||||
const getGroupSortOrder = (group: SettingTreeNode): number =>
|
||||
Math.max(0, ...flattenTree<SettingParams>(group).map((s) => s.sortOrder ?? 0))
|
||||
|
||||
function sortedGroups(category: SettingTreeNode): ISettingGroup[] {
|
||||
return [...(category.children ?? [])]
|
||||
.sort((a, b) => {
|
||||
const orderDiff = getGroupSortOrder(b) - getGroupSortOrder(a)
|
||||
return orderDiff !== 0 ? orderDiff : a.label.localeCompare(b.label)
|
||||
})
|
||||
.map((group) => ({
|
||||
label: group.label,
|
||||
settings: flattenTree<SettingParams>(group).sort((a, b) => {
|
||||
const sortOrderA = a.sortOrder ?? 0
|
||||
const sortOrderB = b.sortOrder ?? 0
|
||||
return sortOrderB - sortOrderA
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
function handleSearch(query: string) {
|
||||
handleSearchBase(query.trim())
|
||||
if (query) {
|
||||
activeCategoryKey.value = null
|
||||
} else if (!activeCategoryKey.value) {
|
||||
activeCategoryKey.value = defaultCategory.value?.key ?? null
|
||||
}
|
||||
}
|
||||
|
||||
function onNavItemClick(id: string) {
|
||||
activeCategoryKey.value = id
|
||||
}
|
||||
|
||||
const searchResults = computed<ISettingGroup[]>(() => {
|
||||
const category = activeCategoryKey.value
|
||||
? findCategoryByKey(activeCategoryKey.value)
|
||||
: null
|
||||
return getSearchResults(category)
|
||||
})
|
||||
|
||||
watch(activeCategoryKey, (newKey, oldKey) => {
|
||||
if (!newKey && !inSearch.value) {
|
||||
activeCategoryKey.value = oldKey
|
||||
}
|
||||
if (newKey === 'credits') {
|
||||
void authActions.fetchBalance()
|
||||
}
|
||||
if (newKey) {
|
||||
void nextTick(() => {
|
||||
navRef.value
|
||||
?.querySelector(`[data-nav-id="${newKey}"]`)
|
||||
?.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user