Skip to content

[System Pop Up] Improve help center menu behavior and Electron compatibility #4330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 2, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 133 additions & 84 deletions src/components/helpcenter/HelpCenterMenuContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<nav class="help-menu-section" role="menubar">
<button
v-for="menuItem in menuItems"
v-show="menuItem.visible !== false"
:key="menuItem.key"
type="button"
class="help-menu-item"
Expand All @@ -29,14 +30,20 @@
@mouseenter="onSubmenuHover"
@mouseleave="onSubmenuLeave"
>
<template v-for="submenuItem in submenuItems" :key="submenuItem.key">
<div v-if="submenuItem.type === 'divider'" class="submenu-divider" />
<template
v-for="submenuItem in moreMenuItem?.items"
:key="submenuItem.key"
>
<div
v-if="submenuItem.type === 'divider'"
v-show="submenuItem.visible !== false"
class="submenu-divider"
/>
<button
v-else
v-show="submenuItem.visible !== false"
type="button"
class="help-menu-item submenu-item"
:class="{ disabled: submenuItem.disabled }"
:disabled="submenuItem.disabled"
role="menuitem"
@click="submenuItem.action"
>
Expand Down Expand Up @@ -117,32 +124,28 @@ import { type CSSProperties, computed, nextTick, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'

import { type ReleaseNote } from '@/services/releaseService'
import { useCommandStore } from '@/stores/commandStore'
import { useReleaseStore } from '@/stores/releaseStore'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { formatVersionAnchor } from '@/utils/formatUtil'

// Types
interface MenuItem {
key: string
icon: string
label: string
action: () => void
}

interface SubmenuItem {
key: string
type?: 'item' | 'divider'
icon?: string
label?: string
action?: () => void
disabled?: boolean
visible?: boolean
type?: 'item' | 'divider'
items?: MenuItem[]
}

// Constants
const EXTERNAL_LINKS = {
DOCS: 'https://docs.comfy.org/',
DISCORD: 'https://www.comfy.org/discord',
GITHUB: 'https://github.com/comfyanonymous/ComfyUI',
DESKTOP_GUIDE: 'https://docs.comfy.org/installation/desktop',
DESKTOP_GUIDE: 'https://comfyorg.notion.site/',
UPDATE_GUIDE: 'https://docs.comfy.org/installation/update_comfyui'
} as const

Expand All @@ -164,6 +167,12 @@ const SUBMENU_CONFIG = {
// Composables
const { t, locale } = useI18n()
const releaseStore = useReleaseStore()
const commandStore = useCommandStore()

// Emits
const emit = defineEmits<{
close: []
}>()

// State
const isSubmenuVisible = ref(false)
Expand All @@ -174,66 +183,99 @@ let hoverTimeout: number | null = null
// Computed
const hasReleases = computed(() => releaseStore.releases.length > 0)

const menuItems = computed<MenuItem[]>(() => [
{
key: 'docs',
icon: 'pi pi-book',
label: t('helpCenter.docs'),
action: () => openExternalLink(EXTERNAL_LINKS.DOCS)
},
{
key: 'discord',
icon: 'pi pi-discord',
label: 'Discord',
action: () => openExternalLink(EXTERNAL_LINKS.DISCORD)
},
{
key: 'github',
icon: 'pi pi-github',
label: t('helpCenter.github'),
action: () => openExternalLink(EXTERNAL_LINKS.GITHUB)
},
{
key: 'help',
icon: 'pi pi-question-circle',
label: t('helpCenter.helpFeedback'),
action: () => openExternalLink(EXTERNAL_LINKS.DISCORD)
},
{
key: 'more',
icon: '',
label: t('helpCenter.more'),
action: () => {} // No action for more item
}
])

const submenuItems = computed<SubmenuItem[]>(() => [
{
key: 'desktop-guide',
type: 'item',
label: t('helpCenter.desktopUserGuide'),
action: () => openExternalLink(EXTERNAL_LINKS.DESKTOP_GUIDE),
disabled: false
},
{
key: 'dev-tools',
type: 'item',
label: t('helpCenter.openDevTools'),
action: openDevTools,
disabled: !isElectron()
},
{
key: 'divider-1',
type: 'divider'
},
{
key: 'reinstall',
type: 'item',
label: t('helpCenter.reinstall'),
action: onReinstall,
disabled: !isElectron()
}
])
const moreMenuItem = computed(() =>
menuItems.value.find((item) => item.key === 'more')
)

const menuItems = computed<MenuItem[]>(() => {
const moreItems: MenuItem[] = [
{
key: 'desktop-guide',
type: 'item',
label: t('helpCenter.desktopUserGuide'),
action: () => {
openExternalLink(EXTERNAL_LINKS.DESKTOP_GUIDE)
emit('close')
}
},
{
key: 'dev-tools',
type: 'item',
label: t('helpCenter.openDevTools'),
visible: isElectron(),
action: () => {
openDevTools()
emit('close')
}
},
{
key: 'divider-1',
type: 'divider',
visible: isElectron()
},
{
key: 'reinstall',
type: 'item',
label: t('helpCenter.reinstall'),
visible: isElectron(),
action: () => {
onReinstall()
emit('close')
}
}
]

return [
{
key: 'docs',
type: 'item',
icon: 'pi pi-book',
label: t('helpCenter.docs'),
action: () => {
openExternalLink(EXTERNAL_LINKS.DOCS)
emit('close')
}
},
{
key: 'discord',
type: 'item',
icon: 'pi pi-discord',
label: 'Discord',
action: () => {
openExternalLink(EXTERNAL_LINKS.DISCORD)
emit('close')
}
},
{
key: 'github',
type: 'item',
icon: 'pi pi-github',
label: t('helpCenter.github'),
action: () => {
openExternalLink(EXTERNAL_LINKS.GITHUB)
emit('close')
}
},
{
key: 'help',
type: 'item',
icon: 'pi pi-question-circle',
label: t('helpCenter.helpFeedback'),
action: () => {
void commandStore.execute('Comfy.Feedback')
emit('close')
}
},
{
key: 'more',
type: 'item',
icon: '',
label: t('helpCenter.more'),
action: () => {}, // No action for more item
items: moreItems
}
]
})

// Utility Functions
const openExternalLink = (url: string): void => {
Expand All @@ -251,8 +293,12 @@ const calculateSubmenuPosition = (button: HTMLElement): CSSProperties => {
const rect = button.getBoundingClientRect()
const submenuWidth = 210 // Width defined in CSS

// Get actual submenu height if available, otherwise use estimated height
const submenuHeight = submenuRef.value?.offsetHeight || 120 // More realistic estimate for 2 items
// Get actual submenu height if available, otherwise estimate based on visible item count
const visibleItemCount =
moreMenuItem.value?.items?.filter((item) => item.visible !== false)
.length || 0
const estimatedHeight = visibleItemCount * 48 + 16 // ~48px per item + padding
const submenuHeight = submenuRef.value?.offsetHeight || estimatedHeight

// Get viewport dimensions
const viewportWidth = window.innerWidth
Expand Down Expand Up @@ -282,6 +328,8 @@ const calculateSubmenuPosition = (button: HTMLElement): CSSProperties => {
top = SUBMENU_CONFIG.OFFSET_PX
}

top -= 8

return {
position: 'fixed',
top: `${top}px`,
Expand Down Expand Up @@ -328,7 +376,13 @@ const onMenuItemHover = async (
key: string,
event: MouseEvent
): Promise<void> => {
if (key !== 'more') return
if (key !== 'more' || !moreMenuItem.value?.items) return

// Don't show submenu if all items are hidden
const hasVisibleItems = moreMenuItem.value.items.some(
(item) => item.visible !== false
)
if (!hasVisibleItems) return

clearHoverTimeout()

Expand Down Expand Up @@ -380,10 +434,12 @@ const onReleaseClick = (release: ReleaseNote): void => {
const versionAnchor = formatVersionAnchor(release.version)
const changelogUrl = `${getChangelogUrl()}#${versionAnchor}`
openExternalLink(changelogUrl)
emit('close')
}

const onUpdate = (_: ReleaseNote): void => {
openExternalLink(EXTERNAL_LINKS.UPDATE_GUIDE)
emit('close')
}

// Generate language-aware changelog URL
Expand Down Expand Up @@ -551,13 +607,6 @@ onMounted(async () => {
box-shadow: none;
}

.submenu-item.disabled,
.submenu-item:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}

.submenu-divider {
height: 1px;
background: #3e3e3e;
Expand Down