Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node library search filters #636

Merged
merged 6 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading