mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-01 19:20:10 +00:00
## Summary Small updates to the UI to make it more visually distinct from the graph nodes and improving the login button ## Changes - **What**: - Improve theme support with dynamic colors - Standardize surface colors/borders - Add shadows to all floating UI elements - Change side toolbar to be docked by default - Decrease side toolbar width - Change login button to icon only - Update C button to be more distinctive ## Review Focus - Theme tokens, I am not sure what the overall plan for how these tokens will be supported for custom user palettes. I've implemented a method where default variables are chosen that look nice over all existing themes, but they can be overridden. ## Screenshots Dark theme updated color <img width="958" height="615" alt="image" src="https://github.com/user-attachments/assets/a2c540cf-6c05-4ad3-996b-8b7b80df8d2d" /> Themed & updated menu button (active vs hover vs default) <img width="58" height="338" alt="github" src="https://github.com/user-attachments/assets/90244ee2-d5ee-4b26-9d99-f4b8439aa372" /><img width="58" height="338" alt="nord" src="https://github.com/user-attachments/assets/053e8e7b-d639-4b72-92d2-ec7e2167aab8" /><img width="58" height="338" alt="arc" src="https://github.com/user-attachments/assets/3caeb07b-d41b-4d88-83b4-ef8d45d4e5a6" /><img width="58" height="338" alt="solarized" src="https://github.com/user-attachments/assets/6ebf6afb-ec3a-436b-90eb-bda40be1c79f" /><img width="58" height="338" alt="light" src="https://github.com/user-attachments/assets/fbb7f96a-b722-40c5-86fa-a0732e166972" /><img width="58" height="338" alt="dark" src="https://github.com/user-attachments/assets/5459208b-9256-4c55-9373-169e9cd9f09a" /> With labels <img width="58" height="394" alt="labels" src="https://github.com/user-attachments/assets/f97ee354-35cd-42b8-896f-57ac39644c1d" /> Logged out (only visible on desktop) <img width="323" height="48" alt="logged out" src="https://github.com/user-attachments/assets/75b71420-0c1b-446f-8cdd-43c68674d346" /> Logged in <img width="355" height="48" alt="logged in" src="https://github.com/user-attachments/assets/324fd133-a793-49dd-844a-f93dd5d1effb" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6381-UI-color-updates-tweaks-29b6d73d3650816384d2ef5617a776a0) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com>
265 lines
7.5 KiB
Vue
265 lines
7.5 KiB
Vue
<template>
|
|
<div
|
|
class="subgraph-breadcrumb w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
|
|
:class="{
|
|
'subgraph-breadcrumb-collapse': collapseTabs,
|
|
'subgraph-breadcrumb-overflow': overflowingTabs
|
|
}"
|
|
:style="{
|
|
'--p-breadcrumb-gap': `0px`,
|
|
'--p-breadcrumb-item-margin': `${ITEM_GAP / 2}px`,
|
|
'--p-breadcrumb-item-min-width': `${MIN_WIDTH}px`,
|
|
'--p-breadcrumb-item-padding': `${ITEM_PADDING}px`,
|
|
'--p-breadcrumb-icon-width': `${ICON_WIDTH}px`
|
|
}"
|
|
>
|
|
<Breadcrumb
|
|
ref="breadcrumbRef"
|
|
class="w-fit rounded-lg p-0"
|
|
:model="items"
|
|
:pt="{ item: { class: 'pointer-events-auto' } }"
|
|
:aria-label="$t('g.graphNavigation')"
|
|
>
|
|
<template #item="{ item }">
|
|
<SubgraphBreadcrumbItem
|
|
:item="item"
|
|
:is-active="item === items.at(-1)"
|
|
/>
|
|
</template>
|
|
<template #separator
|
|
><span style="transform: scale(1.5)"> / </span></template
|
|
>
|
|
</Breadcrumb>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import Breadcrumb from 'primevue/breadcrumb'
|
|
import type { MenuItem } from 'primevue/menuitem'
|
|
import { computed, onUpdated, ref, watch } from 'vue'
|
|
|
|
import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'
|
|
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
|
|
import { useTelemetry } from '@/platform/telemetry'
|
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
|
import { useSubgraphStore } from '@/stores/subgraphStore'
|
|
import { forEachSubgraphNode } from '@/utils/graphTraversalUtil'
|
|
|
|
const MIN_WIDTH = 28
|
|
const ITEM_GAP = 8
|
|
const ITEM_PADDING = 8
|
|
const ICON_WIDTH = 20
|
|
|
|
const workflowStore = useWorkflowStore()
|
|
const navigationStore = useSubgraphNavigationStore()
|
|
const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>()
|
|
const workflowName = computed(() => workflowStore.activeWorkflow?.filename)
|
|
const isBlueprint = computed(() =>
|
|
useSubgraphStore().isSubgraphBlueprint(workflowStore.activeWorkflow)
|
|
)
|
|
const collapseTabs = ref(false)
|
|
const overflowingTabs = ref(false)
|
|
|
|
const breadcrumbElement = computed(() => {
|
|
if (!breadcrumbRef.value) return null
|
|
|
|
const el = (breadcrumbRef.value as unknown as { $el: HTMLElement }).$el
|
|
const list = el?.querySelector('.p-breadcrumb-list') as HTMLElement
|
|
return list
|
|
})
|
|
|
|
const items = computed(() => {
|
|
const items = navigationStore.navigationStack.map<MenuItem>((subgraph) => ({
|
|
label: subgraph.name,
|
|
command: () => {
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'breadcrumb_subgraph_item_selected'
|
|
})
|
|
const canvas = useCanvasStore().getCanvas()
|
|
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
|
|
|
canvas.setGraph(subgraph)
|
|
},
|
|
updateTitle: (title: string) => {
|
|
const rootGraph = useCanvasStore().getCanvas().graph?.rootGraph
|
|
if (!rootGraph) return
|
|
|
|
forEachSubgraphNode(rootGraph, subgraph.id, (node) => {
|
|
node.title = title
|
|
})
|
|
}
|
|
}))
|
|
|
|
return [home.value, ...items]
|
|
})
|
|
|
|
const home = computed(() => ({
|
|
label: workflowName.value,
|
|
icon: 'pi pi-home',
|
|
key: 'root',
|
|
isBlueprint: isBlueprint.value,
|
|
command: () => {
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'breadcrumb_subgraph_root_selected'
|
|
})
|
|
const canvas = useCanvasStore().getCanvas()
|
|
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
|
|
|
canvas.setGraph(canvas.graph.rootGraph)
|
|
}
|
|
}))
|
|
|
|
// Check for overflow on breadcrumb items and collapse/expand the breadcrumb to fit
|
|
let overflowObserver: ReturnType<typeof useOverflowObserver> | undefined
|
|
watch(breadcrumbElement, (el) => {
|
|
overflowObserver?.dispose()
|
|
overflowObserver = undefined
|
|
|
|
if (!el) return
|
|
|
|
overflowObserver = useOverflowObserver(el, {
|
|
onCheck: (isOverflowing) => {
|
|
overflowingTabs.value = isOverflowing
|
|
|
|
if (collapseTabs.value) {
|
|
// Items are currently hidden, check if we can show them
|
|
if (!isOverflowing) {
|
|
const items = [
|
|
...el.querySelectorAll('.p-breadcrumb-item')
|
|
] as HTMLElement[]
|
|
|
|
if (items.length < 3) return
|
|
|
|
const itemsWithIcon = items.filter((item) =>
|
|
item.querySelector('.p-breadcrumb-item-link-icon-visible')
|
|
).length
|
|
const separators = el.querySelectorAll(
|
|
'.p-breadcrumb-separator'
|
|
) as NodeListOf<HTMLElement>
|
|
const separator = separators[separators.length - 1] as HTMLElement
|
|
const separatorWidth = separator.offsetWidth
|
|
|
|
// items + separators + gaps + icons
|
|
const itemsWidth =
|
|
(MIN_WIDTH + ITEM_PADDING + ITEM_PADDING) * items.length +
|
|
itemsWithIcon * ICON_WIDTH
|
|
const separatorsWidth = (items.length - 1) * separatorWidth
|
|
const gapsWidth = (items.length - 1) * (ITEM_GAP * 2)
|
|
const totalWidth = itemsWidth + separatorsWidth + gapsWidth
|
|
const containerWidth = el.clientWidth
|
|
|
|
if (totalWidth <= containerWidth) {
|
|
collapseTabs.value = false
|
|
}
|
|
}
|
|
} else if (isOverflowing) {
|
|
collapseTabs.value = true
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
// If e.g. the workflow name changes, we need to check the overflow again
|
|
onUpdated(() => {
|
|
if (!overflowObserver?.disposed.value) {
|
|
overflowObserver?.checkOverflow()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
@reference '../../assets/css/style.css';
|
|
|
|
.subgraph-breadcrumb:not(:empty) {
|
|
flex: auto;
|
|
flex-shrink: 10000;
|
|
min-width: 120px;
|
|
}
|
|
|
|
.subgraph-breadcrumb,
|
|
:deep(.p-breadcrumb) {
|
|
@apply overflow-hidden;
|
|
}
|
|
|
|
:deep(.p-breadcrumb) {
|
|
width: 100%;
|
|
background-color: transparent;
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item) {
|
|
@apply flex items-center overflow-hidden;
|
|
min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem);
|
|
/* Collapse middle items first */
|
|
flex-shrink: 10000;
|
|
}
|
|
|
|
:deep(.p-breadcrumb-separator) {
|
|
display: flex;
|
|
padding: 0 var(--p-breadcrumb-item-margin);
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item-link) {
|
|
padding: 0
|
|
calc(var(--p-breadcrumb-item-margin) + var(--p-breadcrumb-item-padding));
|
|
}
|
|
|
|
:deep(.p-breadcrumb-separator),
|
|
:deep(.p-breadcrumb-item) {
|
|
@apply h-12;
|
|
border-top: 1px solid var(--interface-stroke);
|
|
border-bottom: 1px solid var(--interface-stroke);
|
|
background-color: var(--comfy-menu-bg);
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item:has(.p-breadcrumb-item-link-icon-visible)) {
|
|
min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem + 20px);
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item:first-child) {
|
|
@apply rounded-l-lg;
|
|
/* Then collapse the root workflow */
|
|
flex-shrink: 5000;
|
|
border-left: 1px solid var(--interface-stroke);
|
|
|
|
.p-breadcrumb-item-link {
|
|
padding-left: var(--p-breadcrumb-item-padding);
|
|
}
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item:last-child) {
|
|
@apply rounded-r-lg;
|
|
/* Then collapse the active item */
|
|
flex-shrink: 1;
|
|
border-right: 1px solid var(--interface-stroke);
|
|
}
|
|
|
|
:deep(.p-breadcrumb-item-link:hover),
|
|
:deep(.p-breadcrumb-item-link-menu-visible) {
|
|
background-color: color-mix(
|
|
in srgb,
|
|
var(--fg-color) 10%,
|
|
var(--comfy-menu-bg)
|
|
) !important;
|
|
color: var(--fg-color);
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
@reference '../../assets/css/style.css';
|
|
|
|
.subgraph-breadcrumb-collapse .p-breadcrumb-list {
|
|
.p-breadcrumb-item,
|
|
.p-breadcrumb-separator {
|
|
@apply hidden;
|
|
}
|
|
|
|
.p-breadcrumb-item:nth-last-child(3),
|
|
.p-breadcrumb-separator:nth-last-child(2),
|
|
.p-breadcrumb-item:nth-last-child(1) {
|
|
@apply flex;
|
|
}
|
|
}
|
|
</style>
|