feat: add editing of remotes
This commit is contained in:
parent
6a9c69a535
commit
d7b8ad9976
@ -97,6 +97,19 @@ func (db *RemoteDatabase) CreateRemoteCommands(remoteId string, commandIds []str
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *RemoteDatabase) UpdateRemoteCommands(remoteId string, commandIds []string) error {
|
||||
queryString := "DELETE FROM RemoteCommands WHERE remote_id = ?"
|
||||
_, err := db.Connection.Exec(queryString, remoteId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.CreateRemoteCommands(remoteId, commandIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *RemoteDatabase) GetRemote(remoteId string) (Remote, error) {
|
||||
var remote Remote
|
||||
queryString := "SELECT id, title FROM Remotes WHERE id = ?"
|
||||
@ -132,6 +145,23 @@ func (db *RemoteDatabase) GetRemotes() ([]Remote, error) {
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
func (db *RemoteDatabase) UpdateRemote(remote Remote) error {
|
||||
queryString := "UPDATE Remotes SET title = ? WHERE id = ?"
|
||||
_, error := db.Connection.Exec(queryString, remote.Title, remote.Id)
|
||||
if error != nil {
|
||||
return fmt.Errorf("error updating remote %s: %s", remote.Id, error)
|
||||
}
|
||||
commandIds := []string{}
|
||||
for _, command := range remote.Commands {
|
||||
commandIds = append(commandIds, command.Id)
|
||||
}
|
||||
err := db.UpdateRemoteCommands(remote.Id, commandIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *RemoteDatabase) DeleteRemote(remoteId string) error {
|
||||
queryString := "DELETE FROM Remotes WHERE id = ?"
|
||||
_, error := db.Connection.Exec(queryString, remoteId)
|
||||
|
||||
@ -33,6 +33,10 @@ func (rm *RemoteManager) GetRemotes() ([]d.Remote, error) {
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
func (rm *RemoteManager) UpdateRemote(remote d.Remote) error {
|
||||
return rm.remoteDatabase.UpdateRemote(remote)
|
||||
}
|
||||
|
||||
func (rm *RemoteManager) DeleteRemote(remoteID string) error {
|
||||
return rm.remoteDatabase.DeleteRemote(remoteID)
|
||||
}
|
||||
|
||||
@ -19,7 +19,8 @@ func (r *RemoteApiHandler) Initialize(authenticator *Authenticator) {
|
||||
remotesApi.GET("", r.handleGetRemotes)
|
||||
remotesApi.GET("/:id", r.handleGetRemote)
|
||||
remotesApi.POST("", r.handleCreateRemote)
|
||||
remotesApi.DELETE("/:id", r.handleDelteRemote)
|
||||
remotesApi.PUT("/:id", r.handleUpdateRemote)
|
||||
remotesApi.DELETE("/:id", r.handleDeleteRemote)
|
||||
|
||||
r.router.Use(authenticator.Authenticate("/api/commands", []string{}))
|
||||
commandsApi := r.router.Group("/api/commands")
|
||||
@ -65,7 +66,21 @@ func (r *RemoteApiHandler) handleGetRemotes(context echo.Context) error {
|
||||
return context.JSON(200, remotes)
|
||||
}
|
||||
|
||||
func (r *RemoteApiHandler) handleDelteRemote(context echo.Context) error {
|
||||
func (r *RemoteApiHandler) handleUpdateRemote(context echo.Context) error {
|
||||
remote := d.Remote{}
|
||||
if err := context.Bind(&remote); err != nil {
|
||||
SendError(400, context, err.Error())
|
||||
return nil
|
||||
}
|
||||
err := r.remoteManager.UpdateRemote(remote)
|
||||
if err != nil {
|
||||
SendError(500, context, err.Error())
|
||||
return err
|
||||
}
|
||||
return context.JSON(200, "")
|
||||
}
|
||||
|
||||
func (r *RemoteApiHandler) handleDeleteRemote(context echo.Context) error {
|
||||
id := context.Param("id")
|
||||
err := r.remoteManager.DeleteRemote(id)
|
||||
if err != nil {
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
import { createEffect, createMemo, createSignal, mergeProps } from "solid-js";
|
||||
import { createMemo, createSignal, mergeProps } from "solid-js";
|
||||
|
||||
function ListManager(props) {
|
||||
props = mergeProps(
|
||||
{
|
||||
items: [],
|
||||
availableItems: [],
|
||||
itemToString: () => "",
|
||||
style: "",
|
||||
onItemSelect: () => {},
|
||||
onItemDeselect: () => {},
|
||||
itemsTitle: "Selected items",
|
||||
availableItemsTitle: "Available items",
|
||||
itemToString: () => "",
|
||||
onItemSelect: () => {},
|
||||
onItemDeselect: () => {},
|
||||
itemsEqual: (a, b) => a === b,
|
||||
},
|
||||
props
|
||||
);
|
||||
|
||||
const itemToString = (item) => props.itemToString(item);
|
||||
const byLabel = (a, b) => itemToString(a).localeCompare(itemToString(b));
|
||||
const [selectedAvailableItemIndex, setSelectedAvailableItemIndex] =
|
||||
createSignal(-1);
|
||||
const [selectedItemIndex, setSelectedItemIndex] = createSignal(-1);
|
||||
@ -42,11 +44,16 @@ function ListManager(props) {
|
||||
.search(availableItemsSearchString())
|
||||
.map((item) => item.item)
|
||||
: props.availableItems
|
||||
).filter((item) => !props.items.includes(item))
|
||||
).filter(
|
||||
(availableItem) =>
|
||||
!props.items.find((item) => props.itemsEqual(item, availableItem))
|
||||
)
|
||||
);
|
||||
const selectableItems = createMemo(() =>
|
||||
itemsSearchString()
|
||||
? itemsFuse().search(itemsSearchString()).map((item) => item.item)
|
||||
? itemsFuse()
|
||||
.search(itemsSearchString())
|
||||
.map((item) => item.item)
|
||||
: props.items
|
||||
);
|
||||
const canSelect = createMemo(
|
||||
@ -120,7 +127,7 @@ function ListManager(props) {
|
||||
/>
|
||||
</div>
|
||||
<div class="rounded rounded-top-0 border bg-body flex-fill overflow-y-scroll">
|
||||
{props.items.map((item, index) => (
|
||||
{props.items.sort(byLabel).map((item, index) => (
|
||||
<ListItem
|
||||
onClick={() => props.onItemSelected(index)}
|
||||
selected={index === props.selectedItemIndex}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
function Remote({ id, title, commands = [] } = {}) {
|
||||
function Remote({ id = "", title = "", commands = [] } = {}) {
|
||||
let _id = id;
|
||||
let _title = title;
|
||||
let _commands = commands;
|
||||
|
||||
145
www/src/modals/edit-remote-modal.jsx
Normal file
145
www/src/modals/edit-remote-modal.jsx
Normal file
@ -0,0 +1,145 @@
|
||||
import {
|
||||
createEffect,
|
||||
createMemo,
|
||||
createResource,
|
||||
createSignal,
|
||||
} from "solid-js";
|
||||
import ListManager from "../components/list-manager.jsx";
|
||||
import ValidatedTextInput from "../components/validated-text-input.jsx";
|
||||
import Remote from "../data/remote.js";
|
||||
import RemoteService from "../services/remotes-service.js";
|
||||
import EventEmitter from "../tools/event-emitter.js";
|
||||
import ModalHandler from "./modal-handler.js";
|
||||
import Modal from "./modal.jsx";
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
const REMOTE_EDITED_EVENT = "success";
|
||||
const MIN_TITLE_LENGTH = 3;
|
||||
const modalHandler = new ModalHandler();
|
||||
|
||||
const [remoteId, setRemoteId] = createSignal(null);
|
||||
|
||||
function EditRemoteModal(props) {
|
||||
const [title, setTitle] = createSignal("");
|
||||
const [commands, setCommands] = createSignal([]);
|
||||
const [availableCommands, { refetch: refetchAvailableCommands }] =
|
||||
createResource(RemoteService.getCommands);
|
||||
const [remote, {}] = createResource(
|
||||
remoteId,
|
||||
() => RemoteService.getRemote(remoteId()),
|
||||
{ initialValue: new Remote() }
|
||||
);
|
||||
const [error, setError] = createSignal("");
|
||||
|
||||
const isTitleValid = createMemo(() => title().length >= MIN_TITLE_LENGTH);
|
||||
const isFormValid = createMemo(() => isTitleValid());
|
||||
|
||||
createEffect(() => {
|
||||
if (!remote()) return;
|
||||
setTitle(remote().getTitle());
|
||||
setCommands(remote().getCommands());
|
||||
});
|
||||
|
||||
modalHandler.onShow(() => {
|
||||
refetchAvailableCommands();
|
||||
});
|
||||
|
||||
async function handleEditRemote() {
|
||||
try {
|
||||
await RemoteService.updateRemote(
|
||||
new Remote({
|
||||
id: remote().getId(),
|
||||
title: title(),
|
||||
commands: commands(),
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
resetFields();
|
||||
EditRemoteModal.Handler.hide();
|
||||
eventEmitter.dispatchEvent(REMOTE_EDITED_EVENT);
|
||||
}
|
||||
|
||||
function resetFields() {
|
||||
setTitle("");
|
||||
setCommands([]);
|
||||
setRemoteId("");
|
||||
setError("");
|
||||
}
|
||||
|
||||
function handleCommandSelect(item) {
|
||||
setCommands([...commands(), item]);
|
||||
}
|
||||
|
||||
function handleCommandDeselect(item) {
|
||||
setCommands(
|
||||
commands().filter((command) => command.getId() !== item.getId())
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
ref={props.ref}
|
||||
id="editRemoteModal"
|
||||
modalTitle="New Remote"
|
||||
centered={true}
|
||||
>
|
||||
<div class="modal-body bg-body-tertiary">
|
||||
<Show when={error() !== ""}>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{error()}
|
||||
</div>
|
||||
</Show>
|
||||
<div class="mb-3 row">
|
||||
<label for="edit_remote_title" class="col-form-label col-sm-1">
|
||||
Title
|
||||
</label>
|
||||
<ValidatedTextInput
|
||||
class="col-sm-11"
|
||||
id="edit_remote_title"
|
||||
valid={isTitleValid()}
|
||||
value={title()}
|
||||
onInput={(e) => setTitle(e.target.value)}
|
||||
errorText={`Title must be at least ${MIN_TITLE_LENGTH} characters long`}
|
||||
/>
|
||||
</div>
|
||||
<ListManager
|
||||
style="height: 20em;"
|
||||
items={commands()}
|
||||
availableItems={availableCommands()}
|
||||
itemToString={(command) =>
|
||||
`${command.getTitle()} (${command.getProtocol()}:${command.getCommandType()})`
|
||||
}
|
||||
onItemSelect={handleCommandSelect}
|
||||
onItemDeselect={handleCommandDeselect}
|
||||
itemsTitle="Selected Commands"
|
||||
availableItemsTitle="Available Commands"
|
||||
itemsEqual={(a, b) => a.getId() === b.getId()}
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEditRemote}
|
||||
class="btn btn-primary"
|
||||
disabled={!isFormValid()}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
EditRemoteModal.Handler = modalHandler;
|
||||
EditRemoteModal.onRemoteEdited = (callback) =>
|
||||
eventEmitter.on(REMOTE_EDITED_EVENT, callback);
|
||||
EditRemoteModal.setRemoteId = setRemoteId;
|
||||
|
||||
export default EditRemoteModal;
|
||||
@ -12,6 +12,7 @@ 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";
|
||||
import EditRemoteModal from "./edit-remote-modal.jsx";
|
||||
|
||||
const ModalRegistry = (function () {
|
||||
const modals = [
|
||||
@ -75,6 +76,11 @@ const ModalRegistry = (function () {
|
||||
component: EditCommandModal,
|
||||
ref: null,
|
||||
},
|
||||
{
|
||||
id: "editRemoteModal",
|
||||
component: EditRemoteModal,
|
||||
ref: null,
|
||||
}
|
||||
];
|
||||
|
||||
function getModals(props) {
|
||||
|
||||
@ -6,6 +6,7 @@ const MESSAGE_TYPE_COMMAND = "command";
|
||||
|
||||
function RemoteService() {
|
||||
async function getRemote(remoteId) {
|
||||
if (!remoteId) return null;
|
||||
let response = await Net.sendRequest({
|
||||
method: "GET",
|
||||
url: "/api/remotes/" + remoteId,
|
||||
@ -49,6 +50,20 @@ function RemoteService() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRemote(remote) {
|
||||
let remoteObject = Serializer.serializeRemote(remote);
|
||||
let response = await Net.sendJsonRequest({
|
||||
method: "PUT",
|
||||
url: "/api/remotes/" + remote.getId(),
|
||||
data: remoteObject,
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
let responseData = JSON.parse(response.data);
|
||||
throw new Error(responseData.error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRemote(remoteId) {
|
||||
let response = await Net.sendRequest({
|
||||
method: "DELETE",
|
||||
@ -132,6 +147,7 @@ function RemoteService() {
|
||||
getRemote,
|
||||
getRemotes,
|
||||
createRemote,
|
||||
updateRemote,
|
||||
deleteRemote,
|
||||
getCommands,
|
||||
createCommand,
|
||||
|
||||
@ -6,6 +6,7 @@ import DeleteRemoteModal from "../../modals/delete-remote-modal";
|
||||
import FileUtils from "../../tools/file-utils";
|
||||
import Serializer from "../../data/serializer";
|
||||
import RemoteService from "../../services/remotes-service";
|
||||
import EditRemoteModal from "../../modals/edit-remote-modal";
|
||||
|
||||
function RemotesList(props) {
|
||||
const [remotes, { refetch: refetchRemotes }] = createResource(
|
||||
@ -26,10 +27,19 @@ function RemotesList(props) {
|
||||
refetchRemotes();
|
||||
});
|
||||
|
||||
EditRemoteModal.onRemoteEdited(() => {
|
||||
refetchRemotes();
|
||||
});
|
||||
|
||||
function handleNewRemote() {
|
||||
CreateRemoteModal.Handler.show();
|
||||
}
|
||||
|
||||
function handleEditRemote(remote) {
|
||||
EditRemoteModal.setRemoteId(remote.getId());
|
||||
EditRemoteModal.Handler.show();
|
||||
}
|
||||
|
||||
function handleDeleteRemote(remote) {
|
||||
DeleteRemoteModal.setRemote(remote);
|
||||
DeleteRemoteModal.Handler.show();
|
||||
@ -90,7 +100,7 @@ function RemotesList(props) {
|
||||
{
|
||||
id: "options",
|
||||
name: "",
|
||||
width: 4,
|
||||
width: 6,
|
||||
},
|
||||
]}
|
||||
onListItemClick={() => {}}
|
||||
@ -105,6 +115,15 @@ function RemotesList(props) {
|
||||
options: {
|
||||
html: (
|
||||
<>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary me-2"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleEditRemote(remote);
|
||||
}}
|
||||
>
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary me-2"
|
||||
onClick={(event) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user