diff --git a/data/remote_database.go b/data/remote_database.go
index 8c38eca..cc68616 100644
--- a/data/remote_database.go
+++ b/data/remote_database.go
@@ -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)
diff --git a/management/remote_manager.go b/management/remote_manager.go
index f1cbe44..1d97581 100644
--- a/management/remote_manager.go
+++ b/management/remote_manager.go
@@ -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)
}
diff --git a/server/remote_api_handler.go b/server/remote_api_handler.go
index 1164b8c..d5b663a 100644
--- a/server/remote_api_handler.go
+++ b/server/remote_api_handler.go
@@ -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 {
diff --git a/www/src/components/list-manager.jsx b/www/src/components/list-manager.jsx
index 4549a05..a89b7cc 100644
--- a/www/src/components/list-manager.jsx
+++ b/www/src/components/list-manager.jsx
@@ -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) {
/>
- {props.items.map((item, index) => (
+ {props.items.sort(byLabel).map((item, index) => (
props.onItemSelected(index)}
selected={index === props.selectedItemIndex}
diff --git a/www/src/data/remote.js b/www/src/data/remote.js
index 8782ecc..00027b5 100644
--- a/www/src/data/remote.js
+++ b/www/src/data/remote.js
@@ -1,4 +1,4 @@
-function Remote({ id, title, commands = [] } = {}) {
+function Remote({ id = "", title = "", commands = [] } = {}) {
let _id = id;
let _title = title;
let _commands = commands;
diff --git a/www/src/modals/edit-remote-modal.jsx b/www/src/modals/edit-remote-modal.jsx
new file mode 100644
index 0000000..34a75ce
--- /dev/null
+++ b/www/src/modals/edit-remote-modal.jsx
@@ -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 (
+
+
+
+
+ {error()}
+
+
+
+
+ setTitle(e.target.value)}
+ errorText={`Title must be at least ${MIN_TITLE_LENGTH} characters long`}
+ />
+
+
+ `${command.getTitle()} (${command.getProtocol()}:${command.getCommandType()})`
+ }
+ onItemSelect={handleCommandSelect}
+ onItemDeselect={handleCommandDeselect}
+ itemsTitle="Selected Commands"
+ availableItemsTitle="Available Commands"
+ itemsEqual={(a, b) => a.getId() === b.getId()}
+ />
+
+
+
+ );
+}
+
+EditRemoteModal.Handler = modalHandler;
+EditRemoteModal.onRemoteEdited = (callback) =>
+ eventEmitter.on(REMOTE_EDITED_EVENT, callback);
+EditRemoteModal.setRemoteId = setRemoteId;
+
+export default EditRemoteModal;
diff --git a/www/src/modals/modal-registry.jsx b/www/src/modals/modal-registry.jsx
index fe9d299..94eb6f1 100644
--- a/www/src/modals/modal-registry.jsx
+++ b/www/src/modals/modal-registry.jsx
@@ -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) {
diff --git a/www/src/services/remotes-service.js b/www/src/services/remotes-service.js
index e40523d..bd1abdd 100644
--- a/www/src/services/remotes-service.js
+++ b/www/src/services/remotes-service.js
@@ -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,
diff --git a/www/src/views/remotes/remotes-list-view.jsx b/www/src/views/remotes/remotes-list-view.jsx
index 66075b7..91dd767 100644
--- a/www/src/views/remotes/remotes-list-view.jsx
+++ b/www/src/views/remotes/remotes-list-view.jsx
@@ -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: (
<>
+