Files
ComfyUI_frontend/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue
Christian Byrne bcb2bceb65 [backport core/1.40] fix: align run controls with queue modal design (#9134) (#9615)
Backport of #9134 to core/1.40.

Conflicts: 25 snapshot PNGs (accepted theirs), 2 Vue files (CSS class
ordering — accepted theirs).

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9134
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-07 18:50:39 -08:00

111 lines
2.7 KiB
Vue

<template>
<div ref="rootEl" class="relative size-full overflow-hidden bg-neutral-900">
<div class="p-terminal size-full rounded-none p-2">
<div ref="terminalEl" class="terminal-host h-full" />
</div>
<Button
v-tooltip.left="{
value: tooltipText,
showDelay: 300
}"
icon="pi pi-copy"
severity="secondary"
size="small"
:class="
cn('absolute top-2 right-8 transition-opacity', {
'pointer-events-none opacity-0 select-none': !isHovered
})
"
:aria-label="tooltipText"
@click="handleCopy"
/>
</div>
</template>
<script setup lang="ts">
import { useElementHover, useEventListener } from '@vueuse/core'
import type { IDisposable } from '@xterm/xterm'
import Button from 'primevue/button'
import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { cn } from '@/utils/tailwindUtil'
const { t } = useI18n()
const emit = defineEmits<{
created: [ReturnType<typeof useTerminal>, Ref<HTMLElement | undefined>]
unmounted: []
}>()
const terminalEl = ref<HTMLElement | undefined>()
const rootEl = ref<HTMLElement | undefined>()
const hasSelection = ref(false)
const isHovered = useElementHover(rootEl)
const terminalData = useTerminal(terminalEl)
emit('created', terminalData, ref(rootEl))
const { terminal } = terminalData
let selectionDisposable: IDisposable | undefined
const tooltipText = computed(() => {
return hasSelection.value
? t('serverStart.copySelectionTooltip')
: t('serverStart.copyAllTooltip')
})
const handleCopy = async () => {
const existingSelection = terminal.getSelection()
const shouldSelectAll = !existingSelection
if (shouldSelectAll) terminal.selectAll()
const selectedText = shouldSelectAll
? terminal.getSelection()
: existingSelection
if (selectedText) {
await navigator.clipboard.writeText(selectedText)
if (shouldSelectAll) {
terminal.clearSelection()
}
}
}
const showContextMenu = (event: MouseEvent) => {
event.preventDefault()
electronAPI()?.showContextMenu({ type: 'text' })
}
if (isElectron()) {
useEventListener(terminalEl, 'contextmenu', showContextMenu)
}
onMounted(() => {
selectionDisposable = terminal.onSelectionChange(() => {
hasSelection.value = terminal.hasSelection()
})
})
onUnmounted(() => {
selectionDisposable?.dispose()
emit('unmounted')
})
</script>
<style scoped>
@reference '../../../../assets/css/style.css';
:deep(.p-terminal) .xterm {
@apply overflow-hidden;
}
:deep(.p-terminal) .xterm-screen {
@apply bg-neutral-900 overflow-hidden;
}
</style>