playback-device-server/www/src/modules/list.jsx

212 lines
6.2 KiB
JavaScript

import {
Match,
Show,
createEffect,
createMemo,
createSignal,
mergeProps,
} from "solid-js";
function List(props) {
props = mergeProps({ items: [], showHeader: true, selectable: true }, props);
const [listItems, setListItems] = createSignal([]);
const selectedItems = createMemo(() =>
listItems()
.filter((item) => item.selected())
.map((item) => item.item)
);
const allItemsSelected = createMemo(() =>
listItems().every((item) => item.selected())
);
createEffect(() => {
setListItems(
props.items.map((item) => {
const [selected, setSelected] = createSignal(false);
return {
selected,
setSelected,
item: item,
};
})
);
});
createEffect(() => {
if (!props.onListItemsSelect) return;
props.onListItemsSelect(selectedItems());
});
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (props.onLazyLoad) {
props.onLazyLoad();
}
}
});
});
const [lazyLoadTriggerElement, setLazyLoadTriggerElement] = createSignal();
createEffect(() => {
observer.observe(lazyLoadTriggerElement());
});
const [scrollTop, setScrollTop] = createSignal(0);
const itemsMutationObserver = new MutationObserver(() => {
itemsContainerElement().scrollTop = scrollTop();
});
const [itemsContainerElement, setItemsContainerElement] = createSignal();
createEffect(() => {
if (!itemsContainerElement()) return;
itemsMutationObserver.observe(itemsContainerElement(), { childList: true });
});
function handleListItemClick(item) {
if (!props.onListItemClick) return;
props.onListItemClick(item);
}
function handleToggleAllItems() {
if (allItemsSelected()) {
listItems().forEach((item) => item.setSelected(false));
return;
}
listItems().forEach((item) => item.setSelected(true));
}
function renderHeaderRow(columns) {
columns = columns.filter((column) => !column.hidden);
if (!props.showHeader) return null;
return (
<div class="list-group-item d-flex p-0">
<div class="d-flex flex-fill border-bottom border-2 fw-bold overflow-y-scroll">
<Show when={props.selectable}>
<div
class="d-flex align-items-center justify-content-center border-end py-2 px-3"
onClick={() => handleToggleAllItems()}
>
<input
class="form-check-input m-0"
type="checkbox"
checked={allItemsSelected()}
id={""}
/>
</div>
</Show>
<For each={columns}>
{(column) => (
<div
class={"d-flex p-2" + (column.width ? "" : " flex-fill")}
style={column.width ? `width:${column.width}em` : ""}
>
<Show when={column.withIcons}>
<div class="pe-2" style="color: transparent">
<i class="bi-folder2" />
</div>
</Show>
<div class="text-truncate">
<span>{column.name}</span>
</div>
</div>
)}
</For>
</div>
</div>
);
}
function renderListItem(listItem, columns) {
columns = columns.filter((column) => !column.hidden);
let item = listItem.item;
return (
<a
role="button"
class="list-group-item list-group-item-action d-flex p-0 overflow-x-hidden border-0 border-bottom border-1"
style="max-width: 100%"
>
<Show when={props.selectable}>
<div
class="d-flex align-items-center justify-content-center border-end py-2 px-3"
onClick={() => listItem.setSelected(!listItem.selected())}
>
<input
class="form-check-input m-0"
type="checkbox"
checked={listItem.selected()}
id={""}
/>
</div>
</Show>
<div
class="d-flex flex-fill overflow-hidden"
onClick={() => handleListItemClick(item)}
>
<For each={columns}>
{(column) => (
<div
class={
"d-flex p-2 align-items-center" +
(column.width ? " flex-shrink-0" : " flex-fill")
}
style={
"overflow: hidden; " +
(column.width ? `width:${column.width}em` : "")
}
>
<Show when={column.withIcons}>
<Show when={!item[column.id].icon}>
<div class="pe-2" style="color: transparent">
<i class="bi-folder2" />
</div>
</Show>
<Show when={item[column.id].icon}>
<div class="pe-2">
<i class={item[column.id].icon} />
</div>
</Show>
</Show>
<Switch>
<Match when={item[column.id].html}>
{item[column.id].html}
</Match>
<Match when={item[column.id].text}>
<div class="text-truncate">
<span>{item[column.id]?.text}</span>
</div>
</Match>
</Switch>
</div>
)}
</For>
</div>
</a>
);
}
return (
<div
style={props.style}
class={
"list-group d-flex flex-column overflow-hidden " + (props.class || "")
}
>
{renderHeaderRow(props.columns)}
<div
class="list-group-item p-0 flex-fill overflow-y-scroll"
style="min-width: 0"
onScroll={(event) => setScrollTop(event.target.scrollTop)}
ref={setItemsContainerElement}
>
<For each={listItems()}>
{(listItem) => renderListItem(listItem, props.columns)}
</For>
<div
style="width: 100%; height: 3em;"
ref={setLazyLoadTriggerElement}
></div>
</div>
</div>
);
}
export default List;