diff --git a/data/device_database.go b/data/device_database.go
index 51469b5..3c802a7 100644
--- a/data/device_database.go
+++ b/data/device_database.go
@@ -24,7 +24,7 @@ func (db *DeviceDatabase) Initialize() error {
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS devices (
id TEXT PRIMARY KEY,
- device_name TEXT UNIQUE,
+ device_name TEXT,
description TEXT
)`)
if error != nil {
diff --git a/www/src/data/playback-device.js b/www/src/data/playback-device.js
new file mode 100644
index 0000000..f4a9e79
--- /dev/null
+++ b/www/src/data/playback-device.js
@@ -0,0 +1,40 @@
+function PlaybackDevice({id, name, description}) {
+ let _id = id;
+ let _name = name;
+ let _description = description;
+
+ function getId() {
+ return _id;
+ }
+
+ function getName() {
+ return _name;
+ }
+
+ function getDescription() {
+ return _description;
+ }
+
+ function setId(id) {
+ _id = id;
+ }
+
+ function setName(name) {
+ _name = name;
+ }
+
+ function setDescription(description) {
+ _description = description;
+ }
+
+ return {
+ getId,
+ getName,
+ getDescription,
+ setId,
+ setName,
+ setDescription
+ };
+}
+
+export default PlaybackDevice;
\ No newline at end of file
diff --git a/www/src/data/serializer.js b/www/src/data/serializer.js
index aeecedf..b33e776 100644
--- a/www/src/data/serializer.js
+++ b/www/src/data/serializer.js
@@ -1,3 +1,4 @@
+import PlaybackDevice from "./playback-device.js";
import User from "./user.js";
const Serializer = (function () {
@@ -10,9 +11,19 @@ const Serializer = (function () {
return objects.map((object) => deserializeUser(object));
}
+ function deserializeDevice(object) {
+ return new PlaybackDevice(object);
+ }
+
+ function deserializeDevices(objects) {
+ return objects.map((object) => deserializeDevice(object));
+ }
+
return {
deserializeUser,
deserializeUsers,
+ deserializeDevice,
+ deserializeDevices,
};
})();
diff --git a/www/src/modals/create-device-modal.jsx b/www/src/modals/create-device-modal.jsx
new file mode 100644
index 0000000..29d404a
--- /dev/null
+++ b/www/src/modals/create-device-modal.jsx
@@ -0,0 +1,108 @@
+import { createEffect, createSignal } from "solid-js";
+import Modal from "./modal.jsx";
+import EventEmitter from "../tools/event-emitter.js";
+import ValidatedTextInput from "../modules/validated-text-input.jsx";
+import ModalHandler from "./modal-handler.js";
+import DeviceService from "../services/device-service.js";
+
+const eventEmitter = new EventEmitter();
+const DEVICE_CREATED_EVENT = "success";
+const MIN_NAME_LENGTH = 3;
+
+function CreateDeviceModal(props) {
+ const [name, setName] = createSignal("");
+ const [isNameValid, setNameValid] = createSignal(true);
+ const [description, setDescription] = createSignal("");
+ const [isDescriptionValid, setDescriptionValid] = createSignal(true);
+ const [error, setError] = createSignal("");
+
+ createEffect(() => setNameValid(checkIfNameValid(name())));
+ createEffect(() => setDescriptionValid(checkIfDescriptionValid(description())));
+
+ function checkIfNameValid(name) {
+ return name.length >= MIN_NAME_LENGTH;
+ }
+
+ function checkIfDescriptionValid(description) {
+ return true;
+ }
+
+ async function handleCreateDevice() {
+ let device;
+ try {
+ device = await DeviceService.createDevice({
+ name: name(),
+ description: description(),
+ });
+ } catch (e) {
+ setError(e.message);
+ return;
+ }
+ setName("");
+ setDescription("");
+ CreateDeviceModal.Handler.hide();
+ eventEmitter.dispatchEvent(DEVICE_CREATED_EVENT, device);
+ }
+
+ return (
+
+
+
+
+ {error()}
+
+
+
+
+ setName(e.target.value)}
+ errorText={"Device name too short"}
+ />
+
+
+
+ setDescription(e.target.value)}
+ errorText={"Invalid description"}
+ />
+
+
+
+
+ );
+}
+
+CreateDeviceModal.Handler = new ModalHandler();
+CreateDeviceModal.onDeviceCreated = (callback) =>
+ eventEmitter.on(DEVICE_CREATED_EVENT, callback);
+
+export default CreateDeviceModal;
diff --git a/www/src/modals/modal-registry.jsx b/www/src/modals/modal-registry.jsx
index d9a6850..eb531c1 100644
--- a/www/src/modals/modal-registry.jsx
+++ b/www/src/modals/modal-registry.jsx
@@ -3,6 +3,7 @@ import { onMount } from "solid-js";
import CreateUserModal from "./create-user-modal.jsx";
import DeleteUserModal from "./delete-user-modal.jsx";
import UserSettingsModal from "./user-settings-modal.jsx";
+import CreateDeviceModal from "./create-device-modal.jsx";
const ModalRegistry = (function () {
const modals = [
@@ -21,6 +22,11 @@ const ModalRegistry = (function () {
component: UserSettingsModal,
ref: null,
},
+ {
+ id: "createDeviceModal",
+ component: CreateDeviceModal,
+ ref: null,
+ },
];
function getModals(props) {
diff --git a/www/src/services/device-service.js b/www/src/services/device-service.js
new file mode 100644
index 0000000..415f299
--- /dev/null
+++ b/www/src/services/device-service.js
@@ -0,0 +1,76 @@
+import Serializer from "../data/serializer";
+import Net from "../tools/net";
+
+function DeviceService() {
+ async function getDevices() {
+ let response = await Net.sendRequest({
+ method: "GET",
+ url: "/api/devices",
+ });
+
+ if (response.status !== 200) {
+ let responseData = JSON.parse(response.data);
+ throw new Error(responseData.error);
+ }
+
+ let deviceObjects = JSON.parse(response.data);
+ return Serializer.deserializeDevices(deviceObjects);
+ }
+
+ async function createDevice({ name, description }) {
+ let response = await Net.sendJsonRequest({
+ method: "POST",
+ url: "/api/devices",
+ data: { name, description },
+ });
+
+ if (response.status !== 200) {
+ let responseData = JSON.parse(response.data);
+ throw new Error(responseData.error);
+ }
+
+ let deviceObject = JSON.parse(response.data);
+ let device = Serializer.deserializeDevice(deviceObject);
+ return device;
+ }
+
+ async function updateDevice(deviceId, { name, description }) {
+ let response = await Net.sendJsonRequest({
+ method: "PUT",
+ url: "/api/devices/" + deviceId,
+ data: { name, description },
+ });
+
+ if (response.status !== 200) {
+ let responseData = JSON.parse(response.data);
+ throw new Error(responseData.error);
+ }
+
+ let deviceObject = JSON.parse(response.data);
+ let device = Serializer.deserializeDevice(deviceObject);
+ return device;
+ }
+
+ async function deleteDevice(deviceId) {
+ let response = await Net.sendJsonRequest({
+ method: "DELETE",
+ url: "/api/devices/" + deviceId,
+ });
+
+ if (response.status !== 200) {
+ let responseData = JSON.parse(response.data);
+ throw new Error(responseData.error);
+ }
+ }
+
+ return {
+ getDevices,
+ createDevice,
+ updateDevice,
+ deleteDevice,
+ };
+}
+
+DeviceService = new DeviceService();
+
+export default DeviceService;
diff --git a/www/src/views/devices-view.jsx b/www/src/views/devices-view.jsx
new file mode 100644
index 0000000..c095137
--- /dev/null
+++ b/www/src/views/devices-view.jsx
@@ -0,0 +1,108 @@
+import { createSignal, onMount } from "solid-js";
+
+import List from "../modules/list";
+import CreateDeviceModal from "../modals/create-device-modal";
+import DeviceService from "../services/device-service";
+
+function DevicesView(props) {
+ const DEVICES_LIST_TYPE = "devices";
+ const INTEGRATION_LIST_TYPE = "integrations";
+
+ const [listType, setListType] = createSignal(DEVICES_LIST_TYPE);
+ const [devices, setDevices] = createSignal([]);
+
+ CreateDeviceModal.onDeviceCreated(() => {
+ handleRefreshDevices();
+ });
+
+ onMount(() => {
+ handleRefreshDevices();
+ });
+
+ function handleNewDevice() {
+ CreateDeviceModal.Handler.show();
+ }
+
+ async function handleRefreshDevices() {
+ let devices = await DeviceService.getDevices();
+ setDevices(devices);
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
{}}
+ items={devices().map((device) => ({
+ id: {
+ html: {device.getId()},
+ },
+ name: {
+ text: device.getName(),
+ },
+ description: {
+ text: device.getDescription(),
+ },
+ device,
+ }))}
+ class={"flex-fill"}
+ columns={[
+ {
+ id: "id",
+ name: "id",
+ width: 6,
+ },
+ {
+ id: "name",
+ name: "Name",
+ width: 10,
+ },
+ {
+ id: "description",
+ name: "Description",
+ },
+ ]}
+ >
+
+ );
+}
+
+export default DevicesView;
diff --git a/www/src/views/main-view.jsx b/www/src/views/main-view.jsx
index 0e3a46e..4e77225 100644
--- a/www/src/views/main-view.jsx
+++ b/www/src/views/main-view.jsx
@@ -12,6 +12,7 @@ import UserService from "../services/user-service.js";
import ModalRegistry from "../modals/modal-registry.jsx";
import UrlUtils from "../tools/url-utils.js";
import SettingsView from "./settings-view.jsx";
+import DevicesView from "./devices-view.jsx";
import {
DEVICES_VIEW,
@@ -121,7 +122,7 @@ const MainView = function (props) {
}
onClick={() => handleViewChange(DEVICES_VIEW)}
>
-
+
Devices
@@ -200,6 +201,9 @@ const MainView = function (props) {
+
+
+
);