feat: add fuzzy find to list manager

This commit is contained in:
Fritz Heiden 2025-04-08 19:47:55 +02:00
parent 36369fb1f0
commit c3562afe8e
3 changed files with 95 additions and 39 deletions

View File

@ -14,6 +14,7 @@
href="./src/lib/bootstrap-icons-1.11.3/font/bootstrap-icons.css" href="./src/lib/bootstrap-icons-1.11.3/font/bootstrap-icons.css"
rel="stylesheet" rel="stylesheet"
/> />
<script src="./lib/fusejs-7.1.0.min.js"></script>
<script src="./lib/popper.min.js"></script> <script src="./lib/popper.min.js"></script>
<script src="./lib/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js"></script> <script src="./lib/bootstrap-5.3.3-dist/js/bootstrap.bundle.min.js"></script>
<title>Playback Device Server</title> <title>Playback Device Server</title>

View File

@ -1,4 +1,4 @@
import { createMemo, createSignal, mergeProps } from "solid-js"; import { createEffect, createMemo, createSignal, mergeProps } from "solid-js";
function ListManager(props) { function ListManager(props) {
props = mergeProps( props = mergeProps(
@ -15,30 +15,44 @@ function ListManager(props) {
props props
); );
const itemToString = (item) => props.itemToString(item);
const [selectedAvailableItemIndex, setSelectedAvailableItemIndex] = const [selectedAvailableItemIndex, setSelectedAvailableItemIndex] =
createSignal(-1); createSignal(-1);
const [selectedItemIndex, setSelectedItemIndex] = createSignal(-1); const [selectedItemIndex, setSelectedItemIndex] = createSignal(-1);
const [itemsSearchString, setItemsSearchString] = createSignal("");
const [availableItemsSearchString, setAvailableItemsSearchString] =
createSignal("");
const itemsFuse = createMemo(
() =>
new Fuse(props.items, {
keys: [{ name: "label", getFn: (item) => props.itemToString(item) }],
})
);
const availableItemsFuse = createMemo(
() =>
new Fuse(props.availableItems, {
keys: [{ name: "label", getFn: (item) => props.itemToString(item) }],
})
);
createEffect(() =>
console.log(availableItemsFuse().search(availableItemsSearchString()))
);
const selectableAvailableItems = createMemo(() => const selectableAvailableItems = createMemo(() =>
props.availableItems (availableItemsSearchString()
.filter((item) => !props.items.includes(item)) ? availableItemsFuse()
.concat(props.availableItems) .search(availableItemsSearchString())
.concat(props.availableItems) .map((item) => item.item)
.concat(props.availableItems) : props.availableItems
.concat(props.availableItems) ).filter((item) => !props.items.includes(item))
.concat(props.availableItems) );
.concat(props.availableItems) const selectableItems = createMemo(() =>
.concat(props.availableItems) itemsSearchString()
.concat(props.availableItems) ? itemsFuse().search(itemsSearchString()).map((item) => item.item)
.concat(props.availableItems) : props.items
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
); );
const selectableItems = createMemo(() => props.items);
const canSelect = createMemo( const canSelect = createMemo(
() => () =>
selectedAvailableItemIndex() >= 0 && selectedAvailableItemIndex() >= 0 &&
@ -83,20 +97,56 @@ function ListManager(props) {
); );
} }
function ItemList(props) {
props = mergeProps(
{
items: [],
onItemSelected: () => {},
selectedItemIndex: -1,
onSearchStringChange: () => {},
},
props
);
return (
<>
<div class="input-group input-group-sm">
<span
class="input-group-text border-bottom-0 rounded-bottom-0"
id="basic-addon1"
>
<i class="bi bi-search"></i>
</span>
<input
type="text"
class="form-control form-control-sm border-bottom-0 rounded-bottom-0"
onInput={(event) => props.onSearchStringChange(event.target.value)}
/>
</div>
<div class="rounded rounded-top-0 border bg-body flex-fill overflow-y-scroll">
{props.items.map((item, index) => (
<ListItem
onClick={() => props.onItemSelected(index)}
selected={index === props.selectedItemIndex}
>
{itemToString(item)}
</ListItem>
))}
</div>
</>
);
}
return ( return (
<div class={"d-flex"} style={props.style}> <div class={"d-flex"} style={props.style}>
<div class="flex-shrink-1 w-50 d-flex flex-column"> <div class="flex-shrink-1 w-50 d-flex flex-column">
<div class="px-2">{props.itemsTitle}</div> <div class="px-2">{props.itemsTitle}</div>
<div class="rounded border bg-body flex-fill overflow-y-scroll"> <ItemList
{selectableItems().map((item, index) => ( items={selectableItems()}
<ListItem onItemSelected={(index) => setSelectedItemIndex(index)}
onClick={() => setSelectedItemIndex(index)} selectedItemIndex={selectedItemIndex()}
selected={index === selectedItemIndex()} onSearchStringChange={(value) => setItemsSearchString(value)}
> />
{props.itemToString(item)}
</ListItem>
))}
</div>
</div> </div>
<div class="p-2 d-flex flex-column justify-content-center align-items-center"> <div class="p-2 d-flex flex-column justify-content-center align-items-center">
<button <button
@ -116,16 +166,12 @@ function ListManager(props) {
</div> </div>
<div class="flex-shrink-1 w-50 d-flex flex-column"> <div class="flex-shrink-1 w-50 d-flex flex-column">
<div class="px-2">{props.availableItemsTitle}</div> <div class="px-2">{props.availableItemsTitle}</div>
<div class="rounded border bg-body flex-fill overflow-y-scroll"> <ItemList
{selectableAvailableItems().map((item, index) => ( items={selectableAvailableItems()}
<ListItem onItemSelected={(index) => setSelectedAvailableItemIndex(index)}
onClick={() => setSelectedAvailableItemIndex(index)} selectedItemIndex={selectedAvailableItemIndex()}
selected={index === selectedAvailableItemIndex()} onSearchStringChange={(value) => setAvailableItemsSearchString(value)}
> />
{props.itemToString(item)}
</ListItem>
))}
</div>
</div> </div>
</div> </div>
); );

9
www/src/lib/fusejs-7.1.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long