1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 14:31:44 +01:00

feat: improves search by showing all matches within threshold and enable scrolling instead (#3029)

This commit is contained in:
Amir Raminfar
2024-06-12 09:23:10 -07:00
committed by GitHub
parent 19ee1fedc4
commit 214b1825a8
2 changed files with 56 additions and 60 deletions

View File

@@ -57,9 +57,9 @@ describe("<FuzzySearchModal />", () => {
vi.mocked(useRouter().push).mockReset();
});
test("shows running all", async () => {
test("shows none initially", async () => {
const wrapper = createFuzzySearchModal();
expect(wrapper.findAll("li").length).toBe(3);
expect(wrapper.findAll("li").length).toBe(0);
});
test("search for foo", async () => {

View File

@@ -15,44 +15,49 @@
/>
<mdi:keyboard-esc class="flex" />
</div>
<ul tabindex="0" class="menu dropdown-content !relative mt-2 w-full rounded-box bg-base-lighter p-2">
<li v-for="(result, index) in data">
<a
class="grid auto-cols-max grid-cols-[min-content,auto] gap-2 py-4"
@click.prevent="selected(result.item)"
@mouseenter="selectedIndex = index"
:class="index === selectedIndex ? 'focus' : ''"
>
<div :class="{ 'text-primary': result.item.state === 'running' }">
<template v-if="result.item.type === 'container'">
<octicon:container-24 />
</template>
<template v-else-if="result.item.type === 'service'">
<ph:stack-simple />
</template>
<template v-else-if="result.item.type === 'stack'">
<ph:stack />
</template>
</div>
<div class="truncate">
<template v-if="config.hosts.length > 1 && result.item.host">
<span class="font-light">{{ result.item.host }}</span> /
</template>
<span data-name v-html="matchedName(result)"></span>
</div>
<DistanceTime :date="result.item.created" class="text-xs font-light" />
<div
class="dropdown-content !relative mt-2 max-h-[calc(100dvh-20rem)] w-full overflow-y-scroll rounded-md bg-base-lighter p-2"
v-if="results.length"
>
<ul tabindex="0" class="menu">
<li v-for="(result, index) in data" ref="listItems">
<a
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
class="hover:text-secondary"
class="grid auto-cols-max grid-cols-[min-content,auto] gap-2 py-4"
@click.prevent="selected(result.item)"
@mouseenter="selectedIndex = index"
:class="index === selectedIndex ? 'focus' : ''"
>
<ic:sharp-keyboard-return v-if="index === selectedIndex" />
<cil:columns v-else />
<div :class="{ 'text-primary': result.item.state === 'running' }">
<template v-if="result.item.type === 'container'">
<octicon:container-24 />
</template>
<template v-else-if="result.item.type === 'service'">
<ph:stack-simple />
</template>
<template v-else-if="result.item.type === 'stack'">
<ph:stack />
</template>
</div>
<div class="truncate">
<template v-if="config.hosts.length > 1 && result.item.host">
<span class="font-light">{{ result.item.host }}</span> /
</template>
<span data-name v-html="matchedName(result)"></span>
</div>
<DistanceTime :date="result.item.created" class="text-xs font-light" />
<a
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
class="hover:text-secondary"
>
<ic:sharp-keyboard-return v-if="index === selectedIndex" />
<cil:columns v-else />
</a>
</a>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</template>
@@ -61,14 +66,11 @@ import { ContainerState } from "@/types/Container";
import { useFuse } from "@vueuse/integrations/useFuse";
import { type FuseResult } from "fuse.js";
const { maxResults = 10 } = defineProps<{
maxResults?: number;
}>();
const close = defineEmit();
const query = ref("");
const input = ref<HTMLInputElement>();
const listItems = ref<HTMLInputElement[]>();
const selectedIndex = ref(0);
const router = useRouter();
@@ -133,26 +135,22 @@ const { results } = useFuse(query, list, {
threshold: 0.3,
includeMatches: true,
},
resultLimit: 10,
matchAllWhenSearchEmpty: true,
});
const data = computed(() => {
return [...results.value]
.sort((a: FuseResult<Item>, b: FuseResult<Item>) => {
if (a.score === b.score) {
if (a.item.state === b.item.state) {
return b.item.created.getTime() - a.item.created.getTime();
} else if (a.item.state === "running" && b.item.state !== "running") {
return -1;
} else {
return 1;
}
return [...results.value].sort((a: FuseResult<Item>, b: FuseResult<Item>) => {
if (a.score === b.score) {
if (a.item.state === b.item.state) {
return b.item.created.getTime() - a.item.created.getTime();
} else if (a.item.state === "running" && b.item.state !== "running") {
return -1;
} else {
return (a.score ?? 0) - (b.score ?? 0);
return 1;
}
})
.slice(0, maxResults);
} else {
return (a.score ?? 0) - (b.score ?? 0);
}
});
});
watch(query, (data) => {
@@ -161,6 +159,8 @@ watch(query, (data) => {
}
});
watch(selectedIndex, () => listItems.value?.[selectedIndex.value].scrollIntoView({ behavior: "smooth", block: "end" }));
useFocus(input, { initialValue: true });
function selected(item: Item) {
@@ -202,8 +202,4 @@ function matchedName({ item, matches = [] }: FuseResult<Item>) {
:deep(mark) {
@apply bg-transparent text-inherit underline underline-offset-2;
}
.menu a {
@apply transition-none duration-0;
}
</style>