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 ( + + + + + ); +} + +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) { + + + );