mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-28 18:54:09 +00:00
Feature/expanded minimap (#4902)
* [feat] Add formatKeySequence function to format keybindings for commands * [feat] Add lock and unlock canvas commands with keybindings and update localization * feat: Implement canvas scale synchronization and zoom level adjustment * feat: Enhance GraphCanvasMenu with zoom controls and improved button functionality * feat: Refactor MiniMap component layout and remove unused bottomPanelStore * feat: Update zoom control shortcuts to use formatted key sequences * feat: Add tests for ZoomControlsModal and enhance GraphCanvasMenu tests * Update locales [skip ci] * Fix browser tests * ui: align minimap properly * Update locales [skip ci] * feat: focus zoom input when zoom modal loads * style: improve styling of zoom controls and add focus effect * fix styling and tests * styling: add divider to graph canvas menu * styling: position minimap properly * styling: add close button for minimap * styling: add horizontal divider to minimap * styling: update minimap toggle button text and remove old styles * Update locales [skip ci] * Update locales [skip ci] * feat: disable canvas menu in viewport settings after zoom adjustments * Update test expectations [skip ci] * fix: update canvas read-only property access to use state object * Update locales [skip ci] * fix: adjust button group and minimap positioning * feat: enhance zoom controls and adjust minimap positioning per PR comments * feat: implement zoom controls composable * feat: add timeout delays for headless tests * fix: update zoom input validation range in applyZoom function * [refactor] Update positioning and styles for GraphCanvasMenu, MiniMap, and ZoomControlsModal components * [refactor] Adjust z-index and positioning for GraphCanvasMenu, MiniMap, and ZoomControlsModal components * [style] Adjust margin for minimap button styles in GraphCanvasMenu component * [refactor] minimap should show on focus mode * [refactor] Update LiteGraphCanvasSplitterOverlay to conditionally render side and bottom panels based on focus mode * [style] Adjust right positioning for MiniMap and ZoomControlsModal components * [style] Adjust right positioning for MiniMap and ZoomControlsModal components --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
committed by
GitHub
parent
23b3914714
commit
84379d9522
@@ -65,6 +65,7 @@ export class Topbar {
|
||||
}
|
||||
|
||||
async openTopbarMenu() {
|
||||
await this.page.waitForTimeout(1000)
|
||||
await this.page.locator('.comfyui-logo-wrapper').click()
|
||||
const menu = this.page.locator('.comfy-command-menu')
|
||||
await menu.waitFor({ state: 'visible' })
|
||||
|
||||
@@ -7,13 +7,11 @@ test.describe('Graph Canvas Menu', () => {
|
||||
// Set link render mode to spline to make sure it's not affected by other tests'
|
||||
// side effects.
|
||||
await comfyPage.setSetting('Comfy.LinkRenderMode', 2)
|
||||
// Enable canvas menu for all tests
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
})
|
||||
|
||||
test('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
// Note: `Comfy.Graph.CanvasMenu` is disabled in comfyPage setup.
|
||||
// so no cleanup is needed.
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
|
||||
const button = comfyPage.page.getByTestId('toggle-link-visibility-button')
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
@@ -36,4 +34,45 @@ test.describe('Graph Canvas Menu', () => {
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
})
|
||||
|
||||
test('Focus mode button is clickable and has correct test id', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const focusButton = comfyPage.page.getByTestId('focus-mode-button')
|
||||
await expect(focusButton).toBeVisible()
|
||||
await expect(focusButton).toBeEnabled()
|
||||
|
||||
// Test that the button can be clicked without error
|
||||
await focusButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
test('Zoom controls popup opens and closes', async ({ comfyPage }) => {
|
||||
// Find the zoom button by its percentage text content
|
||||
const zoomButton = comfyPage.page.locator('button').filter({
|
||||
hasText: '%'
|
||||
})
|
||||
await expect(zoomButton).toBeVisible()
|
||||
|
||||
// Click to open zoom controls
|
||||
await zoomButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Zoom controls modal should be visible
|
||||
const zoomModal = comfyPage.page
|
||||
.locator('div')
|
||||
.filter({
|
||||
hasText: 'Zoom To Fit'
|
||||
})
|
||||
.first()
|
||||
await expect(zoomModal).toBeVisible()
|
||||
|
||||
// Click backdrop to close
|
||||
const backdrop = comfyPage.page.locator('.fixed.inset-0').first()
|
||||
await backdrop.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Modal should be hidden
|
||||
await expect(zoomModal).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 102 KiB |
@@ -780,9 +780,18 @@ test.describe('Viewport settings', () => {
|
||||
|
||||
// Screenshot the canvas element
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
|
||||
|
||||
// Open zoom controls dropdown first
|
||||
const zoomControlsButton = comfyPage.page.getByTestId(
|
||||
'zoom-controls-button'
|
||||
)
|
||||
await zoomControlsButton.click()
|
||||
|
||||
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
|
||||
await toggleButton.click()
|
||||
// close zoom menu
|
||||
await zoomControlsButton.click()
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', false)
|
||||
|
||||
await comfyPage.menu.topbar.saveWorkflow('Workflow A')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
@@ -35,34 +35,44 @@ test.describe('Minimap', () => {
|
||||
})
|
||||
|
||||
test('Validate minimap toggle button state', async ({ comfyPage }) => {
|
||||
// Open zoom controls dropdown first
|
||||
const zoomControlsButton = comfyPage.page.getByTestId(
|
||||
'zoom-controls-button'
|
||||
)
|
||||
await zoomControlsButton.click()
|
||||
|
||||
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
|
||||
|
||||
await expect(toggleButton).toBeVisible()
|
||||
|
||||
await expect(toggleButton).toHaveClass(/minimap-active/)
|
||||
|
||||
const minimapContainer = comfyPage.page.locator('.litegraph-minimap')
|
||||
await expect(minimapContainer).toBeVisible()
|
||||
})
|
||||
|
||||
test('Validate minimap can be toggled off and on', async ({ comfyPage }) => {
|
||||
const minimapContainer = comfyPage.page.locator('.litegraph-minimap')
|
||||
|
||||
// Open zoom controls dropdown first
|
||||
const zoomControlsButton = comfyPage.page.getByTestId(
|
||||
'zoom-controls-button'
|
||||
)
|
||||
await zoomControlsButton.click()
|
||||
|
||||
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
|
||||
|
||||
await expect(minimapContainer).toBeVisible()
|
||||
await expect(toggleButton).toHaveClass(/minimap-active/)
|
||||
|
||||
await toggleButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(minimapContainer).not.toBeVisible()
|
||||
await expect(toggleButton).not.toHaveClass(/minimap-active/)
|
||||
await expect(toggleButton).toContainText('Show Minimap')
|
||||
|
||||
await toggleButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(minimapContainer).toBeVisible()
|
||||
await expect(toggleButton).toHaveClass(/minimap-active/)
|
||||
await expect(toggleButton).toContainText('Hide Minimap')
|
||||
})
|
||||
|
||||
test('Validate minimap keyboard shortcut Alt+M', async ({ comfyPage }) => {
|
||||
|
||||
@@ -193,6 +193,7 @@ test.describe('Workflows sidebar', () => {
|
||||
|
||||
await comfyPage.menu.topbar.saveWorkflowAs('workflow5.json')
|
||||
await comfyPage.confirmDialog.click('overwrite')
|
||||
await comfyPage.page.waitForTimeout(200)
|
||||
expect(await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()).toEqual([
|
||||
'workflow5.json'
|
||||
])
|
||||
|
||||
@@ -256,6 +256,7 @@ test.describe('Animated image widget', () => {
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y }
|
||||
})
|
||||
await comfyPage.page.waitForTimeout(200)
|
||||
|
||||
// Expect the filename combo value to be updated
|
||||
const fileComboWidget = await loadAnimatedWebpNode.getWidget(0)
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
<!-- Load splitter overlay only after comfyApp is ready. -->
|
||||
<!-- If load immediately, the top-level splitter stateKey won't be correctly
|
||||
synced with the stateStorage (localStorage). -->
|
||||
<LiteGraphCanvasSplitterOverlay
|
||||
v-if="comfyAppReady && betaMenuEnabled && !workspaceStore.focusMode"
|
||||
>
|
||||
<template #side-bar-panel>
|
||||
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady && betaMenuEnabled">
|
||||
<template v-if="!workspaceStore.focusMode" #side-bar-panel>
|
||||
<SideToolbar />
|
||||
</template>
|
||||
<template #bottom-panel>
|
||||
<template v-if="!workspaceStore.focusMode" #bottom-panel>
|
||||
<BottomPanel />
|
||||
</template>
|
||||
<template #graph-canvas-panel>
|
||||
|
||||
@@ -1,125 +1,278 @@
|
||||
<template>
|
||||
<ButtonGroup
|
||||
class="p-buttongroup-vertical absolute bottom-[10px] right-[10px] z-[1000]"
|
||||
@wheel="canvasInteractions.handleWheel"
|
||||
>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomIn')"
|
||||
severity="secondary"
|
||||
icon="pi pi-plus"
|
||||
:aria-label="$t('graphCanvasMenu.zoomIn')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomIn')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomOut')"
|
||||
severity="secondary"
|
||||
icon="pi pi-minus"
|
||||
:aria-label="$t('graphCanvasMenu.zoomOut')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomOut')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.fitView')"
|
||||
severity="secondary"
|
||||
icon="pi pi-expand"
|
||||
:aria-label="$t('graphCanvasMenu.fitView')"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="
|
||||
t(
|
||||
'graphCanvasMenu.' +
|
||||
(canvasStore.canvas?.read_only ? 'panMode' : 'selectMode')
|
||||
) + ' (Space)'
|
||||
"
|
||||
severity="secondary"
|
||||
:aria-label="
|
||||
t(
|
||||
'graphCanvasMenu.' +
|
||||
(canvasStore.canvas?.read_only ? 'panMode' : 'selectMode')
|
||||
)
|
||||
"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLock')"
|
||||
<div>
|
||||
<ZoomControlsModal :visible="isModalVisible" />
|
||||
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
v-if="hasActivePopup"
|
||||
class="fixed inset-0 z-[1200]"
|
||||
@click="hideModal"
|
||||
></div>
|
||||
|
||||
<ButtonGroup
|
||||
class="p-buttongroup-vertical p-1 absolute bottom-4 right-2 md:right-4"
|
||||
:style="stringifiedMinimapStyles.buttonGroupStyles"
|
||||
@wheel="canvasInteractions.handleWheel"
|
||||
>
|
||||
<template #icon>
|
||||
<i-material-symbols:pan-tool-outline
|
||||
v-if="canvasStore.canvas?.read_only"
|
||||
/>
|
||||
<i-simple-line-icons:cursor v-else />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
severity="secondary"
|
||||
:icon="linkHidden ? 'pi pi-eye-slash' : 'pi pi-eye'"
|
||||
:aria-label="$t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
data-testid="toggle-link-visibility-button"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="minimapTooltip"
|
||||
severity="secondary"
|
||||
:icon="'pi pi-map'"
|
||||
:aria-label="$t('graphCanvasMenu.toggleMinimap')"
|
||||
:class="{ 'minimap-active': minimapVisible }"
|
||||
data-testid="toggle-minimap-button"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
v-tooltip.top="selectTooltip"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
severity="secondary"
|
||||
:aria-label="selectTooltip"
|
||||
:pressed="isCanvasReadOnly"
|
||||
icon="i-material-symbols:pan-tool-outline"
|
||||
:class="selectButtonClass"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.Unlock')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:mouse-pointer-2 />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-tooltip.top="handTooltip"
|
||||
severity="secondary"
|
||||
:aria-label="handTooltip"
|
||||
:pressed="isCanvasUnlocked"
|
||||
:class="handButtonClass"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.Lock')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:hand />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<!-- vertical line with bg E1DED5 -->
|
||||
<div class="w-px my-1 bg-[#E1DED5] dark-theme:bg-[#2E3037] mx-2" />
|
||||
|
||||
<Button
|
||||
v-tooltip.top="fitViewTooltip"
|
||||
severity="secondary"
|
||||
icon="pi pi-expand"
|
||||
:aria-label="fitViewTooltip"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
class="hover:dark-theme:!bg-[#444444] hover:!bg-[#E7E6E6]"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:focus />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
ref="zoomButton"
|
||||
v-tooltip.top="t('zoomControls.label')"
|
||||
severity="secondary"
|
||||
:label="t('zoomControls.label')"
|
||||
:class="zoomButtonClass"
|
||||
:aria-label="t('zoomControls.label')"
|
||||
data-testid="zoom-controls-button"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
@click="toggleModal"
|
||||
>
|
||||
<span class="inline-flex text-xs">
|
||||
<span>{{ canvasStore.appScalePercentage }}%</span>
|
||||
<i-lucide:chevron-down />
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<div class="w-px my-1 bg-[#E1DED5] dark-theme:bg-[#2E3037] mx-2" />
|
||||
|
||||
<Button
|
||||
ref="focusButton"
|
||||
v-tooltip.top="focusModeTooltip"
|
||||
severity="secondary"
|
||||
:aria-label="focusModeTooltip"
|
||||
data-testid="focus-mode-button"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
:class="focusButtonClass"
|
||||
@click="() => commandStore.execute('Workspace.ToggleFocusMode')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:lightbulb />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-tooltip.top="{
|
||||
value: linkVisibilityTooltip,
|
||||
pt: {
|
||||
root: {
|
||||
style: 'z-index: 2; transform: translateY(-20px);'
|
||||
}
|
||||
}
|
||||
}"
|
||||
severity="secondary"
|
||||
:class="linkVisibleClass"
|
||||
:aria-label="linkVisibilityAriaLabel"
|
||||
data-testid="toggle-link-visibility-button"
|
||||
:style="stringifiedMinimapStyles.buttonStyles"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:route-off />
|
||||
</template>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import ButtonGroup from 'primevue/buttongroup'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCanvasInteractions } from '@/composables/graph/useCanvasInteractions'
|
||||
import { useZoomControls } from '@/composables/useZoomControls'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useKeybindingStore } from '@/stores/keybindingStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import ZoomControlsModal from './modals/ZoomControlsModal.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const commandStore = useCommandStore()
|
||||
const { formatKeySequence } = useCommandStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const keybindingStore = useKeybindingStore()
|
||||
const settingStore = useSettingStore()
|
||||
const canvasInteractions = useCanvasInteractions()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const minimap = useMinimap()
|
||||
|
||||
const minimapVisible = computed(() => settingStore.get('Comfy.Minimap.Visible'))
|
||||
const minimapTooltip = computed(() => {
|
||||
const baseText = t('graphCanvasMenu.toggleMinimap')
|
||||
const keybinding = keybindingStore.getKeybindingByCommandId(
|
||||
'Comfy.Canvas.ToggleMinimap'
|
||||
)
|
||||
return keybinding ? `${baseText} (${keybinding.combo.toString()})` : baseText
|
||||
const { isModalVisible, toggleModal, hideModal, hasActivePopup } =
|
||||
useZoomControls()
|
||||
|
||||
const stringifiedMinimapStyles = computed(() => {
|
||||
const buttonGroupKeys = ['backgroundColor', 'borderRadius', '']
|
||||
const buttonKeys = ['backgroundColor', 'borderRadius']
|
||||
const additionalButtonStyles = {
|
||||
border: 'none',
|
||||
width: '35px',
|
||||
height: '35px',
|
||||
'margin-right': '2px',
|
||||
'margin-left': '2px'
|
||||
}
|
||||
|
||||
const containerStyles = minimap.containerStyles.value
|
||||
|
||||
const buttonStyles = {
|
||||
...Object.fromEntries(
|
||||
Object.entries(containerStyles).filter(([key]) =>
|
||||
buttonKeys.includes(key)
|
||||
)
|
||||
),
|
||||
...additionalButtonStyles
|
||||
}
|
||||
const buttonGroupStyles = Object.entries(containerStyles)
|
||||
.filter(([key]) => buttonGroupKeys.includes(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
|
||||
return { buttonStyles, buttonGroupStyles }
|
||||
})
|
||||
|
||||
// Computed properties for reactive states
|
||||
const isCanvasReadOnly = computed(() => canvasStore.canvas?.read_only ?? false)
|
||||
const isCanvasUnlocked = computed(() => !isCanvasReadOnly.value)
|
||||
const linkHidden = computed(
|
||||
() => settingStore.get('Comfy.LinkRenderMode') === LiteGraph.HIDDEN_LINK
|
||||
)
|
||||
|
||||
let interval: number | null = null
|
||||
const repeat = async (command: string) => {
|
||||
if (interval) return
|
||||
const cmd = () => commandStore.execute(command)
|
||||
await cmd()
|
||||
interval = window.setInterval(cmd, 100)
|
||||
}
|
||||
const stopRepeat = () => {
|
||||
if (interval) {
|
||||
clearInterval(interval)
|
||||
interval = null
|
||||
}
|
||||
}
|
||||
// Computed properties for command text
|
||||
const unlockCommandText = computed(() =>
|
||||
formatKeySequence(
|
||||
commandStore.getCommand('Comfy.Canvas.Unlock')
|
||||
).toUpperCase()
|
||||
)
|
||||
const lockCommandText = computed(() =>
|
||||
formatKeySequence(commandStore.getCommand('Comfy.Canvas.Lock')).toUpperCase()
|
||||
)
|
||||
const fitViewCommandText = computed(() =>
|
||||
formatKeySequence(
|
||||
commandStore.getCommand('Comfy.Canvas.FitView')
|
||||
).toUpperCase()
|
||||
)
|
||||
const focusCommandText = computed(() =>
|
||||
formatKeySequence(
|
||||
commandStore.getCommand('Workspace.ToggleFocusMode')
|
||||
).toUpperCase()
|
||||
)
|
||||
|
||||
// Computed properties for button classes and states
|
||||
const selectButtonClass = computed(() =>
|
||||
isCanvasUnlocked.value
|
||||
? 'dark-theme:[&:not(:active)]:!bg-[#262729] [&:not(:active)]:!bg-[#E7E6E6]'
|
||||
: ''
|
||||
)
|
||||
|
||||
const handButtonClass = computed(() =>
|
||||
isCanvasReadOnly.value
|
||||
? 'dark-theme:[&:not(:active)]:!bg-[#262729] [&:not(:active)]:!bg-[#E7E6E6]'
|
||||
: ''
|
||||
)
|
||||
|
||||
const zoomButtonClass = computed(() => [
|
||||
'!w-16',
|
||||
isModalVisible.value
|
||||
? 'dark-theme:[&:not(:active)]:!bg-[#262729] [&:not(:active)]:!bg-[#E7E6E6]'
|
||||
: '',
|
||||
'hover:dark-theme:!bg-[#262729] hover:!bg-[#E7E6E6]'
|
||||
])
|
||||
|
||||
const focusButtonClass = computed(() => ({
|
||||
'hover:dark-theme:!bg-[#262729] hover:!bg-[#E7E6E6]': true,
|
||||
'dark-theme:[&:not(:active)]:!bg-[#262729] [&:not(:active)]:!bg-[#E7E6E6]':
|
||||
workspaceStore.focusMode
|
||||
}))
|
||||
|
||||
// Computed properties for tooltip and aria-label texts
|
||||
const selectTooltip = computed(
|
||||
() => `${t('graphCanvasMenu.select')} (${unlockCommandText.value})`
|
||||
)
|
||||
const handTooltip = computed(
|
||||
() => `${t('graphCanvasMenu.hand')} (${lockCommandText.value})`
|
||||
)
|
||||
const fitViewTooltip = computed(
|
||||
() => `${t('graphCanvasMenu.fitView')} (${fitViewCommandText.value})`
|
||||
)
|
||||
const focusModeTooltip = computed(
|
||||
() => `${t('graphCanvasMenu.focusMode')} (${focusCommandText.value})`
|
||||
)
|
||||
const linkVisibilityTooltip = computed(() =>
|
||||
linkHidden.value
|
||||
? t('graphCanvasMenu.showLinks')
|
||||
: t('graphCanvasMenu.hideLinks')
|
||||
)
|
||||
const linkVisibilityAriaLabel = computed(() =>
|
||||
linkHidden.value
|
||||
? t('graphCanvasMenu.showLinks')
|
||||
: t('graphCanvasMenu.hideLinks')
|
||||
)
|
||||
const linkVisibleClass = computed(() => [
|
||||
linkHidden.value
|
||||
? 'dark-theme:[&:not(:active)]:!bg-[#262729] [&:not(:active)]:!bg-[#E7E6E6]'
|
||||
: '',
|
||||
'hover:dark-theme:!bg-[#262729] hover:!bg-[#E7E6E6]'
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
canvasStore.initScaleSync()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
canvasStore.cleanupScaleSync()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.p-buttongroup-vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
z-index: 1200;
|
||||
border-radius: var(--p-button-border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--p-panel-border-color);
|
||||
@@ -129,15 +282,4 @@ const stopRepeat = () => {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.p-button.minimap-active {
|
||||
background-color: var(--p-button-primary-background);
|
||||
border-color: var(--p-button-primary-border-color);
|
||||
color: var(--p-button-primary-color);
|
||||
}
|
||||
|
||||
.p-button.minimap-active:hover {
|
||||
background-color: var(--p-button-primary-hover-background);
|
||||
border-color: var(--p-button-primary-hover-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
v-if="visible && initialized"
|
||||
ref="minimapRef"
|
||||
class="minimap-main-container flex absolute bottom-[20px] right-[90px] z-[1000]"
|
||||
class="minimap-main-container flex absolute bottom-[66px] right-2 md:right-11 z-[1000]"
|
||||
>
|
||||
<MiniMapPanel
|
||||
v-if="showOptionsPanel"
|
||||
@@ -31,6 +31,25 @@
|
||||
<i-lucide:settings-2 />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
class="absolute z-10 right-0"
|
||||
size="small"
|
||||
text
|
||||
severity="secondary"
|
||||
data-testid="close-minmap-button"
|
||||
@click.stop="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:x />
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<hr
|
||||
class="absolute top-5 bg-[#E1DED5] dark-theme:bg-[#262729] h-[1px] border-0"
|
||||
:style="{
|
||||
width: containerStyles.width
|
||||
}"
|
||||
/>
|
||||
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
@@ -58,9 +77,12 @@ import Button from 'primevue/button'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
|
||||
import MiniMapPanel from './MiniMapPanel.vue'
|
||||
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
const minimapRef = ref<HTMLDivElement>()
|
||||
|
||||
const {
|
||||
|
||||
237
src/components/graph/modals/ZoomControlsModal.vue
Normal file
237
src/components/graph/modals/ZoomControlsModal.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="w-[250px] absolute flex justify-center right-2 md:right-11 z-[1300] bottom-[66px] !bg-inherit !border-0"
|
||||
>
|
||||
<div
|
||||
class="bg-white dark-theme:bg-[#2b2b2b] border border-gray-200 dark-theme:border-gray-700 rounded-lg shadow-lg p-4 w-4/5"
|
||||
:style="filteredMinimapStyles"
|
||||
@click.stop
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
severity="secondary"
|
||||
text
|
||||
:pt="{
|
||||
root: {
|
||||
class:
|
||||
'flex items-center justify-between cursor-pointer p-2 rounded w-full text-left hover:!bg-transparent focus:!bg-transparent active:!bg-transparent'
|
||||
},
|
||||
label: {
|
||||
class: 'flex flex-col items-start w-full'
|
||||
}
|
||||
}"
|
||||
@mousedown="startRepeat('Comfy.Canvas.ZoomIn')"
|
||||
@mouseup="stopRepeat"
|
||||
@mouseleave="stopRepeat"
|
||||
>
|
||||
<template #default>
|
||||
<span class="text-sm font-medium block">{{
|
||||
$t('graphCanvasMenu.zoomIn')
|
||||
}}</span>
|
||||
<span class="text-sm text-gray-500 block">{{
|
||||
zoomInCommandText
|
||||
}}</span>
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
severity="secondary"
|
||||
text
|
||||
:pt="{
|
||||
root: {
|
||||
class:
|
||||
'flex items-center justify-between cursor-pointer p-2 rounded w-full text-left hover:!bg-transparent focus:!bg-transparent active:!bg-transparent'
|
||||
},
|
||||
label: {
|
||||
class: 'flex flex-col items-start w-full'
|
||||
}
|
||||
}"
|
||||
@mousedown="startRepeat('Comfy.Canvas.ZoomOut')"
|
||||
@mouseup="stopRepeat"
|
||||
@mouseleave="stopRepeat"
|
||||
>
|
||||
<template #default>
|
||||
<span class="text-sm font-medium block">{{
|
||||
$t('graphCanvasMenu.zoomOut')
|
||||
}}</span>
|
||||
<span class="text-sm text-gray-500 block">{{
|
||||
zoomOutCommandText
|
||||
}}</span>
|
||||
</template>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
severity="secondary"
|
||||
text
|
||||
:pt="{
|
||||
root: {
|
||||
class:
|
||||
'flex items-center justify-between cursor-pointer p-2 rounded w-full text-left hover:!bg-transparent focus:!bg-transparent active:!bg-transparent'
|
||||
},
|
||||
label: {
|
||||
class: 'flex flex-col items-start w-full'
|
||||
}
|
||||
}"
|
||||
@click="executeCommand('Comfy.Canvas.FitView')"
|
||||
>
|
||||
<template #default>
|
||||
<span class="text-sm font-medium block">{{
|
||||
$t('zoomControls.zoomToFit')
|
||||
}}</span>
|
||||
<span class="text-sm text-gray-500 block">{{
|
||||
zoomToFitCommandText
|
||||
}}</span>
|
||||
</template>
|
||||
</Button>
|
||||
<hr class="border-[#E1DED5] mb-1 dark-theme:border-[#2E3037]" />
|
||||
<Button
|
||||
severity="secondary"
|
||||
text
|
||||
data-testid="toggle-minimap-button"
|
||||
:pt="{
|
||||
root: {
|
||||
class:
|
||||
'flex items-center justify-between cursor-pointer p-2 rounded w-full text-left hover:!bg-transparent focus:!bg-transparent active:!bg-transparent'
|
||||
},
|
||||
label: {
|
||||
class: 'flex flex-col items-start w-full'
|
||||
}
|
||||
}"
|
||||
@click="executeCommand('Comfy.Canvas.ToggleMinimap')"
|
||||
>
|
||||
<template #default>
|
||||
<span class="text-sm font-medium block">{{
|
||||
minimapToggleText
|
||||
}}</span>
|
||||
<span class="text-sm text-gray-500 block">{{
|
||||
showMinimapCommandText
|
||||
}}</span>
|
||||
</template>
|
||||
</Button>
|
||||
<hr class="border-[#E1DED5] mt-1 dark-theme:border-[#2E3037]" />
|
||||
<div
|
||||
ref="zoomInputContainer"
|
||||
class="flex items-center px-2 bg-[#E7E6E6] focus-within:bg-[#F3F3F3] dark-theme:bg-[#8282821A] rounded p-2 zoomInputContainer"
|
||||
>
|
||||
<InputNumber
|
||||
ref="zoomInput"
|
||||
:default-value="canvasStore.appScalePercentage"
|
||||
:min="1"
|
||||
:max="1000"
|
||||
:show-buttons="false"
|
||||
:use-grouping="false"
|
||||
:unstyled="true"
|
||||
input-class="flex-1 bg-transparent border-none outline-none text-sm shadow-none my-0 "
|
||||
fluid
|
||||
@input="applyZoom"
|
||||
@keyup.enter="applyZoom"
|
||||
/>
|
||||
<span class="text-sm text-gray-500 -ml-4">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Button, InputNumber, InputNumberInputEvent } from 'primevue'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const minimap = useMinimap()
|
||||
const settingStore = useSettingStore()
|
||||
const commandStore = useCommandStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const { formatKeySequence } = useCommandStore()
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const interval = ref<number | null>(null)
|
||||
|
||||
// Computed properties for reactive states
|
||||
const minimapToggleText = computed(() =>
|
||||
settingStore.get('Comfy.Minimap.Visible')
|
||||
? t('zoomControls.hideMinimap')
|
||||
: t('zoomControls.showMinimap')
|
||||
)
|
||||
|
||||
const applyZoom = (val: InputNumberInputEvent) => {
|
||||
const inputValue = val.value as number
|
||||
if (isNaN(inputValue) || inputValue < 1 || inputValue > 1000) {
|
||||
return
|
||||
}
|
||||
canvasStore.setAppZoomFromPercentage(inputValue)
|
||||
}
|
||||
|
||||
const executeCommand = (command: string) => {
|
||||
void commandStore.execute(command)
|
||||
}
|
||||
|
||||
const startRepeat = (command: string) => {
|
||||
if (interval.value) return
|
||||
const cmd = () => commandStore.execute(command)
|
||||
void cmd()
|
||||
interval.value = window.setInterval(cmd, 100)
|
||||
}
|
||||
|
||||
const stopRepeat = () => {
|
||||
if (interval.value) {
|
||||
clearInterval(interval.value)
|
||||
interval.value = null
|
||||
}
|
||||
}
|
||||
const filteredMinimapStyles = computed(() => {
|
||||
return {
|
||||
...minimap.containerStyles.value,
|
||||
height: undefined,
|
||||
width: undefined
|
||||
}
|
||||
})
|
||||
const zoomInCommandText = computed(() =>
|
||||
formatKeySequence(commandStore.getCommand('Comfy.Canvas.ZoomIn'))
|
||||
)
|
||||
const zoomOutCommandText = computed(() =>
|
||||
formatKeySequence(commandStore.getCommand('Comfy.Canvas.ZoomOut'))
|
||||
)
|
||||
const zoomToFitCommandText = computed(() =>
|
||||
formatKeySequence(commandStore.getCommand('Comfy.Canvas.FitView'))
|
||||
)
|
||||
const showMinimapCommandText = computed(() =>
|
||||
formatKeySequence(commandStore.getCommand('Comfy.Canvas.ToggleMinimap'))
|
||||
)
|
||||
const zoomInput = ref<InstanceType<typeof InputNumber> | null>(null)
|
||||
const zoomInputContainer = ref<HTMLDivElement | null>(null)
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
await nextTick()
|
||||
const input = zoomInputContainer.value?.querySelector(
|
||||
'input'
|
||||
) as HTMLInputElement
|
||||
input?.focus()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.zoomInputContainer:focus-within {
|
||||
border: 1px solid rgb(204, 204, 204);
|
||||
}
|
||||
|
||||
.dark-theme .zoomInputContainer:focus-within {
|
||||
border: 1px solid rgb(204, 204, 204);
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<SidebarIcon
|
||||
:tooltip="
|
||||
$t('shortcuts.keyboardShortcuts') +
|
||||
' (' +
|
||||
formatKeySequence(command.keybinding!.combo.getKeySequences()) +
|
||||
')'
|
||||
"
|
||||
:tooltip="tooltipText"
|
||||
:selected="isShortcutsPanelVisible"
|
||||
@click="toggleShortcutsPanel"
|
||||
>
|
||||
@@ -17,28 +12,28 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
|
||||
import SidebarIcon from './SidebarIcon.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const bottomPanelStore = useBottomPanelStore()
|
||||
const command = useCommandStore().getCommand(
|
||||
'Workspace.ToggleBottomPanel.Shortcuts'
|
||||
)
|
||||
const commandStore = useCommandStore()
|
||||
const command = commandStore.getCommand('Workspace.ToggleBottomPanel.Shortcuts')
|
||||
const { formatKeySequence } = commandStore
|
||||
|
||||
const isShortcutsPanelVisible = computed(
|
||||
() => bottomPanelStore.activePanel === 'shortcuts'
|
||||
)
|
||||
|
||||
const tooltipText = computed(
|
||||
() => `${t('shortcuts.keyboardShortcuts')} (${formatKeySequence(command)})`
|
||||
)
|
||||
|
||||
const toggleShortcutsPanel = () => {
|
||||
bottomPanelStore.togglePanel('shortcuts')
|
||||
}
|
||||
|
||||
const formatKeySequence = (sequences: string[]): string => {
|
||||
return sequences
|
||||
.map((seq) => seq.replace(/Control/g, 'Ctrl').replace(/Shift/g, 'Shift'))
|
||||
.join(' + ')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -298,8 +298,26 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
id: 'Comfy.Canvas.ToggleLock',
|
||||
icon: 'pi pi-lock',
|
||||
label: 'Canvas Toggle Lock',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
app.canvas['read_only'] = !app.canvas['read_only']
|
||||
app.canvas.state.readOnly = !app.canvas.state.readOnly
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Canvas.Lock',
|
||||
icon: 'pi pi-lock',
|
||||
label: 'Lock Canvas',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
app.canvas.state.readOnly = true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Canvas.Unlock',
|
||||
icon: 'pi pi-lock-open',
|
||||
label: 'Unlock Canvas',
|
||||
function: () => {
|
||||
app.canvas.state.readOnly = false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
27
src/composables/useZoomControls.ts
Normal file
27
src/composables/useZoomControls.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export function useZoomControls() {
|
||||
const isModalVisible = ref(false)
|
||||
|
||||
const showModal = () => {
|
||||
isModalVisible.value = true
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
isModalVisible.value = false
|
||||
}
|
||||
|
||||
const toggleModal = () => {
|
||||
isModalVisible.value = !isModalVisible.value
|
||||
}
|
||||
|
||||
const hasActivePopup = computed(() => isModalVisible.value)
|
||||
|
||||
return {
|
||||
isModalVisible,
|
||||
showModal,
|
||||
hideModal,
|
||||
toggleModal,
|
||||
hasActivePopup
|
||||
}
|
||||
}
|
||||
@@ -191,6 +191,18 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
},
|
||||
commandId: 'Workspace.ToggleBottomPanel.Shortcuts'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'v'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.Unlock'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'h'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.Lock'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'Escape'
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "تعديل العرض ليناسب العقد المحددة"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "قفل اللوحة"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "تحريك العقد المحددة للأسفل"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "تثبيت/إلغاء تثبيت العناصر المحددة"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "فتح اللوحة"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "تكبير"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "ملائمة العرض",
|
||||
"focusMode": "وضع التركيز",
|
||||
"hand": "يد",
|
||||
"hideLinks": "إخفاء الروابط",
|
||||
"panMode": "وضع التحريك",
|
||||
"resetView": "إعادة تعيين العرض",
|
||||
"select": "تحديد",
|
||||
"selectMode": "وضع التحديد",
|
||||
"toggleLinkVisibility": "تبديل ظهور الروابط",
|
||||
"showLinks": "إظهار الروابط",
|
||||
"toggleMinimap": "تبديل الخريطة المصغرة",
|
||||
"zoomIn": "تكبير",
|
||||
"zoomOptions": "خيارات التكبير",
|
||||
"zoomOut": "تصغير"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "زيادة حجم الفرشاة في محرر القناع",
|
||||
"Interrupt": "إيقاف مؤقت",
|
||||
"Load Default Workflow": "تحميل سير العمل الافتراضي",
|
||||
"Lock Canvas": "قفل اللوحة",
|
||||
"Manage group nodes": "إدارة عقد المجموعة",
|
||||
"Manager": "المدير",
|
||||
"Minimap": "خريطة مصغرة",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "تبديل شريط تقدم مدير العقد المخصصة",
|
||||
"Undo": "تراجع",
|
||||
"Ungroup selected group nodes": "فك تجميع عقد المجموعة المحددة",
|
||||
"Unlock Canvas": "فتح قفل اللوحة",
|
||||
"Unpack the selected Subgraph": "فك تجميع الرسم البياني الفرعي المحدد",
|
||||
"Workflows": "سير العمل",
|
||||
"Zoom In": "تكبير",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "أدخل اسم الملف",
|
||||
"exportWorkflow": "تصدير سير العمل",
|
||||
"saveWorkflow": "حفظ سير العمل"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "إخفاء الخريطة المصغرة",
|
||||
"label": "عناصر التحكم في التكبير",
|
||||
"showMinimap": "إظهار الخريطة المصغرة",
|
||||
"zoomToFit": "تكبير لتناسب الشاشة"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "Fit view to selected nodes"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "Lock Canvas"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "Move Selected Nodes Down"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelectedNodes_Pin": {
|
||||
"label": "Pin/Unpin Selected Nodes"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "Unlock Canvas"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "Zoom In"
|
||||
},
|
||||
|
||||
@@ -894,8 +894,19 @@
|
||||
"fitView": "Fit View",
|
||||
"selectMode": "Select Mode",
|
||||
"panMode": "Pan Mode",
|
||||
"toggleLinkVisibility": "Toggle Link Visibility",
|
||||
"toggleMinimap": "Toggle Minimap"
|
||||
"toggleMinimap": "Toggle Minimap",
|
||||
"select": "Select",
|
||||
"hand": "Hand",
|
||||
"zoomOptions": "Zoom Options",
|
||||
"focusMode": "Focus Mode",
|
||||
"hideLinks": "Hide Links",
|
||||
"showLinks": "Show Links"
|
||||
},
|
||||
"zoomControls": {
|
||||
"label": "Zoom Controls",
|
||||
"zoomToFit": "Zoom To Fit",
|
||||
"showMinimap": "Show Minimap",
|
||||
"hideMinimap": "Hide Minimap"
|
||||
},
|
||||
"groupNode": {
|
||||
"create": "Create group node",
|
||||
@@ -963,6 +974,7 @@
|
||||
"Browse Templates": "Browse Templates",
|
||||
"Delete Selected Items": "Delete Selected Items",
|
||||
"Zoom to fit": "Zoom to fit",
|
||||
"Lock Canvas": "Lock Canvas",
|
||||
"Move Selected Nodes Down": "Move Selected Nodes Down",
|
||||
"Move Selected Nodes Left": "Move Selected Nodes Left",
|
||||
"Move Selected Nodes Right": "Move Selected Nodes Right",
|
||||
@@ -977,6 +989,7 @@
|
||||
"Collapse/Expand Selected Nodes": "Collapse/Expand Selected Nodes",
|
||||
"Mute/Unmute Selected Nodes": "Mute/Unmute Selected Nodes",
|
||||
"Pin/Unpin Selected Nodes": "Pin/Unpin Selected Nodes",
|
||||
"Unlock Canvas": "Unlock Canvas",
|
||||
"Zoom In": "Zoom In",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Clear Pending Tasks": "Clear Pending Tasks",
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "Ajustar vista a los nodos seleccionados"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "Bloquear lienzo"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "Mover nodos seleccionados hacia abajo"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "Anclar/Desanclar elementos seleccionados"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "Desbloquear lienzo"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "Acercar"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "Ajustar vista",
|
||||
"focusMode": "Modo de enfoque",
|
||||
"hand": "Mano",
|
||||
"hideLinks": "Ocultar enlaces",
|
||||
"panMode": "Modo de desplazamiento",
|
||||
"resetView": "Restablecer vista",
|
||||
"select": "Seleccionar",
|
||||
"selectMode": "Modo de selección",
|
||||
"toggleLinkVisibility": "Alternar visibilidad de enlace",
|
||||
"showLinks": "Mostrar enlaces",
|
||||
"toggleMinimap": "Alternar minimapa",
|
||||
"zoomIn": "Acercar",
|
||||
"zoomOptions": "Opciones de zoom",
|
||||
"zoomOut": "Alejar"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "Aumentar tamaño del pincel en MaskEditor",
|
||||
"Interrupt": "Interrumpir",
|
||||
"Load Default Workflow": "Cargar flujo de trabajo predeterminado",
|
||||
"Lock Canvas": "Bloquear lienzo",
|
||||
"Manage group nodes": "Gestionar nodos de grupo",
|
||||
"Manager": "Administrador",
|
||||
"Minimap": "Minimapa",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Alternar la Barra de Progreso del Administrador de Nodos Personalizados",
|
||||
"Undo": "Deshacer",
|
||||
"Ungroup selected group nodes": "Desagrupar nodos de grupo seleccionados",
|
||||
"Unlock Canvas": "Desbloquear lienzo",
|
||||
"Unpack the selected Subgraph": "Desempaquetar el Subgrafo seleccionado",
|
||||
"Workflows": "Flujos de trabajo",
|
||||
"Zoom In": "Acercar",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "Introduzca el nombre del archivo",
|
||||
"exportWorkflow": "Exportar flujo de trabajo",
|
||||
"saveWorkflow": "Guardar flujo de trabajo"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "Ocultar minimapa",
|
||||
"label": "Controles de zoom",
|
||||
"showMinimap": "Mostrar minimapa",
|
||||
"zoomToFit": "Ajustar al zoom"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "Ajuster la vue aux nœuds sélectionnés"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "Verrouiller la toile"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "Déplacer les nœuds sélectionnés vers le bas"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "Épingler/Désépingler les éléments sélectionnés"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "Déverrouiller le Canvas"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "Zoom avant"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "Adapter la vue",
|
||||
"focusMode": "Mode focus",
|
||||
"hand": "Main",
|
||||
"hideLinks": "Masquer les liens",
|
||||
"panMode": "Mode panoramique",
|
||||
"resetView": "Réinitialiser la vue",
|
||||
"select": "Sélectionner",
|
||||
"selectMode": "Mode sélection",
|
||||
"toggleLinkVisibility": "Basculer la visibilité des liens",
|
||||
"showLinks": "Afficher les liens",
|
||||
"toggleMinimap": "Afficher/Masquer la mini-carte",
|
||||
"zoomIn": "Zoom avant",
|
||||
"zoomOptions": "Options de zoom",
|
||||
"zoomOut": "Zoom arrière"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "Augmenter la taille du pinceau dans MaskEditor",
|
||||
"Interrupt": "Interrompre",
|
||||
"Load Default Workflow": "Charger le flux de travail par défaut",
|
||||
"Lock Canvas": "Verrouiller le canevas",
|
||||
"Manage group nodes": "Gérer les nœuds de groupe",
|
||||
"Manager": "Gestionnaire",
|
||||
"Minimap": "Minicarte",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Basculer la barre de progression du gestionnaire de nœuds personnalisés",
|
||||
"Undo": "Annuler",
|
||||
"Ungroup selected group nodes": "Dégrouper les nœuds de groupe sélectionnés",
|
||||
"Unlock Canvas": "Déverrouiller le canevas",
|
||||
"Unpack the selected Subgraph": "Décompresser le Subgraph sélectionné",
|
||||
"Workflows": "Flux de travail",
|
||||
"Zoom In": "Zoom avant",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "Entrez le nom du fichier",
|
||||
"exportWorkflow": "Exporter le flux de travail",
|
||||
"saveWorkflow": "Enregistrer le flux de travail"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "Masquer la mini-carte",
|
||||
"label": "Contrôles de zoom",
|
||||
"showMinimap": "Afficher la mini-carte",
|
||||
"zoomToFit": "Ajuster à l’écran"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "選択したノードにビューを合わせる"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "キャンバスをロック"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "選択したノードを下に移動"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "選択したアイテムのピン留め/ピン留め解除"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "キャンバスをロック解除"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "ズームイン"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "ビューに合わせる",
|
||||
"focusMode": "フォーカスモード",
|
||||
"hand": "手のひら",
|
||||
"hideLinks": "リンクを非表示",
|
||||
"panMode": "パンモード",
|
||||
"resetView": "ビューをリセット",
|
||||
"select": "選択",
|
||||
"selectMode": "選択モード",
|
||||
"toggleLinkVisibility": "リンクの表示切り替え",
|
||||
"showLinks": "リンクを表示",
|
||||
"toggleMinimap": "ミニマップの切り替え",
|
||||
"zoomIn": "拡大",
|
||||
"zoomOptions": "ズームオプション",
|
||||
"zoomOut": "縮小"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "マスクエディタでブラシサイズを大きくする",
|
||||
"Interrupt": "中断",
|
||||
"Load Default Workflow": "デフォルトワークフローを読み込む",
|
||||
"Lock Canvas": "キャンバスをロック",
|
||||
"Manage group nodes": "グループノードを管理",
|
||||
"Manager": "マネージャー",
|
||||
"Minimap": "ミニマップ",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え",
|
||||
"Undo": "元に戻す",
|
||||
"Ungroup selected group nodes": "選択したグループノードのグループ解除",
|
||||
"Unlock Canvas": "キャンバスのロックを解除",
|
||||
"Unpack the selected Subgraph": "選択したサブグラフを展開",
|
||||
"Workflows": "ワークフロー",
|
||||
"Zoom In": "ズームイン",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "ファイル名を入力",
|
||||
"exportWorkflow": "ワークフローをエクスポート",
|
||||
"saveWorkflow": "ワークフローを保存"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "ミニマップを非表示",
|
||||
"label": "ズームコントロール",
|
||||
"showMinimap": "ミニマップを表示",
|
||||
"zoomToFit": "全体表示にズーム"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "선택한 노드에 뷰 맞추기"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "캔버스 잠금"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "선택한 노드 아래로 이동"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "선택한 항목 고정/고정 해제"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "캔버스 잠금 해제"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "확대"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "보기 맞춤",
|
||||
"focusMode": "포커스 모드",
|
||||
"hand": "손 도구",
|
||||
"hideLinks": "링크 숨기기",
|
||||
"panMode": "팬 모드",
|
||||
"resetView": "보기 재설정",
|
||||
"select": "선택",
|
||||
"selectMode": "선택 모드",
|
||||
"toggleLinkVisibility": "링크 가시성 전환",
|
||||
"showLinks": "링크 표시",
|
||||
"toggleMinimap": "미니맵 전환",
|
||||
"zoomIn": "확대",
|
||||
"zoomOptions": "확대/축소 옵션",
|
||||
"zoomOut": "축소"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 늘리기",
|
||||
"Interrupt": "중단",
|
||||
"Load Default Workflow": "기본 워크플로 불러오기",
|
||||
"Lock Canvas": "캔버스 잠금",
|
||||
"Manage group nodes": "그룹 노드 관리",
|
||||
"Manager": "매니저",
|
||||
"Minimap": "미니맵",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환",
|
||||
"Undo": "실행 취소",
|
||||
"Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제",
|
||||
"Unlock Canvas": "캔버스 잠금 해제",
|
||||
"Unpack the selected Subgraph": "선택한 서브그래프 풀기",
|
||||
"Workflows": "워크플로우",
|
||||
"Zoom In": "확대",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "파일 이름 입력",
|
||||
"exportWorkflow": "워크플로 내보내기",
|
||||
"saveWorkflow": "워크플로 저장"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "미니맵 숨기기",
|
||||
"label": "확대/축소 컨트롤",
|
||||
"showMinimap": "미니맵 표시",
|
||||
"zoomToFit": "화면에 맞게 확대"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "Подогнать вид к выбранным нодам"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "Заблокировать холст"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "Переместить выбранные узлы вниз"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "Закрепить/Открепить выбранных нод"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "Разблокировать Canvas"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "Увеличить"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "Подгонять под выделенные",
|
||||
"focusMode": "Режим фокуса",
|
||||
"hand": "Рука",
|
||||
"hideLinks": "Скрыть связи",
|
||||
"panMode": "Режим панорамирования",
|
||||
"resetView": "Сбросить вид",
|
||||
"select": "Выбрать",
|
||||
"selectMode": "Выбрать режим",
|
||||
"toggleLinkVisibility": "Переключить видимость ссылок",
|
||||
"showLinks": "Показать связи",
|
||||
"toggleMinimap": "Показать/скрыть миникарту",
|
||||
"zoomIn": "Увеличить",
|
||||
"zoomOptions": "Параметры масштабирования",
|
||||
"zoomOut": "Уменьшить"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "Увеличить размер кисти в MaskEditor",
|
||||
"Interrupt": "Прервать",
|
||||
"Load Default Workflow": "Загрузить стандартный рабочий процесс",
|
||||
"Lock Canvas": "Заблокировать холст",
|
||||
"Manage group nodes": "Управление групповыми нодами",
|
||||
"Manager": "Менеджер",
|
||||
"Minimap": "Мини-карта",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов",
|
||||
"Undo": "Отменить",
|
||||
"Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды",
|
||||
"Unlock Canvas": "Разблокировать холст",
|
||||
"Unpack the selected Subgraph": "Распаковать выбранный подграф",
|
||||
"Workflows": "Рабочие процессы",
|
||||
"Zoom In": "Увеличить",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "Введите название файла",
|
||||
"exportWorkflow": "Экспорт рабочего процесса",
|
||||
"saveWorkflow": "Сохранить рабочий процесс"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "Скрыть миникарту",
|
||||
"label": "Элементы управления масштабом",
|
||||
"showMinimap": "Показать миникарту",
|
||||
"zoomToFit": "Масштабировать по размеру"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "將視圖適應至所選節點"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "鎖定畫布"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "將選取的節點下移"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "釘選/取消釘選已選項目"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "解鎖畫布"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "放大"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "適合視窗",
|
||||
"focusMode": "專注模式",
|
||||
"hand": "拖曳",
|
||||
"hideLinks": "隱藏連結",
|
||||
"panMode": "平移模式",
|
||||
"resetView": "重設視圖",
|
||||
"select": "選取",
|
||||
"selectMode": "選取模式",
|
||||
"toggleLinkVisibility": "切換連結顯示",
|
||||
"showLinks": "顯示連結",
|
||||
"toggleMinimap": "切換小地圖",
|
||||
"zoomIn": "放大",
|
||||
"zoomOptions": "縮放選項",
|
||||
"zoomOut": "縮小"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
|
||||
"Interrupt": "中斷",
|
||||
"Load Default Workflow": "載入預設工作流程",
|
||||
"Lock Canvas": "鎖定畫布",
|
||||
"Manage group nodes": "管理群組節點",
|
||||
"Manager": "管理員",
|
||||
"Minimap": "縮圖地圖",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "切換自訂節點管理器進度條",
|
||||
"Undo": "復原",
|
||||
"Ungroup selected group nodes": "取消群組選取的群組節點",
|
||||
"Unlock Canvas": "解除鎖定畫布",
|
||||
"Unpack the selected Subgraph": "解包所選子圖",
|
||||
"Workflows": "工作流程",
|
||||
"Zoom In": "放大",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "輸入檔案名稱",
|
||||
"exportWorkflow": "匯出工作流程",
|
||||
"saveWorkflow": "儲存工作流程"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "隱藏小地圖",
|
||||
"label": "縮放控制",
|
||||
"showMinimap": "顯示小地圖",
|
||||
"zoomToFit": "縮放至適合大小"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@
|
||||
"Comfy_Canvas_FitView": {
|
||||
"label": "适应视图到选中节点"
|
||||
},
|
||||
"Comfy_Canvas_Lock": {
|
||||
"label": "鎖定畫布"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "下移选中的节点"
|
||||
},
|
||||
@@ -89,6 +92,9 @@
|
||||
"Comfy_Canvas_ToggleSelected_Pin": {
|
||||
"label": "固定/取消固定选中项"
|
||||
},
|
||||
"Comfy_Canvas_Unlock": {
|
||||
"label": "解鎖畫布"
|
||||
},
|
||||
"Comfy_Canvas_ZoomIn": {
|
||||
"label": "放大"
|
||||
},
|
||||
|
||||
@@ -410,12 +410,17 @@
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "适应视图",
|
||||
"focusMode": "專注模式",
|
||||
"hand": "拖曳",
|
||||
"hideLinks": "隱藏連結",
|
||||
"panMode": "平移模式",
|
||||
"resetView": "重置视图",
|
||||
"select": "選取",
|
||||
"selectMode": "选择模式",
|
||||
"toggleLinkVisibility": "切换连线可见性",
|
||||
"showLinks": "顯示連結",
|
||||
"toggleMinimap": "切换小地图",
|
||||
"zoomIn": "放大",
|
||||
"zoomOptions": "縮放選項",
|
||||
"zoomOut": "缩小"
|
||||
},
|
||||
"groupNode": {
|
||||
@@ -801,6 +806,7 @@
|
||||
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大笔刷大小",
|
||||
"Interrupt": "中断",
|
||||
"Load Default Workflow": "加载默认工作流",
|
||||
"Lock Canvas": "鎖定畫布",
|
||||
"Manage group nodes": "管理组节点",
|
||||
"Manager": "管理器",
|
||||
"Minimap": "小地图",
|
||||
@@ -855,6 +861,7 @@
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条",
|
||||
"Undo": "撤销",
|
||||
"Ungroup selected group nodes": "解散选中组节点",
|
||||
"Unlock Canvas": "解除鎖定畫布",
|
||||
"Unpack the selected Subgraph": "解包选中子图",
|
||||
"Workflows": "工作流",
|
||||
"Zoom In": "放大画面",
|
||||
@@ -1694,5 +1701,11 @@
|
||||
"enterFilename": "输入文件名",
|
||||
"exportWorkflow": "导出工作流",
|
||||
"saveWorkflow": "保存工作流"
|
||||
},
|
||||
"zoomControls": {
|
||||
"hideMinimap": "隱藏小地圖",
|
||||
"label": "縮放控制",
|
||||
"showMinimap": "顯示小地圖",
|
||||
"zoomToFit": "適合畫面"
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,13 @@ export const useCommandStore = defineStore('command', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const formatKeySequence = (command: ComfyCommandImpl): string => {
|
||||
const sequences = command.keybinding?.combo.getKeySequences() || []
|
||||
return sequences
|
||||
.map((seq) => seq.replace(/Control/g, 'Ctrl').replace(/Shift/g, 'Shift'))
|
||||
.join(' + ')
|
||||
}
|
||||
|
||||
return {
|
||||
commands,
|
||||
execute,
|
||||
@@ -127,6 +134,7 @@ export const useCommandStore = defineStore('command', () => {
|
||||
registerCommand,
|
||||
registerCommands,
|
||||
isRegistered,
|
||||
loadExtensionCommands
|
||||
loadExtensionCommands,
|
||||
formatKeySequence
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { type Raw, computed, markRaw, ref, shallowRef } from 'vue'
|
||||
|
||||
import type { Positionable } from '@/lib/litegraph/src/interfaces'
|
||||
import type { Point, Positionable } from '@/lib/litegraph/src/interfaces'
|
||||
import type {
|
||||
LGraphCanvas,
|
||||
LGraphGroup,
|
||||
LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { app } from '@/scripts/app'
|
||||
import { isLGraphGroup, isLGraphNode, isReroute } from '@/utils/litegraphUtil'
|
||||
|
||||
export const useTitleEditorStore = defineStore('titleEditor', () => {
|
||||
@@ -33,6 +34,36 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||
selectedItems.value = items.map((item) => markRaw(item))
|
||||
}
|
||||
|
||||
// Reactive scale percentage that syncs with app.canvas.ds.scale
|
||||
const appScalePercentage = ref(100)
|
||||
|
||||
// Set up scale synchronization when canvas is available
|
||||
let originalOnChanged: ((scale: number, offset: Point) => void) | undefined =
|
||||
undefined
|
||||
const initScaleSync = () => {
|
||||
if (app.canvas?.ds) {
|
||||
// Initial sync
|
||||
originalOnChanged = app.canvas.ds.onChanged
|
||||
appScalePercentage.value = Math.round(app.canvas.ds.scale * 100)
|
||||
|
||||
// Set up continuous sync
|
||||
app.canvas.ds.onChanged = () => {
|
||||
if (app.canvas?.ds?.scale) {
|
||||
appScalePercentage.value = Math.round(app.canvas.ds.scale * 100)
|
||||
}
|
||||
// Call original handler if exists
|
||||
originalOnChanged?.(app.canvas.ds.scale, app.canvas.ds.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupScaleSync = () => {
|
||||
if (app.canvas?.ds) {
|
||||
app.canvas.ds.onChanged = originalOnChanged
|
||||
originalOnChanged = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const nodeSelected = computed(() => selectedItems.value.some(isLGraphNode))
|
||||
const groupSelected = computed(() => selectedItems.value.some(isLGraphGroup))
|
||||
const rerouteSelected = computed(() => selectedItems.value.some(isReroute))
|
||||
@@ -42,13 +73,38 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||
return canvas.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the canvas zoom level from a percentage value
|
||||
* @param percentage - Zoom percentage value (1-1000, where 1000 = 1000% zoom)
|
||||
*/
|
||||
const setAppZoomFromPercentage = (percentage: number) => {
|
||||
if (!app.canvas?.ds || percentage <= 0) return
|
||||
|
||||
// Convert percentage to scale (1000% = 10.0 scale)
|
||||
const newScale = percentage / 100
|
||||
const ds = app.canvas.ds
|
||||
|
||||
ds.changeScale(
|
||||
newScale,
|
||||
ds.element ? [ds.element.width / 2, ds.element.height / 2] : undefined
|
||||
)
|
||||
app.canvas.setDirty(true, true)
|
||||
|
||||
// Update reactive value immediately for UI consistency
|
||||
appScalePercentage.value = Math.round(newScale * 100)
|
||||
}
|
||||
|
||||
return {
|
||||
canvas,
|
||||
selectedItems,
|
||||
nodeSelected,
|
||||
groupSelected,
|
||||
rerouteSelected,
|
||||
appScalePercentage,
|
||||
updateSelectedItems,
|
||||
getCanvas
|
||||
getCanvas,
|
||||
setAppZoomFromPercentage,
|
||||
initScaleSync,
|
||||
cleanupScaleSync
|
||||
}
|
||||
})
|
||||
|
||||
168
tests-ui/tests/components/graph/ZoomControlsModal.spec.ts
Normal file
168
tests-ui/tests/components/graph/ZoomControlsModal.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// Mock functions
|
||||
const mockExecute = vi.fn()
|
||||
const mockGetCommand = vi.fn().mockReturnValue({
|
||||
keybinding: {
|
||||
combo: {
|
||||
getKeySequences: () => ['Ctrl', '+']
|
||||
}
|
||||
}
|
||||
})
|
||||
const mockFormatKeySequence = vi.fn().mockReturnValue('Ctrl+')
|
||||
const mockSetAppZoom = vi.fn()
|
||||
const mockSettingGet = vi.fn().mockReturnValue(true)
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/renderer/extensions/minimap/composables/useMinimap', () => ({
|
||||
useMinimap: () => ({
|
||||
containerStyles: { value: { backgroundColor: '#fff', borderRadius: '8px' } }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/commandStore', () => ({
|
||||
useCommandStore: () => ({
|
||||
execute: mockExecute,
|
||||
getCommand: mockGetCommand,
|
||||
formatKeySequence: mockFormatKeySequence
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/graphStore', () => ({
|
||||
useCanvasStore: () => ({
|
||||
appScalePercentage: 100,
|
||||
setAppZoomFromPercentage: mockSetAppZoom
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/settingStore', () => ({
|
||||
useSettingStore: () => ({
|
||||
get: mockSettingGet
|
||||
})
|
||||
}))
|
||||
|
||||
describe('ZoomControlsModal', () => {
|
||||
it('should have proper props interface', () => {
|
||||
// Test that the component file structure and basic exports work
|
||||
expect(mockExecute).toBeDefined()
|
||||
expect(mockGetCommand).toBeDefined()
|
||||
expect(mockFormatKeySequence).toBeDefined()
|
||||
expect(mockSetAppZoom).toBeDefined()
|
||||
expect(mockSettingGet).toBeDefined()
|
||||
})
|
||||
|
||||
it('should call command store execute when executeCommand is invoked', () => {
|
||||
mockExecute.mockClear()
|
||||
|
||||
// Simulate the executeCommand function behavior
|
||||
const executeCommand = (command: string) => {
|
||||
mockExecute(command)
|
||||
}
|
||||
|
||||
executeCommand('Comfy.Canvas.FitView')
|
||||
expect(mockExecute).toHaveBeenCalledWith('Comfy.Canvas.FitView')
|
||||
})
|
||||
|
||||
it('should validate zoom input ranges correctly', () => {
|
||||
mockSetAppZoom.mockClear()
|
||||
|
||||
// Simulate the applyZoom function behavior
|
||||
const applyZoom = (val: { value: number }) => {
|
||||
const inputValue = val.value as number
|
||||
if (isNaN(inputValue) || inputValue < 1 || inputValue > 1000) {
|
||||
return
|
||||
}
|
||||
mockSetAppZoom(inputValue)
|
||||
}
|
||||
|
||||
// Test invalid values
|
||||
applyZoom({ value: 0 })
|
||||
applyZoom({ value: 1010 })
|
||||
applyZoom({ value: NaN })
|
||||
expect(mockSetAppZoom).not.toHaveBeenCalled()
|
||||
|
||||
// Test valid value
|
||||
applyZoom({ value: 50 })
|
||||
expect(mockSetAppZoom).toHaveBeenCalledWith(50)
|
||||
})
|
||||
|
||||
it('should return correct minimap toggle text based on setting', () => {
|
||||
const t = (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
'zoomControls.showMinimap': 'Show Minimap',
|
||||
'zoomControls.hideMinimap': 'Hide Minimap'
|
||||
}
|
||||
return translations[key] || key
|
||||
}
|
||||
|
||||
// Simulate the minimapToggleText computed property
|
||||
const minimapToggleText = () =>
|
||||
mockSettingGet('Comfy.Minimap.Visible')
|
||||
? t('zoomControls.hideMinimap')
|
||||
: t('zoomControls.showMinimap')
|
||||
|
||||
// Test when minimap is visible
|
||||
mockSettingGet.mockReturnValue(true)
|
||||
expect(minimapToggleText()).toBe('Hide Minimap')
|
||||
|
||||
// Test when minimap is hidden
|
||||
mockSettingGet.mockReturnValue(false)
|
||||
expect(minimapToggleText()).toBe('Show Minimap')
|
||||
})
|
||||
|
||||
it('should format keyboard shortcuts correctly', () => {
|
||||
mockFormatKeySequence.mockReturnValue('Ctrl+')
|
||||
|
||||
expect(mockFormatKeySequence()).toBe('Ctrl+')
|
||||
expect(mockGetCommand).toBeDefined()
|
||||
})
|
||||
|
||||
it('should handle repeat command functionality', () => {
|
||||
mockExecute.mockClear()
|
||||
let interval: number | null = null
|
||||
|
||||
// Simulate the repeat functionality
|
||||
const startRepeat = (command: string) => {
|
||||
if (interval) return
|
||||
const cmd = () => mockExecute(command)
|
||||
cmd() // Execute immediately
|
||||
interval = 1 // Mock interval ID
|
||||
}
|
||||
|
||||
const stopRepeat = () => {
|
||||
if (interval) {
|
||||
interval = null
|
||||
}
|
||||
}
|
||||
|
||||
startRepeat('Comfy.Canvas.ZoomIn')
|
||||
expect(mockExecute).toHaveBeenCalledWith('Comfy.Canvas.ZoomIn')
|
||||
|
||||
stopRepeat()
|
||||
expect(interval).toBeNull()
|
||||
})
|
||||
|
||||
it('should have proper filteredMinimapStyles computed property', () => {
|
||||
const mockContainerStyles = {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
height: '100px',
|
||||
width: '200px'
|
||||
}
|
||||
|
||||
// Simulate the filteredMinimapStyles computed property
|
||||
const filteredMinimapStyles = () => {
|
||||
return {
|
||||
...mockContainerStyles,
|
||||
height: undefined,
|
||||
width: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const result = filteredMinimapStyles()
|
||||
expect(result.backgroundColor).toBe('#fff')
|
||||
expect(result.borderRadius).toBe('8px')
|
||||
expect(result.height).toBeUndefined()
|
||||
expect(result.width).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user