feat: implement list-manager

This commit is contained in:
Fritz Heiden 2025-04-08 17:09:23 +02:00
parent fa89bd904f
commit 36369fb1f0
3 changed files with 177 additions and 11 deletions

View File

@ -1,11 +1,134 @@
import { mergeProps } from "solid-js";
import { createMemo, createSignal, mergeProps } from "solid-js";
function ListManager(props) {
props = mergeProps(
{ items: [], availableItems: [], itemToString: () => "" },
{
items: [],
availableItems: [],
itemToString: () => "",
style: "",
onItemSelect: () => {},
onItemDeselect: () => {},
itemsTitle: "Selected items",
availableItemsTitle: "Available items",
},
props
);
return <></>;
const [selectedAvailableItemIndex, setSelectedAvailableItemIndex] =
createSignal(-1);
const [selectedItemIndex, setSelectedItemIndex] = createSignal(-1);
const selectableAvailableItems = createMemo(() =>
props.availableItems
.filter((item) => !props.items.includes(item))
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.concat(props.availableItems)
.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(
() =>
selectedAvailableItemIndex() >= 0 &&
selectedAvailableItemIndex() < selectableAvailableItems().length
);
const canDeselect = createMemo(
() =>
selectedItemIndex() >= 0 && selectedItemIndex() < selectableItems().length
);
function handleSelectItem() {
let itemIndex = selectedAvailableItemIndex();
let item = selectableAvailableItems()[itemIndex];
if (itemIndex === selectableAvailableItems().length - 1) {
setSelectedAvailableItemIndex(itemIndex - 1);
}
props.onItemSelect(item);
}
function handleDeselectItem() {
let itemIndex = selectedItemIndex();
let item = selectableItems()[itemIndex];
if (itemIndex === selectableItems().length - 1) {
setSelectedItemIndex(itemIndex - 1);
}
props.onItemDeselect(item);
}
function ListItem(props) {
props = mergeProps(
{ children: "", selected: false, onClick: () => {} },
props
);
return (
<div
class={"px-2" + (props.selected ? " bg-secondary" : "")}
role="button"
onClick={props.onClick}
>
{props.children}
</div>
);
}
return (
<div class={"d-flex"} style={props.style}>
<div class="flex-shrink-1 w-50 d-flex flex-column">
<div class="px-2">{props.itemsTitle}</div>
<div class="rounded border bg-body flex-fill overflow-y-scroll">
{selectableItems().map((item, index) => (
<ListItem
onClick={() => setSelectedItemIndex(index)}
selected={index === selectedItemIndex()}
>
{props.itemToString(item)}
</ListItem>
))}
</div>
</div>
<div class="p-2 d-flex flex-column justify-content-center align-items-center">
<button
class="btn btn-sm btn-outline-secondary mb-2"
onClick={handleSelectItem}
disabled={!canSelect()}
>
<i class="bi bi-caret-left-fill"></i>
</button>
<button
class="btn btn-sm btn-outline-secondary"
onClick={handleDeselectItem}
disabled={!canDeselect()}
>
<i class="bi bi-caret-right-fill"></i>
</button>
</div>
<div class="flex-shrink-1 w-50 d-flex flex-column">
<div class="px-2">{props.availableItemsTitle}</div>
<div class="rounded border bg-body flex-fill overflow-y-scroll">
{selectableAvailableItems().map((item, index) => (
<ListItem
onClick={() => setSelectedAvailableItemIndex(index)}
selected={index === selectedAvailableItemIndex()}
>
{props.itemToString(item)}
</ListItem>
))}
</div>
</div>
</div>
);
}
export default ListManager;

View File

@ -42,6 +42,16 @@ function CreateRemoteModal(props) {
setError("");
}
function handleCommandSelect(item) {
setCommands([...commands(), item]);
}
function handleCommandDeselect(item) {
setCommands(
commands().filter((command) => command.getId() !== item.getId())
);
}
return (
<Modal
ref={props.ref}
@ -49,7 +59,7 @@ function CreateRemoteModal(props) {
modalTitle="New Remote"
centered={true}
>
<div class="modal-body" style="overflow-y:inherit !important;">
<div class="modal-body bg-body-tertiary">
<Show when={error() !== ""}>
<div class="alert alert-danger" role="alert">
{error()}
@ -68,12 +78,19 @@ function CreateRemoteModal(props) {
errorText={`Title must be at least ${MIN_TITLE_LENGTH} characters long`}
/>
</div>
</div>
<ListManager
style="height: 20em;"
items={commands()}
availableItems={availableCommands()}
itemToString={(command) => command.getName()}
itemToString={(command) =>
`${command.getTitle()} (${command.getProtocol()}:${command.getCommandType()})`
}
onItemSelect={handleCommandSelect}
onItemDeselect={handleCommandDeselect}
itemsTitle="Selected Commands"
availableItemsTitle="Available Commands"
/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel

View File

@ -1,7 +1,33 @@
import Command from "../data/command";
import Serializer from "../data/serializer";
function RemotesService() {
let commands = [];
let commands = [
new Command({
id: 1,
protocol: "samsung",
commandNumber: 1,
device: 7,
commandType: "power",
title: "Power Samsung",
}),
new Command({
id: 2,
protocol: "samsung",
commandNumber: 2,
device: 7,
commandType: "input",
title: "Input Samsung",
}),
new Command({
id: 3,
protocol: "samsung",
commandNumber: 3,
device: 7,
commandType: "volume_up",
title: "Volume Up Samsung",
}),
];
let remotes = [];
async function getRemotes() {