Bookmark folder icon customization (#647)

* Add bookmark customization support

* WIP

* Fix bugs

* Fix color update

* Handle rename and delete of customization

* nit

* Add custom color picker

* Computed final color

* i18n

* Remove cancel button as dialog already has it

* Add playwright test
This commit is contained in:
Chenlei Hu
2024-08-26 21:30:38 -04:00
committed by GitHub
parent c604209f40
commit 0795c3041c
8 changed files with 470 additions and 10 deletions

View File

@@ -86,6 +86,12 @@
</template>
</SidebarTabTemplate>
<ContextMenu ref="menu" :model="menuItems" />
<FolderCustomizationDialog
v-model="showCustomizationDialog"
@confirm="updateCustomization"
:initialIcon="initialIcon"
:initialColor="initialColor"
/>
</template>
<script setup lang="ts">
@@ -105,6 +111,7 @@ import ContextMenu from 'primevue/contextmenu'
import EditableText from '@/components/common/EditableText.vue'
import NodePreview from '@/components/node/NodePreview.vue'
import SearchBox from '@/components/common/SearchBox.vue'
import FolderCustomizationDialog from '@/components/common/CustomizationDialog.vue'
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
import { useSettingStore } from '@/stores/settingStore'
import { app } from '@/scripts/app'
@@ -171,6 +178,11 @@ const getTreeNodeIcon = (node: TreeNode) => {
// If the node is a bookmark folder, show a bookmark icon
if (node.data && node.data.isDummyFolder) {
const customization =
nodeBookmarkStore.bookmarksCustomization[node.data.nodePath]
if (customization?.icon) {
return 'pi ' + customization.icon
}
return 'pi pi-bookmark-fill'
}
@@ -280,13 +292,22 @@ const menuItems = computed<MenuItem[]>(() => [
command: () => {
renameEditingNode.value = menuTargetNode.value
}
},
{
label: t('customize'),
icon: 'pi pi-palette',
command: () => {
initialIcon.value =
nodeBookmarkStore.bookmarksCustomization[
menuTargetNode.value.data.nodePath
]?.icon || nodeBookmarkStore.defaultBookmarkIcon
initialColor.value =
nodeBookmarkStore.bookmarksCustomization[
menuTargetNode.value.data.nodePath
]?.color || nodeBookmarkStore.defaultBookmarkColor
showCustomizationDialog.value = true
}
}
// TODO: Add customize color and icon feature.
// {
// label: t('customize'),
// icon: 'pi pi-palette',
// command: () => console.log('customize')
// }
])
const handleContextMenu = (node: TreeNode, e: MouseEvent) => {
@@ -320,6 +341,18 @@ const addNewBookmarkFolder = (parent?: ComfyNodeDefImpl) => {
renameEditingNode.value = findNodeByKey(renderedRoot.value, newFolderKey)
})
}
const showCustomizationDialog = ref(false)
const initialIcon = ref(nodeBookmarkStore.defaultBookmarkIcon)
const initialColor = ref(nodeBookmarkStore.defaultBookmarkColor)
const updateCustomization = (icon: string, color: string) => {
if (menuTargetNode.value?.data) {
nodeBookmarkStore.updateBookmarkCustomization(
menuTargetNode.value.data.nodePath,
{ icon, color }
)
}
}
</script>
<style>

View File

@@ -26,7 +26,8 @@ import type { CanvasDragAndDropData } from '@/types/litegraphTypes'
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
import Badge from 'primevue/badge'
import type { TreeNode } from 'primevue/treenode'
import { onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import type { BookmarkCustomization } from '@/types/apiTypes'
const props = defineProps<{
node: TreeNode
@@ -39,6 +40,10 @@ const emit = defineEmits<{
const nodeBookmarkStore = useNodeBookmarkStore()
const customization = computed<BookmarkCustomization | undefined>(() => {
return nodeBookmarkStore.bookmarksCustomization[props.node.data.nodePath]
})
const addNodeToBookmarkFolder = (node: ComfyNodeDefImpl) => {
if (!props.node.data) {
console.error('Bookmark folder does not have data!')
@@ -56,15 +61,20 @@ const addNodeToBookmarkFolder = (node: ComfyNodeDefImpl) => {
const container = ref<HTMLElement | null>(null)
const canDrop = ref(false)
const treeNodeElement = ref<HTMLElement | null>(null)
const iconElement = ref<HTMLElement | null>(null)
let dropTargetCleanup = () => {}
let stopWatchCustomization: (() => void) | null = null
onMounted(() => {
if (!props.isBookmarkFolder) return
const treeNodeElement = container.value?.closest(
treeNodeElement.value = container.value?.closest(
'.p-tree-node-content'
) as HTMLElement
dropTargetCleanup = dropTargetForElements({
element: treeNodeElement,
element: treeNodeElement.value,
onDrop: (event) => {
const dndData = event.source.data as CanvasDragAndDropData
if (dndData.type === 'add-node') {
@@ -83,8 +93,34 @@ onMounted(() => {
canDrop.value = false
}
})
iconElement.value = treeNodeElement.value.querySelector(
':scope > .p-tree-node-icon'
) as HTMLElement
updateIconColor()
// Start watching after the component is mounted
stopWatchCustomization = watch(customization, updateIconColor, { deep: true })
})
const updateIconColor = () => {
if (iconElement.value && customization.value) {
iconElement.value.style.color = customization.value.color
}
}
onUnmounted(() => {
dropTargetCleanup()
if (stopWatchCustomization) {
stopWatchCustomization()
}
})
</script>
<style scoped>
.node-tree-folder {
display: flex;
align-items: center;
}
</style>