Docking action bar on top menu bar (#1119)

* Teleport when docked

* Docking logic

* Remove unnecessary v-show

* Docked panel style

* Drop zone highlight

* Rename test

* Add playwright test
This commit is contained in:
Chenlei Hu
2024-10-05 14:06:29 -04:00
committed by GitHub
parent 9c118c8e37
commit ad55722662
5 changed files with 112 additions and 18 deletions

View File

@@ -113,4 +113,15 @@ test.describe('Actionbar', () => {
).toBe(END)
expect(promptNumber, 'queued prompt count should be 2').toBe(2)
})
test('Can dock actionbar into top menu', async ({ comfyPage }) => {
await comfyPage.page.dragAndDrop(
'.actionbar .drag-handle',
'.comfyui-menu',
{
targetPosition: { x: 0, y: 0 }
}
)
expect(await comfyPage.actionbar.isDocked()).toBe(true)
})
})

View File

@@ -9,6 +9,11 @@ export class ComfyActionbar {
this.root = page.locator('.actionbar')
this.queueButton = new ComfyQueueButton(this)
}
async isDocked() {
const className = await this.root.getAttribute('class')
return className?.includes('is-docked') ?? false
}
}
class ComfyQueueButton {

View File

@@ -1,9 +1,8 @@
<template>
<Panel
v-show="visible"
class="actionbar w-fit"
:style="style"
:class="{ 'is-dragging': isDragging }"
:class="{ 'is-dragging': isDragging, 'is-docked': isDocked }"
>
<div class="actionbar-content flex items-center" ref="panelRef">
<span class="drag-handle cursor-move mr-2 p-0!" ref="dragHandleRef">
@@ -24,7 +23,7 @@
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { computed, inject, nextTick, onMounted, Ref, ref, watch } from 'vue'
import Panel from 'primevue/panel'
import Divider from 'primevue/divider'
import Button from 'primevue/button'
@@ -32,34 +31,48 @@ import ButtonGroup from 'primevue/buttongroup'
import ComfyQueueButton from './ComfyQueueButton.vue'
import { useSettingStore } from '@/stores/settingStore'
import { useCommandStore } from '@/stores/commandStore'
import { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'
import { debounce, clamp } from 'lodash'
import {
useDraggable,
useElementBounding,
useEventBus,
useEventListener,
useLocalStorage,
watchDebounced
} from '@vueuse/core'
import { clamp } from 'lodash'
const settingsStore = useSettingStore()
const commandStore = useCommandStore()
const visible = computed(
() => settingsStore.get('Comfy.UseNewMenu') === 'Floating'
() => settingsStore.get('Comfy.UseNewMenu') !== 'Disabled'
)
const panelRef = ref<HTMLElement | null>(null)
const dragHandleRef = ref<HTMLElement | null>(null)
const isDocked = useLocalStorage('Comfy.MenuPosition.Docked', false)
const storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', {
x: 0,
y: 0
})
const { x, y, style, isDragging } = useDraggable(panelRef, {
const {
x,
y,
style: style,
isDragging
} = useDraggable(panelRef, {
initialValue: { x: 0, y: 0 },
handle: dragHandleRef,
containerElement: document.body
})
// Update storedPosition when x or y changes
watch(
watchDebounced(
[x, y],
debounce(([newX, newY]) => {
([newX, newY]) => {
storedPosition.value = { x: newX, y: newY }
}, 300)
},
{ debounce: 300 }
)
// Set initial position to bottom center
@@ -109,6 +122,41 @@ const adjustMenuPosition = () => {
}
useEventListener(window, 'resize', adjustMenuPosition)
const topMenuRef = inject<Ref<HTMLDivElement | null>>('topMenuRef')
const topMenuBounds = useElementBounding(topMenuRef)
const overlapThreshold = 20 // pixels
const isOverlappingWithTopMenu = computed(() => {
if (!panelRef.value) {
return false
}
const { height } = panelRef.value.getBoundingClientRect()
const actionbarBottom = y.value + height
const topMenuBottom = topMenuBounds.bottom.value
const overlapPixels =
Math.min(actionbarBottom, topMenuBottom) -
Math.max(y.value, topMenuBounds.top.value)
return overlapPixels > overlapThreshold
})
watch(isDragging, (newIsDragging) => {
if (!newIsDragging) {
// Stop dragging
isDocked.value = isOverlappingWithTopMenu.value
} else {
// Start dragging
isDocked.value = false
}
})
const eventBus = useEventBus<string>('topMenu')
watch([isDragging, isOverlappingWithTopMenu], ([dragging, overlapping]) => {
eventBus.emit('updateHighlight', {
isDragging: dragging,
isOverlapping: overlapping
})
})
</script>
<style scoped>
@@ -118,6 +166,11 @@ useEventListener(window, 'resize', adjustMenuPosition)
z-index: 1000;
}
.actionbar.is-docked {
position: static;
@apply bg-transparent border-none p-0;
}
.actionbar.is-dragging {
user-select: none;
}

View File

@@ -1,6 +1,11 @@
<template>
<teleport to=".comfyui-body-top">
<div class="comfyui-menu flex items-center" v-show="betaMenuEnabled">
<div
ref="topMenuRef"
class="comfyui-menu flex items-center"
v-show="betaMenuEnabled"
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
>
<h1 class="comfyui-logo mx-2">ComfyUI</h1>
<Menubar
:model="items"
@@ -10,11 +15,11 @@
}"
/>
<Divider layout="vertical" class="mx-2" />
<WorkflowTabs
v-if="workflowTabsPosition === 'Topbar'"
class="flex-grow"
/>
<div class="flex-grow">
<WorkflowTabs v-if="workflowTabsPosition === 'Topbar'" />
</div>
<div class="comfyui-menu-right" ref="menuRight"></div>
<Actionbar />
</div>
</teleport>
</template>
@@ -23,10 +28,12 @@
import Menubar from 'primevue/menubar'
import Divider from 'primevue/divider'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, provide, ref } from 'vue'
import { useSettingStore } from '@/stores/settingStore'
import { app } from '@/scripts/app'
import { useEventBus } from '@vueuse/core'
const settingStore = useSettingStore()
const workflowTabsPosition = computed(() =>
@@ -45,6 +52,18 @@ onMounted(() => {
menuRight.value.appendChild(app.menu.element)
}
})
const topMenuRef = ref<HTMLDivElement | null>(null)
provide('topMenuRef', topMenuRef)
const eventBus = useEventBus<string>('topMenu')
const isDropZone = ref(false)
const isDroppable = ref(false)
eventBus.on((event: string, payload: any) => {
if (event === 'updateHighlight') {
isDropZone.value = payload.isDragging
isDroppable.value = payload.isOverlapping && payload.isDragging
}
})
</script>
<style scoped>
@@ -61,6 +80,14 @@ onMounted(() => {
max-height: 90vh;
}
.comfyui-menu.dropzone {
background: var(--p-highlight-background);
}
.comfyui-menu.dropzone-active {
background: var(--p-highlight-background-focus);
}
.comfyui-logo {
font-size: 1.2em;
user-select: none;

View File

@@ -6,7 +6,6 @@
<GlobalToast />
<UnloadWindowConfirmDialog />
<BrowserTabTitle />
<Actionbar />
</template>
<script setup lang="ts">
@@ -41,7 +40,6 @@ import ModelLibrarySidebarTab from '@/components/sidebar/tabs/ModelLibrarySideba
import GlobalToast from '@/components/toast/GlobalToast.vue'
import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDialog.vue'
import BrowserTabTitle from '@/components/BrowserTabTitle.vue'
import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
import WorkflowsSidebarTab from '@/components/sidebar/tabs/WorkflowsSidebarTab.vue'
import TopMenubar from '@/components/topbar/TopMenubar.vue'
import { setupAutoQueueHandler } from '@/services/autoQueueService'