Skip to content

Commit

Permalink
Node library search filters (#636)
Browse files Browse the repository at this point in the history
* Add search filters to node library

* Fix

* Dont close on add

* Fix wildcard

---------

Co-authored-by: Chenlei Hu <chenlei.hu@mail.utoronto.ca>
  • Loading branch information
pythongosssss and huchenlei authored Aug 28, 2024
1 parent fef9395 commit 968f417
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 95 deletions.
73 changes: 61 additions & 12 deletions src/components/common/SearchBox.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
<template>
<IconField :class="props.class">
<InputIcon :class="props.icon" />
<InputText
class="search-box-input"
@input="handleInput"
:modelValue="props.modelValue"
:placeholder="props.placeholder"
/>
</IconField>
<div :class="props.class">
<IconField>
<InputIcon :class="props.icon" />
<InputText
class="search-box-input"
:class="{ ['with-filter']: props.filterIcon }"
@input="handleInput"
:modelValue="props.modelValue"
:placeholder="props.placeholder"
/>
<Button
v-if="props.filterIcon"
class="p-inputicon"
:icon="props.filterIcon"
text
severity="contrast"
@click="$emit('showFilter', $event)"
/>
</IconField>
<div class="search-filters" v-if="filters">
<SearchFilterChip
v-for="filter in filters"
:key="filter.id"
:text="filter.text"
:badge="filter.badge"
:badge-class="filter.badgeClass"
@remove="$emit('removeFilter', filter)"
/>
</div>
</div>
</template>

<script setup lang="ts">
<script setup lang="ts" generic="TFilter extends SearchFilter">
import type { SearchFilter } from './SearchFilterChip.vue'
import { debounce } from 'lodash'
import IconField from 'primevue/iconfield'
import InputIcon from 'primevue/inputicon'
import InputText from 'primevue/inputtext'
import Button from 'primevue/button'
import SearchFilterChip from './SearchFilterChip.vue'
import { toRefs } from 'vue'
interface Props {
class?: string
modelValue: string
placeholder?: string
icon?: string
debounceTime?: number
filterIcon?: string
filters?: TFilter[]
}
const props = withDefaults(defineProps<Props>(), {
Expand All @@ -30,10 +57,17 @@ const props = withDefaults(defineProps<Props>(), {
debounceTime: 300
})
const emit = defineEmits(['update:modelValue', 'search'])
const { filters } = toRefs(props)
const emit = defineEmits([
'update:modelValue',
'search',
'showFilter',
'removeFilter'
])
const emitSearch = debounce((value: string) => {
emit('search', value)
emit('search', value, props.filters)
}, props.debounceTime)
const handleInput = (event: Event) => {
Expand All @@ -46,5 +80,20 @@ const handleInput = (event: Event) => {
<style scoped>
.search-box-input {
width: 100%;
padding-left: 36px;
}
.search-box-input.with-filter {
padding-right: 36px;
}
.p-button.p-inputicon {
padding: 0;
width: auto;
border: none !important;
}
.search-filters {
@apply pt-2 flex flex-wrap gap-2;
}
</style>
41 changes: 41 additions & 0 deletions src/components/common/SearchFilterChip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<Chip removable @remove="$emit('remove', $event)">
<Badge size="small" :class="badgeClass">
{{ badge }}
</Badge>
{{ text }}
</Chip>
</template>

<script setup lang="ts">
import Chip from 'primevue/chip'
import Badge from 'primevue/badge'
export interface SearchFilter {
text: string
badge: string
badgeClass: string
id: string | number
}
defineProps<Omit<SearchFilter, 'id'>>()
defineEmits(['remove'])
</script>

<style scoped>
:deep(.i-badge) {
@apply bg-green-500 text-white;
}
:deep(.o-badge) {
@apply bg-red-500 text-white;
}
:deep(.c-badge) {
@apply bg-blue-500 text-white;
}
:deep(.s-badge) {
@apply bg-yellow-500;
}
</style>
60 changes: 35 additions & 25 deletions src/components/searchbox/NodeSearchBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,22 @@
v-if="hoveredSuggestion"
/>
</div>
<NodeSearchFilter @addFilter="onAddFilter" />

<Button
icon="pi pi-filter"
severity="secondary"
class="_filter-button"
@click="nodeSearchFilterVisible = true"
/>
<Dialog v-model:visible="nodeSearchFilterVisible" class="_dialog">
<template #header>
<h3>Add node filter condition</h3>
</template>
<div class="_dialog-body">
<NodeSearchFilter @addFilter="onAddFilter"></NodeSearchFilter>
</div>
</Dialog>

<AutoCompletePlus
:model-value="props.filters"
class="comfy-vue-node-search-box"
Expand Down Expand Up @@ -56,12 +71,12 @@
</template>
<!-- FilterAndValue -->
<template v-slot:chip="{ value }">
<Chip removable @remove="onRemoveFilter($event, value)">
<Badge size="small" :class="value[0].invokeSequence + '-badge'">
{{ value[0].invokeSequence.toUpperCase() }}
</Badge>
{{ value[1] }}
</Chip>
<SearchFilterChip
@remove="onRemoveFilter($event, value)"
:text="value[1]"
:badge="value[0].invokeSequence.toUpperCase()"
:badge-class="value[0].invokeSequence + '-badge'"
/>
</template>
</AutoCompletePlus>
</div>
Expand All @@ -70,16 +85,17 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import AutoCompletePlus from '@/components/primevueOverride/AutoCompletePlus.vue'
import Chip from 'primevue/chip'
import Badge from 'primevue/badge'
import Tag from 'primevue/tag'
import Dialog from 'primevue/dialog'
import Button from 'primevue/button'
import NodeSearchFilter from '@/components/searchbox/NodeSearchFilter.vue'
import NodeSourceChip from '@/components/node/NodeSourceChip.vue'
import { type FilterAndValue } from '@/services/nodeSearchService'
import NodePreview from '@/components/node/NodePreview.vue'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
import { useSettingStore } from '@/stores/settingStore'
import { useI18n } from 'vue-i18n'
import SearchFilterChip from '../common/SearchFilterChip.vue'
const settingStore = useSettingStore()
const { t } = useI18n()
Expand All @@ -101,6 +117,7 @@ const props = defineProps({
}
})
const nodeSearchFilterVisible = ref(false)
const inputId = `comfy-vue-node-search-box-input-${Math.random()}`
const suggestions = ref<ComfyNodeDefImpl[]>([])
const hoveredSuggestion = ref<ComfyNodeDefImpl | null>(null)
Expand Down Expand Up @@ -136,6 +153,7 @@ const reFocusInput = () => {
onMounted(reFocusInput)
const onAddFilter = (filterAndValue: FilterAndValue) => {
nodeSearchFilterVisible.value = false
emit('addFilter', filterAndValue)
reFocusInput()
}
Expand Down Expand Up @@ -188,22 +206,6 @@ const setHoverSuggestion = (index: number) => {
white-space: nowrap;
}
.i-badge {
@apply bg-green-500 text-white;
}
.o-badge {
@apply bg-red-500 text-white;
}
.c-badge {
@apply bg-blue-500 text-white;
}
.s-badge {
@apply bg-yellow-500;
}
:deep(.highlight) {
background-color: var(--p-primary-color);
color: var(--p-primary-contrast-color);
Expand All @@ -212,4 +214,12 @@ const setHoverSuggestion = (index: number) => {
padding: 0rem 0.125rem;
margin: -0.125rem 0.125rem;
}
._filter-button {
z-index: 10;
}
._dialog {
@apply min-w-96;
}
</style>
73 changes: 26 additions & 47 deletions src/components/searchbox/NodeSearchFilter.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,35 @@
<template>
<Button
icon="pi pi-filter"
severity="secondary"
class="_filter-button"
@click="showModal"
/>
<Dialog v-model:visible="visible" class="_dialog">
<template #header>
<h3>Add node filter condition</h3>
</template>
<div class="_dialog-body">
<SelectButton
v-model="selectedFilter"
:options="filters"
:allowEmpty="false"
optionLabel="name"
@change="updateSelectedFilterValue"
/>
<AutoComplete
v-model="selectedFilterValue"
:suggestions="filterValues"
:min-length="0"
@complete="(event) => updateFilterValues(event.query)"
completeOnFocus
forceSelection
dropdown
></AutoComplete>
</div>
<template #footer>
<Button type="button" label="Add" @click="submit"></Button>
</template>
</Dialog>
<div class="_content">
<SelectButton
v-model="selectedFilter"
:options="filters"
:allowEmpty="false"
optionLabel="name"
@change="updateSelectedFilterValue"
/>
<AutoComplete
v-model="selectedFilterValue"
:suggestions="filterValues"
:min-length="0"
@complete="(event) => updateFilterValues(event.query)"
completeOnFocus
forceSelection
dropdown
></AutoComplete>
</div>
<div class="_footer">
<Button type="button" label="Add" @click="submit"></Button>
</div>
</template>

<script setup lang="ts">
import { NodeFilter, type FilterAndValue } from '@/services/nodeSearchService'
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import SelectButton from 'primevue/selectbutton'
import AutoComplete from 'primevue/autocomplete'
import { ref, onMounted } from 'vue'
import { useNodeDefStore } from '@/stores/nodeDefStore'
const visible = ref<boolean>(false)
const filters = ref<NodeFilter[]>([])
const selectedFilter = ref<NodeFilter>()
const filterValues = ref<string[]>([])
Expand All @@ -69,29 +56,21 @@ const updateFilterValues = (query: string) => {
}
const submit = () => {
visible.value = false
emit('addFilter', [
selectedFilter.value,
selectedFilterValue.value
] as FilterAndValue)
}
const showModal = () => {
updateSelectedFilterValue()
visible.value = true
}
onMounted(updateSelectedFilterValue)
</script>

<style scoped>
._filter-button {
z-index: 10;
}
._dialog {
@apply min-w-96;
._content {
@apply flex flex-col space-y-2;
}
._dialog-body {
@apply flex flex-col space-y-2;
._footer {
@apply flex flex-col pt-4 items-end;
}
</style>
Loading

0 comments on commit 968f417

Please sign in to comment.