feat: add create command modal

This commit is contained in:
Fritz Heiden 2025-04-07 14:25:53 +02:00
parent dbaa7a768c
commit 9a59326afa
8 changed files with 353 additions and 14 deletions

View File

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

View File

@ -1,9 +1,16 @@
function Command({ id, protocol, command, device, command_type, title } = {}) {
function Command({
id,
protocol,
command: commandNumber,
device,
commandType,
title,
} = {}) {
let _id = id;
let _protocol = protocol;
let _command = command;
let _commandNumber = commandNumber;
let _device = device;
let _command_type = command_type;
let _commandType = commandType;
let _title = title;
function getId() {
@ -22,12 +29,12 @@ function Command({ id, protocol, command, device, command_type, title } = {}) {
_protocol = protocol;
}
function getCommand() {
return _command;
function getCommandNumber() {
return _commandNumber;
}
function setCommand(command) {
_command = command;
function setCommandNumber(commandNumber) {
_commandNumber = commandNumber;
}
function getDevice() {
@ -39,11 +46,11 @@ function Command({ id, protocol, command, device, command_type, title } = {}) {
}
function getCommandType() {
return _command_type;
return _commandType;
}
function setCommandType(command_type) {
_command_type = command_type;
function setCommandType(commandType) {
_commandType = commandType;
}
function getTitle() {
@ -59,8 +66,8 @@ function Command({ id, protocol, command, device, command_type, title } = {}) {
setId,
getProtocol,
setProtocol,
getCommand,
setCommand,
getCommandNumber,
setCommandNumber,
getDevice,
setDevice,
getCommandType,
@ -70,4 +77,73 @@ function Command({ id, protocol, command, device, command_type, title } = {}) {
};
}
Command.Protocols = {
samsung: "Samsung",
nec: "NEC",
onkyo: "Onkyo",
apple: "Apple",
denon: "Denon",
sharp: "Sharp",
panasonic: "Panasonic",
kaseikyo: "Kaseikyo",
jvc: "JVC",
lg: "LG",
sony: "Sony",
rc5: "RC5",
rc6: "RC6",
universal_pulse_distance: "Universal Pulse Distance",
universal_pulse_width: "Universal Pulse Width",
universal_pulse_distance_width: "Universal Pulse Distance Width",
hash: "Hash",
pronto: "Pronto",
bose_wave: "BoseWave",
bang_olufsen: "Bang & Olufsen",
lego: "Lego",
fast: "FAST",
whynter: "Whynter",
magiquest: "MagiQuest",
}
Command.CommandTypes = {
power: "Power",
input: "Input",
one: "1",
two: "2",
three: "3",
four: "4",
five: "5",
six: "6",
seven: "7",
eight: "8",
nine: "9",
zero: "0",
volume_up: "Volume Up",
volume_down: "Volume Down",
mute: "Mute",
channel_up: "Channel Up",
channel_down: "Channel Down",
menu: "Menu",
home: "Home",
settings: "Settings",
options: "Options",
up_arrow: "Up Arrow",
down_arrow: "Down Arrow",
left_arrow: "Left Arrow",
right_arrow: "Right Arrow",
select: "Select",
info: "Info",
back: "Back",
exit: "Exit",
red: "Red",
green: "Green",
yellow: "Yellow",
blue: "Blue",
rewind: "Rewind",
play: "Play",
pause: "Pause",
stop: "Stop",
forward: "Forward",
other: "Other",
}
export default Command;

View File

@ -1,6 +1,7 @@
import PlaybackDevice from "./playback-device.js";
import User from "./user.js";
import Integration from "./integration.js";
import Command from "./command.js";
const Serializer = (function () {
function deserializeUser(object) {
@ -29,6 +30,15 @@ const Serializer = (function () {
if (!objects) return [];
return objects.map((object) => deserializeIntegration(object));
}
function deserializeCommand(object) {
return new Command(object);
}
function deserializeCommands(objects) {
if (!objects) return [];
return objects.map((object) => deserializeCommand(object));
}
return {
deserializeUser,
@ -37,6 +47,8 @@ const Serializer = (function () {
deserializeDevices,
deserializeIntegration,
deserializeIntegrations,
deserializeCommand,
deserializeCommands,
};
})();

6
www/src/lib/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,215 @@
import { createEffect, createMemo, createSignal } from "solid-js";
import ValidatedTextInput from "../modules/validated-text-input.jsx";
import EventEmitter from "../tools/event-emitter.js";
import ModalHandler from "./modal-handler.js";
import Modal from "./modal.jsx";
import RemotesService from "../services/remotes-service.js";
import Command from "../data/command.js";
const eventEmitter = new EventEmitter();
const COMMAND_CREATED_EVENT = "success";
const MIN_TITLE_LENGTH = 3;
function CreateCommandModal(props) {
const [protocol, setProtocol] = createSignal("");
const [commandNumber, setCommandNumber] = createSignal("");
const [device, setDevice] = createSignal("");
const [commandType, setCommandType] = createSignal("");
const [title, setTitle] = createSignal("");
const [error, setError] = createSignal("");
const isProtocolValid = createMemo(() => protocol() !== "");
const isCommandNumberValid = createMemo(
() => commandNumber() !== "" && !isNaN(commandNumber())
);
const isDeviceValid = createMemo(() => device() !== "" && !isNaN(device()));
const isCommandTypeValid = createMemo(() => commandType() !== "");
const isTitleValid = createMemo(() => title().length >= MIN_TITLE_LENGTH);
createEffect(() => {
let commandString = commandType() ? Command.CommandTypes[commandType()] : "";
let protocolString = protocol() ? Command.Protocols[protocol()] : "";
setTitle(commandString + " " + protocolString);
});
const isFormValid = createMemo(
() =>
isProtocolValid() &&
isCommandNumberValid() &&
isDeviceValid() &&
isCommandTypeValid() &&
isTitleValid()
);
async function handleCreateCommand() {
let command;
try {
command = await RemotesService.createCommand({
protocol: protocol(),
commandNumber: commandNumber(),
device: device(),
commandType: commandType(),
title: title(),
});
} catch (e) {
setError(e.message);
return;
}
resetFields();
CreateCommandModal.Handler.hide();
eventEmitter.dispatchEvent(COMMAND_CREATED_EVENT, command);
}
function resetFields() {
setProtocol("");
setCommandNumber("");
setDevice("");
setCommandType("");
setTitle("");
setError("");
}
return (
<Modal
ref={props.ref}
id="createCommandModal"
modalTitle="New Command"
centered={true}
>
<div class="modal-body" style="overflow-y:inherit !important;">
<Show when={error() !== ""}>
<div class="alert alert-danger" role="alert">
{error()}
</div>
</Show>
<div class="mb-3 row">
<label for="new_command_protocol" class="col-form-label col-sm-3">
Protocol
</label>
<div class="col-sm-9">
<div class="btn-group">
<button
type="button"
class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{protocol() ? Command.Protocols[protocol()] : "Please select"}
</button>
<ul
class="dropdown-menu"
style="max-height: 10em; overflow-y: auto;"
>
{Object.keys(Command.Protocols).map((protocol) => (
<li>
<a
class="dropdown-item pe-auto"
onClick={() => setProtocol(protocol)}
>
{Command.Protocols[protocol]}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="new_command_number" class="col-form-label col-sm-3">
Command Number
</label>
<ValidatedTextInput
class="col-sm-9"
id="new_command_number"
valid={isCommandNumberValid()}
value={commandNumber()}
onInput={(e) => setCommandNumber(e.target.value)}
errorText={"Command number must be a number"}
/>
</div>
<div class="mb-3 row">
<label
for="new_command_device_number"
class="col-form-label col-sm-3"
>
Device Number
</label>
<ValidatedTextInput
class="col-sm-9"
id="new_command_device_number"
valid={isDeviceValid()}
value={device()}
onInput={(e) => setDevice(e.target.value)}
errorText={"Device number must be a number"}
/>
</div>
<div class="mb-3 row">
<label for="new_command_protocol" class="col-form-label col-sm-3">
Command Type
</label>
<div class="col-sm-9">
<div class="btn-group">
<button
type="button"
class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{commandType()
? Command.CommandTypes[commandType()]
: "Please select"}
</button>
<ul
class="dropdown-menu"
style="max-height: 10em; overflow-y: auto;"
>
{Object.keys(Command.CommandTypes).map((commandType) => (
<li>
<a
class="dropdown-item pe-auto"
onClick={() => setCommandType(commandType)}
>
{Command.CommandTypes[commandType]}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="new_command_title" class="col-form-label col-sm-3">
Title
</label>
<ValidatedTextInput
class="col-sm-9"
id="new_command_title"
valid={isTitleValid()}
value={title()}
onInput={(e) => setTitle(e.target.value)}
errorText={`Title must be at least ${MIN_TITLE_LENGTH} characters long`}
/>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel
</button>
<button
type="button"
onClick={handleCreateCommand}
class="btn btn-primary"
disabled={!isFormValid()}
>
Create
</button>
</div>
</Modal>
);
}
CreateCommandModal.Handler = new ModalHandler();
CreateCommandModal.onCommandCreated = (callback) =>
eventEmitter.on(COMMAND_CREATED_EVENT, callback);
export default CreateCommandModal;

View File

@ -6,6 +6,7 @@ import UserSettingsModal from "./user-settings-modal.jsx";
import CreateDeviceModal from "./create-device-modal.jsx";
import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx";
import DeleteIntegrationModal from "./delete-integration-modal.jsx";
import CreateCommandModal from "./create-command-modal.jsx";
const ModalRegistry = (function () {
const modals = [
@ -39,6 +40,11 @@ const ModalRegistry = (function () {
component: DeleteIntegrationModal,
ref: null,
},
{
id: "newCommandModal",
component: CreateCommandModal,
ref: null,
},
];
function getModals(props) {

View File

@ -1,15 +1,26 @@
import Serializer from "../data/serializer";
function RemotesService() {
let commands = [];
async function getRemotes() {
return [];
}
async function getCommands() {
return [];
return [].concat(commands);
}
async function createCommand(commandObject) {
let command = Serializer.deserializeCommand(commandObject);
commands.push(command);
return command;
}
return {
getRemotes,
getCommands,
createCommand,
};
}

View File

@ -1,6 +1,7 @@
import { createResource, onMount } from "solid-js";
import List from "../../modules/list";
import RemotesService from "../../services/remotes-service";
import CreateCommandModal from "../../modals/create-command-modal";
function CommandsList(props) {
const [commands, { refetch: refetchCommands }] = createResource(
@ -11,7 +12,18 @@ function CommandsList(props) {
refetchCommands();
});
function handleNewCommand() {}
CreateCommandModal.onCommandCreated(() => {
console.log("CREATED");
refetchCommands();
});
function handleNewCommand() {
refetchCommands();
CreateCommandModal.Handler.show();
}
function handleDeleteCommand(command) {
}
return (
<>