mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
## Summary
Backports PR #6381 (UI color updates & tweaks) to core/1.30 branch.
This includes:
- Text readability improvements
- Semantic color token consistency updates
- UI element styling refinements
- Component import path fixes (ComfyLogoTransparent → ComfyLogo)
## Merge Conflicts Resolved
- **ComfyMenuButton.vue**: Accepted cherry-picked version with import
changes
- **20 browser test snapshots**: Used backportee's copy as instructed
**Original commit**: fd236b3587
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6679-Backport-PR-6381-to-core-1-30-UI-color-updates-tweaks-2aa6d73d3650815689defd191e3dd86e)
by [Unito](https://www.unito.io)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
267 lines
7.4 KiB
Vue
267 lines
7.4 KiB
Vue
<template>
|
|
<div class="flex h-full items-center">
|
|
<div
|
|
v-if="isDragging && !isDocked"
|
|
:class="actionbarClass"
|
|
@mouseenter="onMouseEnterDropZone"
|
|
@mouseleave="onMouseLeaveDropZone"
|
|
>
|
|
{{ t('actionbar.dockToTop') }}
|
|
</div>
|
|
|
|
<Panel
|
|
class="z-1000 pointer-events-auto"
|
|
:style="style"
|
|
:class="panelClass"
|
|
:pt="{
|
|
header: { class: 'hidden' },
|
|
content: { class: isDocked ? 'p-0' : 'p-1' }
|
|
}"
|
|
>
|
|
<div ref="panelRef" class="flex select-none items-center">
|
|
<span
|
|
ref="dragHandleRef"
|
|
:class="
|
|
cn(
|
|
'drag-handle cursor-grab w-3 h-max mr-2',
|
|
isDragging && 'cursor-grabbing'
|
|
)
|
|
"
|
|
/>
|
|
|
|
<ComfyRunButton />
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {
|
|
useDraggable,
|
|
useEventListener,
|
|
useLocalStorage,
|
|
watchDebounced
|
|
} from '@vueuse/core'
|
|
import { clamp } from 'es-toolkit/compat'
|
|
import Panel from 'primevue/panel'
|
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
|
|
import { t } from '@/i18n'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
import ComfyRunButton from './ComfyRunButton'
|
|
|
|
const settingsStore = useSettingStore()
|
|
|
|
const position = computed(() => settingsStore.get('Comfy.UseNewMenu'))
|
|
const visible = computed(() => position.value !== 'Disabled')
|
|
|
|
const tabContainer = document.querySelector('.workflow-tabs-container')
|
|
const panelRef = ref<HTMLElement | null>(null)
|
|
const dragHandleRef = ref<HTMLElement | null>(null)
|
|
const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', true)
|
|
const storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', {
|
|
x: 0,
|
|
y: 0
|
|
})
|
|
const {
|
|
x,
|
|
y,
|
|
style: style,
|
|
isDragging
|
|
} = useDraggable(panelRef, {
|
|
initialValue: { x: 0, y: 0 },
|
|
handle: dragHandleRef,
|
|
containerElement: document.body,
|
|
onMove: (event) => {
|
|
// Prevent dragging the menu over the top of the tabs
|
|
const minY = tabContainer?.getBoundingClientRect().bottom ?? 40
|
|
if (event.y < minY) {
|
|
event.y = minY
|
|
}
|
|
}
|
|
})
|
|
|
|
// Update storedPosition when x or y changes
|
|
watchDebounced(
|
|
[x, y],
|
|
([newX, newY]) => {
|
|
storedPosition.value = { x: newX, y: newY }
|
|
},
|
|
{ debounce: 300 }
|
|
)
|
|
|
|
// Set initial position to bottom center
|
|
const setInitialPosition = () => {
|
|
if (panelRef.value) {
|
|
const screenWidth = window.innerWidth
|
|
const screenHeight = window.innerHeight
|
|
const menuWidth = panelRef.value.offsetWidth
|
|
const menuHeight = panelRef.value.offsetHeight
|
|
|
|
if (menuWidth === 0 || menuHeight === 0) {
|
|
return
|
|
}
|
|
|
|
// Check if stored position exists and is within bounds
|
|
if (storedPosition.value.x !== 0 || storedPosition.value.y !== 0) {
|
|
// Ensure stored position is within screen bounds
|
|
x.value = clamp(storedPosition.value.x, 0, screenWidth - menuWidth)
|
|
y.value = clamp(storedPosition.value.y, 0, screenHeight - menuHeight)
|
|
captureLastDragState()
|
|
return
|
|
}
|
|
|
|
// If no stored position or current position, set to bottom center
|
|
if (x.value === 0 && y.value === 0) {
|
|
x.value = clamp((screenWidth - menuWidth) / 2, 0, screenWidth - menuWidth)
|
|
y.value = clamp(
|
|
screenHeight - menuHeight - 10,
|
|
0,
|
|
screenHeight - menuHeight
|
|
)
|
|
captureLastDragState()
|
|
}
|
|
}
|
|
}
|
|
onMounted(setInitialPosition)
|
|
watch(visible, async (newVisible) => {
|
|
if (newVisible) {
|
|
await nextTick(setInitialPosition)
|
|
}
|
|
})
|
|
|
|
const lastDragState = ref({
|
|
x: x.value,
|
|
y: y.value,
|
|
windowWidth: window.innerWidth,
|
|
windowHeight: window.innerHeight
|
|
})
|
|
const captureLastDragState = () => {
|
|
lastDragState.value = {
|
|
x: x.value,
|
|
y: y.value,
|
|
windowWidth: window.innerWidth,
|
|
windowHeight: window.innerHeight
|
|
}
|
|
}
|
|
watch(
|
|
isDragging,
|
|
(newIsDragging) => {
|
|
if (!newIsDragging) {
|
|
// Stop dragging
|
|
captureLastDragState()
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const adjustMenuPosition = () => {
|
|
if (panelRef.value) {
|
|
const screenWidth = window.innerWidth
|
|
const screenHeight = window.innerHeight
|
|
const menuWidth = panelRef.value.offsetWidth
|
|
const menuHeight = panelRef.value.offsetHeight
|
|
|
|
// Calculate distances to all edges
|
|
const distanceLeft = lastDragState.value.x
|
|
const distanceRight =
|
|
lastDragState.value.windowWidth - (lastDragState.value.x + menuWidth)
|
|
const distanceTop = lastDragState.value.y
|
|
const distanceBottom =
|
|
lastDragState.value.windowHeight - (lastDragState.value.y + menuHeight)
|
|
|
|
// Find the smallest distance to determine which edge to anchor to
|
|
const distances = [
|
|
{ edge: 'left', distance: distanceLeft },
|
|
{ edge: 'right', distance: distanceRight },
|
|
{ edge: 'top', distance: distanceTop },
|
|
{ edge: 'bottom', distance: distanceBottom }
|
|
]
|
|
const closestEdge = distances.reduce((min, curr) =>
|
|
curr.distance < min.distance ? curr : min
|
|
)
|
|
|
|
// Calculate vertical position as a percentage of screen height
|
|
const verticalRatio =
|
|
lastDragState.value.y / lastDragState.value.windowHeight
|
|
const horizontalRatio =
|
|
lastDragState.value.x / lastDragState.value.windowWidth
|
|
|
|
// Apply positioning based on closest edge
|
|
if (closestEdge.edge === 'left') {
|
|
x.value = closestEdge.distance // Maintain exact distance from left
|
|
y.value = verticalRatio * screenHeight
|
|
} else if (closestEdge.edge === 'right') {
|
|
x.value = screenWidth - menuWidth - closestEdge.distance // Maintain exact distance from right
|
|
y.value = verticalRatio * screenHeight
|
|
} else if (closestEdge.edge === 'top') {
|
|
x.value = horizontalRatio * screenWidth
|
|
y.value = closestEdge.distance // Maintain exact distance from top
|
|
} else {
|
|
// bottom
|
|
x.value = horizontalRatio * screenWidth
|
|
y.value = screenHeight - menuHeight - closestEdge.distance // Maintain exact distance from bottom
|
|
}
|
|
|
|
// Ensure the menu stays within the screen bounds
|
|
x.value = clamp(x.value, 0, screenWidth - menuWidth)
|
|
y.value = clamp(y.value, 0, screenHeight - menuHeight)
|
|
}
|
|
}
|
|
|
|
useEventListener(window, 'resize', adjustMenuPosition)
|
|
|
|
// Drop zone state
|
|
const isMouseOverDropZone = ref(false)
|
|
|
|
// Mouse event handlers for self-contained drop zone
|
|
const onMouseEnterDropZone = () => {
|
|
if (isDragging.value) {
|
|
isMouseOverDropZone.value = true
|
|
}
|
|
}
|
|
|
|
const onMouseLeaveDropZone = () => {
|
|
if (isDragging.value) {
|
|
isMouseOverDropZone.value = false
|
|
}
|
|
}
|
|
|
|
// Handle drag state changes
|
|
watch(isDragging, (dragging) => {
|
|
if (dragging) {
|
|
// Starting to drag - undock if docked
|
|
if (isDocked.value) {
|
|
isDocked.value = false
|
|
}
|
|
} else {
|
|
// Stopped dragging - dock if mouse is over drop zone
|
|
if (isMouseOverDropZone.value) {
|
|
isDocked.value = true
|
|
}
|
|
// Reset drop zone state
|
|
isMouseOverDropZone.value = false
|
|
}
|
|
})
|
|
const actionbarClass = computed(() =>
|
|
cn(
|
|
'w-[265px] border-dashed border-blue-500 opacity-80',
|
|
'm-1.5 flex items-center justify-center self-stretch',
|
|
'rounded-md before:w-50 before:-ml-50 before:h-full',
|
|
isMouseOverDropZone.value &&
|
|
'border-[3px] opacity-100 scale-105 shadow-[0_0_20px] shadow-blue-500'
|
|
)
|
|
)
|
|
const panelClass = computed(() =>
|
|
cn(
|
|
'actionbar pointer-events-auto z1000',
|
|
isDragging.value && 'select-none pointer-events-none',
|
|
isDocked.value
|
|
? 'p-0 static mr-2 border-none bg-transparent'
|
|
: 'fixed shadow-interface'
|
|
)
|
|
)
|
|
</script>
|