mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
feat: add useSearch composable to search in Array or AnyTreeNode
This commit is contained in:
parent
3e60b96ee0
commit
501bce484a
3 changed files with 76 additions and 27 deletions
|
@ -1,18 +1,20 @@
|
|||
<script setup lang="ts" generic="T extends Obj">
|
||||
import type { Component } from 'vue'
|
||||
<script setup lang="ts" generic="T extends Obj | AnyTreeNode">
|
||||
import { computed, type Component } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { AnyTreeNode } from '@/helpers/data/tree'
|
||||
import type { Obj } from '@/types/commons'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
items: T[] | null
|
||||
items?: T[] | null
|
||||
itemsName: string | null
|
||||
filteredItems: T[] | null
|
||||
search?: string
|
||||
modelValue?: string
|
||||
skeleton?: string | Component
|
||||
}>(),
|
||||
{
|
||||
search: undefined,
|
||||
items: undefined,
|
||||
modelValue: undefined,
|
||||
skeleton: 'ListGroupSkeleton',
|
||||
},
|
||||
)
|
||||
|
@ -27,9 +29,21 @@ const slots = defineSlots<{
|
|||
skeleton: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:search': [value: string]
|
||||
defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const model = defineModel<string>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const noItemsMessage = computed(() => {
|
||||
if (props.items) return
|
||||
return t(
|
||||
props.items === undefined ? 'items_verbose_count' : 'search.not_found',
|
||||
{ items: t('items.' + props.itemsName, 0) },
|
||||
0,
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -45,11 +59,8 @@ const emit = defineEmits<{
|
|||
|
||||
<BFormInput
|
||||
id="top-bar-search"
|
||||
:modelValue="search"
|
||||
@update:modelValue="emit('update:search', $event)"
|
||||
:placeholder="
|
||||
$t('search.for', { items: $t('items.' + itemsName, 2) })
|
||||
"
|
||||
v-model="model"
|
||||
:placeholder="t('search.for', { items: t('items.' + itemsName, 2) })"
|
||||
:disabled="!items"
|
||||
/>
|
||||
</BInputGroup>
|
||||
|
@ -63,22 +74,10 @@ const emit = defineEmits<{
|
|||
</template>
|
||||
|
||||
<template #default>
|
||||
<BAlert
|
||||
v-if="items === null || filteredItems === null"
|
||||
:modelValue="true"
|
||||
variant="warning"
|
||||
>
|
||||
<BAlert v-if="noItemsMessage" :model-value="true" variant="warning">
|
||||
<slot name="alert-message">
|
||||
<YIcon iname="exclamation-triangle" />
|
||||
{{
|
||||
$t(
|
||||
items === null ? 'items_verbose_count' : 'search.not_found',
|
||||
{
|
||||
items: $t('items.' + itemsName, 0),
|
||||
},
|
||||
0,
|
||||
)
|
||||
}}
|
||||
{{ noItemsMessage }}
|
||||
</slot>
|
||||
</BAlert>
|
||||
|
||||
|
|
46
app/src/composables/useSearch.ts
Normal file
46
app/src/composables/useSearch.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue'
|
||||
import { computed, ref, toValue, watch } from 'vue'
|
||||
|
||||
import type { AnyTreeNode, TreeRootNode } from '@/helpers/data/tree'
|
||||
|
||||
export function useSearch<
|
||||
T extends any[] | TreeRootNode,
|
||||
V extends T extends (infer V)[] ? V : AnyTreeNode,
|
||||
>(
|
||||
items: MaybeRefOrGetter<T | undefined> | ComputedRef<T | undefined>,
|
||||
filterFn: (search: string, item: V, index: number, arr: T) => boolean,
|
||||
{
|
||||
externalSearch,
|
||||
filterIfNoSearch = false,
|
||||
filterAllFn,
|
||||
}: {
|
||||
filterAllFn?: (search: string, items: T) => boolean | undefined
|
||||
filterIfNoSearch?: boolean
|
||||
externalSearch?: MaybeRefOrGetter<string>
|
||||
} = {},
|
||||
): [search: Ref<string>, filteredItems: ComputedRef<T | undefined | null>] {
|
||||
const search = ref(toValue(externalSearch) ?? '')
|
||||
watch(
|
||||
() => toValue(externalSearch),
|
||||
(s) => (search.value = s ?? ''),
|
||||
)
|
||||
|
||||
const filteredItems = computed(() => {
|
||||
const items_ = toValue(items)
|
||||
const s = toValue(search.value).toLowerCase()
|
||||
if (!items_) return undefined
|
||||
if (filterAllFn) {
|
||||
const returnAll = filterAllFn(s, items_)
|
||||
if (returnAll !== undefined) {
|
||||
return returnAll ? items_ : undefined
|
||||
}
|
||||
}
|
||||
if (!s && !filterIfNoSearch) return items_
|
||||
const filteredItems_ = items_.filter((...args) =>
|
||||
filterFn(s, ...(args as [V, number, T])),
|
||||
) as T
|
||||
return filteredItems_.length ? filteredItems_ : null
|
||||
})
|
||||
|
||||
return [search, filteredItems]
|
||||
}
|
|
@ -106,6 +106,10 @@ class TreeNode {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this.children.length
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeRootNode extends TreeNode {
|
||||
|
|
Loading…
Add table
Reference in a new issue