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">
|
<script setup lang="ts" generic="T extends Obj | AnyTreeNode">
|
||||||
import type { Component } from 'vue'
|
import { computed, type Component } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import type { AnyTreeNode } from '@/helpers/data/tree'
|
||||||
import type { Obj } from '@/types/commons'
|
import type { Obj } from '@/types/commons'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
items: T[] | null
|
items?: T[] | null
|
||||||
itemsName: string | null
|
itemsName: string | null
|
||||||
filteredItems: T[] | null
|
modelValue?: string
|
||||||
search?: string
|
|
||||||
skeleton?: string | Component
|
skeleton?: string | Component
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
search: undefined,
|
items: undefined,
|
||||||
|
modelValue: undefined,
|
||||||
skeleton: 'ListGroupSkeleton',
|
skeleton: 'ListGroupSkeleton',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -27,9 +29,21 @@ const slots = defineSlots<{
|
||||||
skeleton: any
|
skeleton: any
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
defineEmits<{
|
||||||
'update:search': [value: string]
|
'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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -45,11 +59,8 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
<BFormInput
|
<BFormInput
|
||||||
id="top-bar-search"
|
id="top-bar-search"
|
||||||
:modelValue="search"
|
v-model="model"
|
||||||
@update:modelValue="emit('update:search', $event)"
|
:placeholder="t('search.for', { items: t('items.' + itemsName, 2) })"
|
||||||
:placeholder="
|
|
||||||
$t('search.for', { items: $t('items.' + itemsName, 2) })
|
|
||||||
"
|
|
||||||
:disabled="!items"
|
:disabled="!items"
|
||||||
/>
|
/>
|
||||||
</BInputGroup>
|
</BInputGroup>
|
||||||
|
@ -63,22 +74,10 @@ const emit = defineEmits<{
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<BAlert
|
<BAlert v-if="noItemsMessage" :model-value="true" variant="warning">
|
||||||
v-if="items === null || filteredItems === null"
|
|
||||||
:modelValue="true"
|
|
||||||
variant="warning"
|
|
||||||
>
|
|
||||||
<slot name="alert-message">
|
<slot name="alert-message">
|
||||||
<YIcon iname="exclamation-triangle" />
|
<YIcon iname="exclamation-triangle" />
|
||||||
{{
|
{{ noItemsMessage }}
|
||||||
$t(
|
|
||||||
items === null ? 'items_verbose_count' : 'search.not_found',
|
|
||||||
{
|
|
||||||
items: $t('items.' + itemsName, 0),
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</slot>
|
</slot>
|
||||||
</BAlert>
|
</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 {
|
export class TreeRootNode extends TreeNode {
|
||||||
|
|
Loading…
Add table
Reference in a new issue