feat: add edit command

This commit is contained in:
Fritz Heiden 2025-04-15 16:04:38 +02:00
parent 8f3832657b
commit 6a9c69a535
7 changed files with 276 additions and 7 deletions

View File

@ -202,6 +202,15 @@ func (db *RemoteDatabase) GetCommands() ([]Command, error) {
return commands, nil
}
func (db *RemoteDatabase) UpdateCommand(command Command) error {
queryString := "UPDATE Commands SET protocol = ?, commandNumber = ?, device = ?, commandType = ?, title = ? WHERE id = ?"
_, error := db.Connection.Exec(queryString, command.Protocol, command.CommandNumber, command.Device, command.CommandType, command.Title, command.Id)
if error != nil {
return fmt.Errorf("error updating command %s: %s", command.Id, error)
}
return nil
}
func (db *RemoteDatabase) DeleteCommand(commandId string) error {
queryString := "DELETE FROM Commands WHERE id = ?"
_, error := db.Connection.Exec(queryString, commandId)

View File

@ -49,6 +49,10 @@ func (rm *RemoteManager) GetCommands() ([]d.Command, error) {
return rm.remoteDatabase.GetCommands()
}
func (rm *RemoteManager) UpdateCommand(command d.Command) error {
return rm.remoteDatabase.UpdateCommand(command)
}
func (rm *RemoteManager) SetRemoteDatabase(remoteDatabase *d.RemoteDatabase) {
rm.remoteDatabase = remoteDatabase
}

View File

@ -25,6 +25,7 @@ func (r *RemoteApiHandler) Initialize(authenticator *Authenticator) {
commandsApi := r.router.Group("/api/commands")
commandsApi.GET("", r.handleGetCommands)
commandsApi.POST("", r.handleCreateCommands)
commandsApi.PUT("/:id", r.handleUpdateCommand)
commandsApi.DELETE("/:id", r.handleDeleteCommand)
}
@ -97,6 +98,20 @@ func (r *RemoteApiHandler) handleGetCommands(context echo.Context) error {
return context.JSON(200, commands)
}
func (r *RemoteApiHandler) handleUpdateCommand(context echo.Context) error {
command := d.Command{}
if err := context.Bind(&command); err != nil {
SendError(400, context, err.Error())
return nil
}
err := r.remoteManager.UpdateCommand(command)
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, "")
}
func (r *RemoteApiHandler) handleDeleteCommand(context echo.Context) error {
id := context.Param("id")
err := r.remoteManager.DeleteCommand(id)

View File

@ -0,0 +1,199 @@
import { createEffect, createMemo, createSignal } from "solid-js";
import ValidatedTextInput from "../components/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_EDITED_EVENT = "success";
const MIN_TITLE_LENGTH = 3;
const [command, setCommand] = createSignal(new Command());
function EditCommandModal(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() === "" || title().length >= MIN_TITLE_LENGTH
);
createEffect(() => {
setProtocol(command().getProtocol());
setCommandNumber(command().getCommandNumber());
setDevice(command().getDevice());
setCommandType(command().getCommandType());
setTitle(command().getTitle());
});
const isFormValid = createMemo(
() =>
isProtocolValid() &&
isCommandNumberValid() &&
isDeviceValid() &&
isCommandTypeValid() &&
isTitleValid()
);
async function handleEditCommand() {
try {
await RemotesService.updateCommand(
new Command({
id: command().getId(),
protocol: protocol(),
commandNumber: parseInt(commandNumber()),
device: parseInt(device()),
commandType: commandType(),
title: title(),
})
);
} catch (e) {
console.error(e);
setError(e.message);
return;
}
resetFields();
EditCommandModal.Handler.hide();
eventEmitter.dispatchEvent(COMMAND_EDITED_EVENT);
}
function resetFields() {
setProtocol("");
setCommandNumber("");
setDevice("");
setCommandType("");
setTitle("");
setError("");
}
return (
<Modal
ref={props.ref}
id="editCommandModal"
modalTitle="Edit 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="edit_command_protocol" class="col-form-label col-sm-3">
Protocol
</label>
<div class="col-sm-9">
<select
class="form-select"
onChange={(e) => setProtocol(e.target.value)}
>
{Object.values(Command.PROTOCOLS).map((protocol) => (
<option
value={protocol}
selected={protocol === command().getProtocol()}
>
{Command.getProtocolString(protocol)}
</option>
))}
</select>
</div>
</div>
<div class="mb-3 row">
<label for="edit_command_number" class="col-form-label col-sm-3">
Command Number
</label>
<ValidatedTextInput
class="col-sm-9"
id="edit_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="edit_command_device_number"
class="col-form-label col-sm-3"
>
Device Number
</label>
<ValidatedTextInput
class="col-sm-9"
id="edit_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="edit_command_protocol" class="col-form-label col-sm-3">
Command Type
</label>
<div class="col-sm-9">
<select
class="form-select"
onChange={(e) => setCommandType(e.target.value)}
>
{Object.values(Command.TYPES).map((commandType) => (
<option
value={commandType}
selected={commandType === command().getCommandType()}
>
{Command.getTypeString(commandType)}
</option>
))}
</select>
</div>
</div>
<div class="mb-3 row">
<label for="edit_command_title" class="col-form-label col-sm-3">
Title
</label>
<ValidatedTextInput
class="col-sm-9"
id="edit_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={handleEditCommand}
class="btn btn-primary"
disabled={!isFormValid()}
>
Edit
</button>
</div>
</Modal>
);
}
EditCommandModal.Handler = new ModalHandler();
EditCommandModal.onCommandEdited = (callback) =>
eventEmitter.on(COMMAND_EDITED_EVENT, callback);
EditCommandModal.setCommand = setCommand;
export default EditCommandModal;

View File

@ -11,6 +11,7 @@ import DeleteCommandModal from "./delete-command-modal.jsx";
import CreateRemoteModal from "./create-remote-modal.jsx";
import DeleteRemoteModal from "./delete-remote-modal.jsx";
import ImportCommandsModal from "./import-commands-modal.jsx";
import EditCommandModal from "./edit-command-modal.jsx";
const ModalRegistry = (function () {
const modals = [
@ -68,7 +69,12 @@ const ModalRegistry = (function () {
id: "importCommandsModal",
component: ImportCommandsModal,
ref: null,
}
},
{
id: "editCommandModal",
component: EditCommandModal,
ref: null,
},
];
function getModals(props) {

View File

@ -1,4 +1,3 @@
import Command from "../data/command";
import Serializer from "../data/serializer";
import Net from "../tools/net";
import WebRTCService from "./webrtc-service";
@ -95,6 +94,20 @@ function RemoteService() {
}
}
async function updateCommand(command) {
let commandObject = Serializer.serializeCommand(command);
let response = await Net.sendJsonRequest({
method: "PUT",
url: "/api/commands/" + command.getId(),
data: commandObject,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
}
async function deleteCommand(commandId) {
let response = await Net.sendRequest({
method: "DELETE",
@ -109,7 +122,10 @@ function RemoteService() {
function sendCommand(command) {
let commandObject = Serializer.serializeCommand(command);
WebRTCService.sendDataJson({type: MESSAGE_TYPE_COMMAND, data: commandObject});
WebRTCService.sendDataJson({
type: MESSAGE_TYPE_COMMAND,
data: commandObject,
});
}
return {
@ -120,6 +136,7 @@ function RemoteService() {
getCommands,
createCommand,
createCommands,
updateCommand,
deleteCommand,
sendCommand,
};

View File

@ -4,6 +4,7 @@ import RemotesService from "../../services/remotes-service";
import CreateCommandModal from "../../modals/create-command-modal";
import DeleteCommandModal from "../../modals/delete-command-modal";
import ImportCommandsModal from "../../modals/import-commands-modal";
import EditCommandModal from "../../modals/edit-command-modal";
function CommandsList(props) {
const [commands, { refetch: refetchCommands }] = createResource(
@ -17,7 +18,7 @@ function CommandsList(props) {
CreateCommandModal.onCommandCreated(() => {
refetchCommands();
});
ImportCommandsModal.onCommandsImported(() => {
refetchCommands();
});
@ -26,6 +27,10 @@ function CommandsList(props) {
refetchCommands();
});
EditCommandModal.onCommandEdited(() => {
refetchCommands();
});
function handleNewCommand() {
refetchCommands();
CreateCommandModal.Handler.show();
@ -35,11 +40,16 @@ function CommandsList(props) {
DeleteCommandModal.setCommand(command);
DeleteCommandModal.Handler.show();
}
function handleImportCommands() {
ImportCommandsModal.Handler.show();
}
function handleEditCommand(command) {
EditCommandModal.setCommand(command);
EditCommandModal.Handler.show();
}
return (
<>
<div class="d-flex flex-row">
@ -72,7 +82,7 @@ function CommandsList(props) {
{
id: "protocol",
name: "Protocol",
width: 10,
width: 8,
},
{
id: "type",
@ -82,7 +92,7 @@ function CommandsList(props) {
{
id: "options",
name: "",
width: 4,
width: 6,
},
]}
items={(commands() || []).map((command) => ({
@ -101,6 +111,15 @@ function CommandsList(props) {
options: {
html: (
<>
<button
class="btn btn-sm btn-outline-secondary me-2"
onClick={(event) => {
event.stopPropagation();
handleEditCommand(command);
}}
>
<i class="bi bi-pencil-fill"></i>
</button>
<button
class="btn btn-sm btn-outline-secondary me-2"
onClick={(event) => {