mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Port sidebar documentation to vue component
This commit is contained in:
@@ -715,24 +715,3 @@ audio.comfy-audio.empty-audio-widget {
|
|||||||
.p-tree-node-content {
|
.p-tree-node-content {
|
||||||
padding: var(--comfy-tree-explorer-item-padding) !important;
|
padding: var(--comfy-tree-explorer-item-padding) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.doc-node {
|
|
||||||
font-size: 1.5em
|
|
||||||
}
|
|
||||||
.doc-section {
|
|
||||||
background-color: var(--comfy-menu-bg)
|
|
||||||
}
|
|
||||||
.doc-item div {
|
|
||||||
margin-inline-start: 1vw;
|
|
||||||
}
|
|
||||||
@keyframes selectAnimation {
|
|
||||||
0% { background-color: #5555}
|
|
||||||
80% { background-color: #5555}
|
|
||||||
100% { background-color: #0000}
|
|
||||||
}
|
|
||||||
.doc-item:focus {
|
|
||||||
animation: selectAnimation 2s;
|
|
||||||
}
|
|
||||||
.DocumentationIcon:before {
|
|
||||||
font-size: 1.5em; content: '?';
|
|
||||||
}
|
|
||||||
|
|||||||
206
src/components/sidebar/tabs/DocumentationSidebarTab.vue
Normal file
206
src/components/sidebar/tabs/DocumentationSidebarTab.vue
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="!hasAnyDoc()">No documentation available</div>
|
||||||
|
<div v-else-if="Array.isArray(def.description)" ref="docElement">
|
||||||
|
{{ def[1] }}
|
||||||
|
</div>
|
||||||
|
<div v-else ref="docElement">
|
||||||
|
<div class="doc-node">{{ def.display_name }}</div>
|
||||||
|
<div v-if="hasInputDoc()" class="doc-section">Inputs</div>
|
||||||
|
<div
|
||||||
|
v-if="hasInputDoc()"
|
||||||
|
v-for="input in inputs"
|
||||||
|
tabindex="-1"
|
||||||
|
class="doc-item"
|
||||||
|
>
|
||||||
|
{{ input[0] }}
|
||||||
|
<div>{{ input[1] }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="outputs.length" class="doc-section">Outputs</div>
|
||||||
|
<div
|
||||||
|
v-if="outputs.length"
|
||||||
|
v-for="output in outputs"
|
||||||
|
tabindex="-1"
|
||||||
|
class="doc-item"
|
||||||
|
>
|
||||||
|
{{ output[0] }}
|
||||||
|
<div>{{ output[1] }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { DocItem } from 'DocItems'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
|
|
||||||
|
let def
|
||||||
|
const inputs = ref([])
|
||||||
|
const outputs = ref([])
|
||||||
|
const docElement = ref(null)
|
||||||
|
|
||||||
|
function hasInputDoc() {
|
||||||
|
return !!inputs.value.length
|
||||||
|
}
|
||||||
|
function hasAnyDoc() {
|
||||||
|
return def?.description || inputs.value.length || outputs.value.length
|
||||||
|
}
|
||||||
|
function setCollapse(el, doCollapse) {
|
||||||
|
if (doCollapse) {
|
||||||
|
el.children[0].children[0].innerHTML = '+'
|
||||||
|
Object.assign(el.children[1].style, {
|
||||||
|
color: '#CCC',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
width: '0px',
|
||||||
|
minWidth: 'calc(100% - 20px)',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
})
|
||||||
|
for (let child of el.children[1].children) {
|
||||||
|
if (child.style.display != 'none') {
|
||||||
|
child.origDisplay = child.style.display
|
||||||
|
}
|
||||||
|
child.style.display = 'none'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el.children[0].children[0].innerHTML = '-'
|
||||||
|
Object.assign(el.children[1].style, {
|
||||||
|
color: '',
|
||||||
|
overflowX: '',
|
||||||
|
width: '100%',
|
||||||
|
minWidth: '',
|
||||||
|
textOverflow: '',
|
||||||
|
whiteSpace: ''
|
||||||
|
})
|
||||||
|
for (let child of el.children[1].children) {
|
||||||
|
child.style.display = child.origDisplay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function collapseOnClick() {
|
||||||
|
let doCollapse = this.children[0].innerHTML == '-'
|
||||||
|
setCollapse(this.parentElement, doCollapse)
|
||||||
|
}
|
||||||
|
function selectHelp(name: string, value?: string) {
|
||||||
|
if (!docElement.value) {
|
||||||
|
console.log("doc element doesn't exist")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (def[2]?.select) {
|
||||||
|
return def[2].select(docElement.value, name, value)
|
||||||
|
}
|
||||||
|
//attempt to navigate to name in help
|
||||||
|
function collapseUnlessMatch(items, t) {
|
||||||
|
var match = items.querySelector('[doc_title="' + t + '"]')
|
||||||
|
if (!match) {
|
||||||
|
for (let i of items.children) {
|
||||||
|
if (i.innerHTML.slice(0, t.length + 5).includes(t)) {
|
||||||
|
match = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!match) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
//For longer documentation items with fewer collapsable elements,
|
||||||
|
//scroll to make sure the entirety of the selected item is visible
|
||||||
|
match.scrollIntoView({ block: 'nearest' })
|
||||||
|
//The previous floating help implementation would try to scroll the window
|
||||||
|
//itself if the display was partiall offscreen. As the sidebar documentation
|
||||||
|
//does not pan with the canvas, this should no longer be needed
|
||||||
|
//window.scrollTo(0, 0)
|
||||||
|
for (let i of items.querySelectorAll('.doc_collapse')) {
|
||||||
|
if (i.contains(match)) {
|
||||||
|
setCollapse(i, false)
|
||||||
|
} else {
|
||||||
|
setCollapse(i, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
let target = collapseUnlessMatch(docElement.value, name)
|
||||||
|
if (target) {
|
||||||
|
target.focus()
|
||||||
|
if (value) {
|
||||||
|
collapseUnlessMatch(target, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.tooltipCallback = function (node, name, value) {
|
||||||
|
if (node != app.canvas.current_node || name == 'DESCRIPTION') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
selectHelp(name, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNode(node?) {
|
||||||
|
console.log('updating node')
|
||||||
|
node ||= app.canvas.current_node
|
||||||
|
if (!node) {
|
||||||
|
// Graph has no nodes
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def = LiteGraph.getNodeType(node.type).nodeData
|
||||||
|
let input_temp = []
|
||||||
|
for (let k in def?.input?.required) {
|
||||||
|
if (def.input.required[k][1]?.tooltip) {
|
||||||
|
input_temp.push([k, def.input.required[k][1].tooltip])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let k in def?.optional?.required) {
|
||||||
|
if (def.input.optional[k][1]?.tooltip) {
|
||||||
|
input_temp.push([k, def.input.optional[k][1].tooltip])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputs.value = input_temp
|
||||||
|
if (def.output_tooltips) {
|
||||||
|
const outputs_temp = []
|
||||||
|
const output_name = def.output_name || def.output
|
||||||
|
for (let i = 0; i < def.output_tooltips.length; i++) {
|
||||||
|
outputs_temp[i] = [output_name[i], def.output_tooltips[i]]
|
||||||
|
}
|
||||||
|
outputs.value = outputs_temp
|
||||||
|
} else {
|
||||||
|
outputs.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => app.canvas.current_node,
|
||||||
|
(node) => {
|
||||||
|
console.log('watched')
|
||||||
|
updateNode(ndoe)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
updateNode()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.doc-node {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.doc-section {
|
||||||
|
background-color: var(--comfy-menu-bg);
|
||||||
|
}
|
||||||
|
.doc-item div {
|
||||||
|
margin-inline-start: 1vw;
|
||||||
|
}
|
||||||
|
@keyframes selectAnimation {
|
||||||
|
0% {
|
||||||
|
background-color: #5555;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
background-color: #5555;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doc-item:focus {
|
||||||
|
animation: selectAnimation 2s;
|
||||||
|
}
|
||||||
|
.DocumentationIcon:before {
|
||||||
|
font-size: 1.5em;
|
||||||
|
content: '?';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
import { app } from '../../scripts/app'
|
|
||||||
import { getColorPalette } from './colorPalette'
|
|
||||||
import type { CustomSidebarTabExtension } from '../../types/extensionTypes'
|
|
||||||
import { LiteGraph } from '@comfyorg/litegraph'
|
|
||||||
|
|
||||||
var helpDOM = document.createElement('div')
|
|
||||||
var cdef
|
|
||||||
|
|
||||||
function setCollapse(el, doCollapse) {
|
|
||||||
if (doCollapse) {
|
|
||||||
el.children[0].children[0].innerHTML = '+'
|
|
||||||
Object.assign(el.children[1].style, {
|
|
||||||
color: '#CCC',
|
|
||||||
overflowX: 'hidden',
|
|
||||||
width: '0px',
|
|
||||||
minWidth: 'calc(100% - 20px)',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap'
|
|
||||||
})
|
|
||||||
for (let child of el.children[1].children) {
|
|
||||||
if (child.style.display != 'none') {
|
|
||||||
child.origDisplay = child.style.display
|
|
||||||
}
|
|
||||||
child.style.display = 'none'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
el.children[0].children[0].innerHTML = '-'
|
|
||||||
Object.assign(el.children[1].style, {
|
|
||||||
color: '',
|
|
||||||
overflowX: '',
|
|
||||||
width: '100%',
|
|
||||||
minWidth: '',
|
|
||||||
textOverflow: '',
|
|
||||||
whiteSpace: ''
|
|
||||||
})
|
|
||||||
for (let child of el.children[1].children) {
|
|
||||||
child.style.display = child.origDisplay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function collapseOnClick() {
|
|
||||||
let doCollapse = this.children[0].innerHTML == '-'
|
|
||||||
setCollapse(this.parentElement, doCollapse)
|
|
||||||
}
|
|
||||||
function selectHelp(name: string, value?: string) {
|
|
||||||
if (cdef[2]?.select) {
|
|
||||||
return cdef[2].select(this, name, value)
|
|
||||||
}
|
|
||||||
//attempt to navigate to name in help
|
|
||||||
function collapseUnlessMatch(items, t) {
|
|
||||||
var match = items.querySelector('[doc_title="' + t + '"]')
|
|
||||||
if (!match) {
|
|
||||||
for (let i of items.children) {
|
|
||||||
if (i.innerHTML.slice(0, t.length + 5).includes(t)) {
|
|
||||||
match = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!match) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
//For longer documentation items with fewer collapsable elements,
|
|
||||||
//scroll to make sure the entirety of the selected item is visible
|
|
||||||
match.scrollIntoView({ block: 'nearest' })
|
|
||||||
//The previous floating help implementation would try to scroll the window
|
|
||||||
//itself if the display was partiall offscreen. As the sidebar documentation
|
|
||||||
//does not pan with the canvas, this should no longer be needed
|
|
||||||
//window.scrollTo(0, 0)
|
|
||||||
for (let i of items.querySelectorAll('.doc_collapse')) {
|
|
||||||
if (i.contains(match)) {
|
|
||||||
setCollapse(i, false)
|
|
||||||
} else {
|
|
||||||
setCollapse(i, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
let target = collapseUnlessMatch(helpDOM, name)
|
|
||||||
if (target) {
|
|
||||||
target.focus()
|
|
||||||
if (value) {
|
|
||||||
collapseUnlessMatch(target, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app.tooltipCallback = function (node, name, value) {
|
|
||||||
if (node != app.canvas.current_node) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (name == 'DESCRIPTION') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
selectHelp(name, value)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
function updateNode(node?) {
|
|
||||||
//Always use latest node. If it lacks documentation, that should be communicated
|
|
||||||
//instead of confusing users by picking a different recent node that does
|
|
||||||
node ||= app.canvas.current_node
|
|
||||||
const def = LiteGraph.getNodeType(node.type).nodeData
|
|
||||||
if (cdef == def) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cdef = def
|
|
||||||
if (Array.isArray(def.description)) {
|
|
||||||
helpDOM.innerHTML = def.description[1]
|
|
||||||
} else {
|
|
||||||
//do additional parsing to prettify output and combine tooltips
|
|
||||||
let content = ''
|
|
||||||
if (def.description) {
|
|
||||||
content += '<section>' + def.description + '</section>'
|
|
||||||
}
|
|
||||||
let inputs = []
|
|
||||||
for (let input in def?.input?.required || {}) {
|
|
||||||
if (def.input.required[input][1]?.tooltip) {
|
|
||||||
inputs.push([input, def.input.required[input][1].tooltip])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let input in def?.input?.optional || {}) {
|
|
||||||
if (def.input.optional[input][1]?.tooltip) {
|
|
||||||
inputs.push([input, def.input.optional[input][1].tooltip])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inputs.length) {
|
|
||||||
content += '<div class="doc-section">Inputs</div>'
|
|
||||||
for (let [k, v] of inputs) {
|
|
||||||
content +=
|
|
||||||
'<div tabindex=-1 class="doc-item">' +
|
|
||||||
k +
|
|
||||||
'<div>' +
|
|
||||||
v +
|
|
||||||
'</div></div>'
|
|
||||||
}
|
|
||||||
//content += "</div>"
|
|
||||||
//content += '<br><br><div>' + inputs.join('</div><div>') + '</div>'
|
|
||||||
}
|
|
||||||
if (def.output_tooltips) {
|
|
||||||
content += '<div class="doc-section">Outputs</div>'
|
|
||||||
let outputs = def.output_name || def.output
|
|
||||||
for (let i = 0; i < outputs.length; i++) {
|
|
||||||
content +=
|
|
||||||
'<div tabindex=-1 class="doc-item">' +
|
|
||||||
outputs[i] +
|
|
||||||
'<div>' +
|
|
||||||
def.output_tooltips[i] +
|
|
||||||
'</div></div>'
|
|
||||||
}
|
|
||||||
//outputs += '</div>'
|
|
||||||
}
|
|
||||||
if (content == '') {
|
|
||||||
content = 'No documentation available'
|
|
||||||
}
|
|
||||||
content = '<div class="doc-node">' + def.display_name + '</div>' + content
|
|
||||||
helpDOM.innerHTML = content
|
|
||||||
if (cdef.description[2]?.render) {
|
|
||||||
cdef.description[2].render(helpDOM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bringToFront
|
|
||||||
class DocumentationSidebar implements CustomSidebarTabExtension {
|
|
||||||
id = 'documentationSidebar'
|
|
||||||
title = 'Documentation'
|
|
||||||
type
|
|
||||||
icon = 'DocumentationIcon'
|
|
||||||
render(e) {
|
|
||||||
if (!bringToFront) {
|
|
||||||
var bringToFront = app.canvas.bringToFront
|
|
||||||
app.canvas.bringToFront = function (node) {
|
|
||||||
updateNode(node)
|
|
||||||
return bringToFront.apply(this, arguments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateNode()
|
|
||||||
if (!e?.children?.length) {
|
|
||||||
e.appendChild(helpDOM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app.extensionManager.registerSidebarTab(new DocumentationSidebar())
|
|
||||||
@@ -22,4 +22,3 @@ import './webcamCapture'
|
|||||||
import './widgetInputs'
|
import './widgetInputs'
|
||||||
import './uploadAudio'
|
import './uploadAudio'
|
||||||
import './nodeBadge'
|
import './nodeBadge'
|
||||||
import './documentationSidebar'
|
|
||||||
|
|||||||
Reference in New Issue
Block a user