Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
be02e80
[feat] Implement shortcuts management panel with categorized commands
Myestery Aug 1, 2025
dda25af
feat: Add shortcuts tab and categorize commands in command store
Myestery Aug 1, 2025
1968333
feat: Add category to sidebar tab and extend bottom panel interface
Myestery Aug 1, 2025
10300f8
feat: Add shortcuts toggle button to sidebar and re order mindmap
Myestery Aug 1, 2025
b37d8d4
feat: Enhance bottom panel store with multi-panel support and shortcu…
Myestery Aug 1, 2025
e2efdd0
npm run locale
Myestery Aug 1, 2025
96ecb88
Merge branch 'main' into bottom-keybindings
Myestery Aug 1, 2025
06a7e66
cleanup commands and style
Myestery Aug 1, 2025
0f6b4ca
Merge branch 'main' into bottom-keybindings
Myestery Aug 1, 2025
d737a84
Reset src/locales to main branch state
Myestery Aug 2, 2025
197c8dd
[bugfix] Fix pre-commit hook cross-platform compatibility (#4643)
huchenlei Aug 1, 2025
912c64f
Ignore Claude local config (#4649)
benceruleanlu Aug 1, 2025
6cf4d69
[fix] Add type guard for SubgraphDefinition to improve TypeScript inf…
christian-byrne Aug 2, 2025
9c85776
[fix] Fix viewport sync in minimap and subgraphs navigation (#4644)
christian-byrne Aug 2, 2025
84123c5
[feat] Add keyboard shortcuts localization to main.json
Myestery Aug 2, 2025
f26d967
Merge branch 'main' into bottom-keybindings
Myestery Aug 2, 2025
d73d627
unit and browser tests
Myestery Aug 2, 2025
dce73e5
use correct type in EssentialsPanel
Myestery Aug 4, 2025
471ddf4
[refactor] Simplify command subcategory grouping in EssentialsPanel a…
Myestery Aug 4, 2025
34351f5
fix unused css
Myestery Aug 4, 2025
7c5ab4e
feat: add aria-label for keybinding display accessibility
Myestery Aug 4, 2025
732bcc4
fix: use filtered subcategories for rendering shortcuts
Myestery Aug 4, 2025
af90f5d
adjust keyboard shortcuts margins and responsiveness
Myestery Aug 7, 2025
256997e
fix playwright tests
Myestery Aug 7, 2025
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
280 changes: 280 additions & 0 deletions browser_tests/tests/bottomPanelShortcuts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import { expect } from '@playwright/test'

import { comfyPageFixture as test } from '../fixtures/ComfyPage'

test.describe('Bottom Panel Shortcuts', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})

test('should toggle shortcuts panel visibility', async ({ comfyPage }) => {
// Initially shortcuts panel should be hidden
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()

// Click shortcuts toggle button in sidebar
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Shortcuts panel should now be visible
await expect(comfyPage.page.locator('.bottom-panel')).toBeVisible()

// Click toggle button again to hide
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Panel should be hidden again
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()
})

test('should display essentials shortcuts tab', async ({ comfyPage }) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Essentials tab should be visible and active by default
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).toBeVisible()
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).toHaveAttribute('aria-selected', 'true')

// Should display shortcut categories
await expect(
comfyPage.page.locator('.subcategory-title').first()
).toBeVisible()

// Should display some keyboard shortcuts
await expect(comfyPage.page.locator('.key-badge').first()).toBeVisible()

// Should have workflow, node, and queue sections
await expect(
comfyPage.page.getByRole('heading', { name: 'Workflow' })
).toBeVisible()
await expect(
comfyPage.page.getByRole('heading', { name: 'Node' })
).toBeVisible()
await expect(
comfyPage.page.getByRole('heading', { name: 'Queue' })
).toBeVisible()
})

test('should display view controls shortcuts tab', async ({ comfyPage }) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Click view controls tab
await comfyPage.page.getByRole('tab', { name: /View Controls/i }).click()

// View controls tab should be active
await expect(
comfyPage.page.getByRole('tab', { name: /View Controls/i })
).toHaveAttribute('aria-selected', 'true')

// Should display view controls shortcuts
await expect(comfyPage.page.locator('.key-badge').first()).toBeVisible()

// Should have view and panel controls sections
await expect(
comfyPage.page.getByRole('heading', { name: 'View' })
).toBeVisible()
await expect(
comfyPage.page.getByRole('heading', { name: 'Panel Controls' })
).toBeVisible()
})

test('should switch between shortcuts tabs', async ({ comfyPage }) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Essentials should be active initially
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).toHaveAttribute('aria-selected', 'true')

// Click view controls tab
await comfyPage.page.getByRole('tab', { name: /View Controls/i }).click()

// View controls should now be active
await expect(
comfyPage.page.getByRole('tab', { name: /View Controls/i })
).toHaveAttribute('aria-selected', 'true')
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).not.toHaveAttribute('aria-selected', 'true')

// Switch back to essentials
await comfyPage.page.getByRole('tab', { name: /Essential/i }).click()

// Essentials should be active again
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).toHaveAttribute('aria-selected', 'true')
await expect(
comfyPage.page.getByRole('tab', { name: /View Controls/i })
).not.toHaveAttribute('aria-selected', 'true')
})

test('should display formatted keyboard shortcuts', async ({ comfyPage }) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Wait for shortcuts to load
await comfyPage.page.waitForSelector('.key-badge')

// Check for common formatted keys
const keyBadges = comfyPage.page.locator('.key-badge')
const count = await keyBadges.count()
expect(count).toBeGreaterThanOrEqual(1)

// Should show formatted modifier keys
const badgeText = await keyBadges.allTextContents()
const hasModifiers = badgeText.some((text) =>
['Ctrl', 'Cmd', 'Shift', 'Alt'].includes(text)
)
expect(hasModifiers).toBeTruthy()
})

test('should maintain panel state when switching to terminal', async ({
comfyPage
}) => {
// Open shortcuts panel first
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()
await expect(comfyPage.page.locator('.bottom-panel')).toBeVisible()

// Open terminal panel (should switch panels)
await comfyPage.page
.locator('button[aria-label*="Toggle Bottom Panel"]')
.click()

// Panel should still be visible but showing terminal content
await expect(comfyPage.page.locator('.bottom-panel')).toBeVisible()

// Switch back to shortcuts
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Should show shortcuts content again
await expect(
comfyPage.page.locator('[id*="tab_shortcuts-essentials"]')
).toBeVisible()
})

test('should handle keyboard navigation', async ({ comfyPage }) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Focus the first tab
await comfyPage.page.getByRole('tab', { name: /Essential/i }).focus()

// Use arrow keys to navigate between tabs
await comfyPage.page.keyboard.press('ArrowRight')

// View controls tab should now have focus
await expect(
comfyPage.page.getByRole('tab', { name: /View Controls/i })
).toBeFocused()

// Press Enter to activate the tab
await comfyPage.page.keyboard.press('Enter')

// Tab should be selected
await expect(
comfyPage.page.getByRole('tab', { name: /View Controls/i })
).toHaveAttribute('aria-selected', 'true')
})

test('should close panel by clicking shortcuts button again', async ({
comfyPage
}) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()
await expect(comfyPage.page.locator('.bottom-panel')).toBeVisible()

// Click shortcuts button again to close
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Panel should be hidden
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()
})

test('should display shortcuts in organized columns', async ({
comfyPage
}) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Should have 3-column grid layout
await expect(comfyPage.page.locator('.md\\:grid-cols-3')).toBeVisible()

// Should have multiple subcategory sections
const subcategoryTitles = comfyPage.page.locator('.subcategory-title')
const titleCount = await subcategoryTitles.count()
expect(titleCount).toBeGreaterThanOrEqual(2)
})

test('should open shortcuts panel with Ctrl+Shift+K', async ({
comfyPage
}) => {
// Initially shortcuts panel should be hidden
await expect(comfyPage.page.locator('.bottom-panel')).not.toBeVisible()

// Press Ctrl+Shift+K to open shortcuts panel
await comfyPage.page.keyboard.press('Control+Shift+KeyK')

// Shortcuts panel should now be visible
await expect(comfyPage.page.locator('.bottom-panel')).toBeVisible()

// Should show essentials tab by default
await expect(
comfyPage.page.getByRole('tab', { name: /Essential/i })
).toHaveAttribute('aria-selected', 'true')
})

test('should open settings dialog when clicking manage shortcuts button', async ({
comfyPage
}) => {
// Open shortcuts panel
await comfyPage.page
.locator('button[aria-label*="Keyboard Shortcuts"]')
.click()

// Manage shortcuts button should be visible
await expect(
comfyPage.page.getByRole('button', { name: /Manage Shortcuts/i })
).toBeVisible()

// Click manage shortcuts button
await comfyPage.page
.getByRole('button', { name: /Manage Shortcuts/i })
.click()

// Settings dialog should open with keybinding tab
await expect(comfyPage.page.getByRole('dialog')).toBeVisible()

// Should show keybinding settings (check for keybinding-related content)
await expect(
comfyPage.page.getByRole('option', { name: 'Keybinding' })
).toBeVisible()
})
})
56 changes: 47 additions & 9 deletions src/components/bottomPanel/BottomPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,33 @@
class="p-3 border-none"
>
<span class="font-bold">
{{ tab.title.toUpperCase() }}
{{
shouldCapitalizeTab(tab.id)
? tab.title.toUpperCase()
: tab.title
}}
</span>
</Tab>
</div>
<Button
class="justify-self-end"
icon="pi pi-times"
severity="secondary"
size="small"
text
@click="bottomPanelStore.bottomPanelVisible = false"
/>
<div class="flex items-center gap-2">
<Button
v-if="isShortcutsTabActive"
:label="$t('shortcuts.manageShortcuts')"
icon="pi pi-cog"
severity="secondary"
size="small"
text
@click="openKeybindingSettings"
/>
<Button
class="justify-self-end"
icon="pi pi-times"
severity="secondary"
size="small"
text
@click="closeBottomPanel"
/>
</div>
</div>
</TabList>
</Tabs>
Expand All @@ -44,9 +59,32 @@ import Button from 'primevue/button'
import Tab from 'primevue/tab'
import TabList from 'primevue/tablist'
import Tabs from 'primevue/tabs'
import { computed } from 'vue'

import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
import { useDialogService } from '@/services/dialogService'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'

const bottomPanelStore = useBottomPanelStore()
const dialogService = useDialogService()

const isShortcutsTabActive = computed(() => {
const activeTabId = bottomPanelStore.activeBottomPanelTabId
return (
activeTabId === 'shortcuts-essentials' ||
activeTabId === 'shortcuts-view-controls'
)
})

const shouldCapitalizeTab = (tabId: string): boolean => {
return tabId !== 'shortcuts-essentials' && tabId !== 'shortcuts-view-controls'
}

const openKeybindingSettings = async () => {
dialogService.showSettingsDialog('keybinding')
}

const closeBottomPanel = () => {
bottomPanelStore.activePanel = null
}
</script>
33 changes: 33 additions & 0 deletions src/components/bottomPanel/tabs/shortcuts/EssentialsPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<div class="h-full flex flex-col p-4">
<div class="flex-1 min-h-0 overflow-auto">
<ShortcutsList
:commands="essentialsCommands"
:subcategories="essentialsSubcategories"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

import {
ESSENTIALS_CONFIG,
useCommandSubcategories
} from '@/composables/bottomPanelTabs/useCommandSubcategories'
import { useCommandStore } from '@/stores/commandStore'

import ShortcutsList from './ShortcutsList.vue'

const commandStore = useCommandStore()

const essentialsCommands = computed(() =>
commandStore.commands.filter((cmd) => cmd.category === 'essentials')
)

const { subcategories: essentialsSubcategories } = useCommandSubcategories(
essentialsCommands,
ESSENTIALS_CONFIG
)
</script>
Loading