From db1beac03339a2c6ae28e2f4f46d9f0e81021df7 Mon Sep 17 00:00:00 2001 From: Fritz Heiden Date: Thu, 10 Apr 2025 22:22:42 +0200 Subject: [PATCH] feat: add sub menu navigation --- www/src/services/device-service.js | 18 ++ www/src/views/devices-view.jsx | 195 ------------------ www/src/views/devices/devices-list-view.jsx | 76 +++++++ www/src/views/devices/devices-view.jsx | 105 ++++++++++ .../views/devices/integration-list-view.jsx | 103 +++++++++ .../views/{ => devices}/integration-view.jsx | 35 +++- www/src/views/main-view.jsx | 19 +- www/src/views/remotes/remotes-view.jsx | 34 ++- 8 files changed, 374 insertions(+), 211 deletions(-) delete mode 100644 www/src/views/devices-view.jsx create mode 100644 www/src/views/devices/devices-list-view.jsx create mode 100644 www/src/views/devices/devices-view.jsx create mode 100644 www/src/views/devices/integration-list-view.jsx rename www/src/views/{ => devices}/integration-view.jsx (67%) diff --git a/www/src/services/device-service.js b/www/src/services/device-service.js index 04dc6db..2008519 100644 --- a/www/src/services/device-service.js +++ b/www/src/services/device-service.js @@ -79,6 +79,23 @@ function DeviceService() { return codeObject.code; } + async function getIntegration(id) { + if (!id) return null; + let response = await Net.sendRequest({ + method: "GET", + url: "/api/integrations/" + id, + }); + + if (response.status !== 200) { + let responseData = JSON.parse(response.data); + throw new Error(responseData.error); + } + + let integration = JSON.parse(response.data); + integration = Serializer.deserializeIntegration(integration); + return integration; + } + async function getIntegrations() { let response = await Net.sendRequest({ method: "GET", @@ -113,6 +130,7 @@ function DeviceService() { updateDevice, deleteDevice, getRegistrationCode, + getIntegration, getIntegrations, deleteIntegration, }; diff --git a/www/src/views/devices-view.jsx b/www/src/views/devices-view.jsx deleted file mode 100644 index 2764548..0000000 --- a/www/src/views/devices-view.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import { - createResource, - createSignal, - mergeProps, - onMount, - Show, -} from "solid-js"; - -import List from "../components/list"; -import CreateDeviceModal from "../modals/create-device-modal"; -import DeviceService from "../services/device-service"; -import ShowRegistrationCodeModal from "../modals/show-registration-code-modal"; -import DeleteIntegrationModal from "../modals/delete-integration-modal"; - -function DevicesView(props) { - props = mergeProps({ onIntegrationClicked: () => {} }, props); - const DEVICES_LIST_TYPE = "devices"; - const INTEGRATION_LIST_TYPE = "integrations"; - - const [listType, setListType] = createSignal(INTEGRATION_LIST_TYPE); - const [devices, setDevices] = createSignal([]); - const [integrations, { refetch: refetchIntegrations }] = createResource( - DeviceService.getIntegrations - ); - - CreateDeviceModal.onDeviceCreated(() => { - handleRefreshDevices(); - }); - - onMount(() => { - handleRefreshDevices(); - DeleteIntegrationModal.onIntegrationDeleted(() => { - refetchIntegrations(); - }); - }); - - function handleNewDevice() { - CreateDeviceModal.Handler.show(); - } - - async function handleRefreshDevices() { - let devices = await DeviceService.getDevices(); - setDevices(devices); - } - - function handleRegisterIntegration() { - ShowRegistrationCodeModal.Handler.show(); - } - - function handleDeleteIntegration(integration) { - DeleteIntegrationModal.setIntegration(integration); - DeleteIntegrationModal.Handler.show(); - } - - function handleIntegrationItemClicked(item) { - props.onIntegrationClicked(item.integration); - } - - 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", - }, - ]} - > - - - ({ - id: { - html: {integration.getId()}, - }, - name: { - text: integration.getName(), - }, - options: { - html: ( - <> - - - ), - }, - integration: integration, - }))} - class={"flex-fill"} - columns={[ - { - id: "id", - name: "id", - width: 6, - }, - { - id: "name", - name: "Name", - }, - { - id: "options", - name: "", - width: 4, - }, - ]} - > - -
- ); -} - -export default DevicesView; diff --git a/www/src/views/devices/devices-list-view.jsx b/www/src/views/devices/devices-list-view.jsx new file mode 100644 index 0000000..04bbbdb --- /dev/null +++ b/www/src/views/devices/devices-list-view.jsx @@ -0,0 +1,76 @@ +import { createSignal } from "solid-js"; +import List from "../../components/list"; +import CreateDeviceModal from "../../modals/create-device-modal"; +import DeviceService from "../../services/device-service"; + +function DevicesListView(props) { + const [devices, setDevices] = createSignal([]); + + handleRefreshDevices(); + + CreateDeviceModal.onDeviceCreated(() => { + handleRefreshDevices(); + }); + + function handleNewDevice() { + CreateDeviceModal.Handler.show(); + } + + async function handleRefreshDevices() { + let devices = await DeviceService.getDevices(); + setDevices(devices); + } + + return ( +
+
+ {props.navigation} +
+ +
+
+ {}} + 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 DevicesListView; diff --git a/www/src/views/devices/devices-view.jsx b/www/src/views/devices/devices-view.jsx new file mode 100644 index 0000000..888f86f --- /dev/null +++ b/www/src/views/devices/devices-view.jsx @@ -0,0 +1,105 @@ +import { + createEffect, + createSignal, + Match, + mergeProps, + onCleanup, + Switch, +} from "solid-js"; + +import DevicesListView from "./devices-list-view"; +import IntegrationListView from "./integration-list-view"; +import UrlUtils from "../../tools/url-utils"; +import IntegrationView from "./integration-view"; + +function DevicesView(props) { + props = mergeProps({ onIntegrationClicked: () => {} }, props); + const DEVICES_LIST_VIEW = "devices"; + const INTEGRATION_LIST_VIEW = "integrations"; + const INTEGRATION_VIEW = "integration"; + const VIEWS = [DEVICES_LIST_VIEW, INTEGRATION_LIST_VIEW, INTEGRATION_VIEW]; + + const [currentView, setCurrentView] = createSignal(INTEGRATION_LIST_VIEW); + + createEffect(() => { + let url = UrlUtils.getUrl(); + url = UrlUtils.addQueryParameter(url, "tab", currentView()); + UrlUtils.setUrl(url); + }); + + onCleanup(() => { + window.removeEventListener("popstate", setViewFromUrl); + }); + + setViewFromUrl(); + window.addEventListener("popstate", setViewFromUrl); + + function setViewFromUrl() { + let view = UrlUtils.getQueryParameter("tab"); + if (!view) return; + if (!VIEWS.includes(view)) return; + setCurrentView(view); + } + + function handleIntegrationClicked(integration) { + IntegrationView.setIntegration(integration); + setCurrentView(INTEGRATION_VIEW); + } + + function Navigation() { + return ( +
+ + +
+ ); + } + + return ( +
+ + + } + onIntegrationClicked={handleIntegrationClicked} + /> + + + } /> + + + + + +
+ ); +} + +export default DevicesView; diff --git a/www/src/views/devices/integration-list-view.jsx b/www/src/views/devices/integration-list-view.jsx new file mode 100644 index 0000000..46bbfa9 --- /dev/null +++ b/www/src/views/devices/integration-list-view.jsx @@ -0,0 +1,103 @@ +import { createResource, mergeProps } from "solid-js"; +import List from "../../components/list"; +import ShowRegistrationCodeModal from "../../modals/show-registration-code-modal"; +import DeleteIntegrationModal from "../../modals/delete-integration-modal"; +import DeviceService from "../../services/device-service"; + +function IntegrationListView(props) { + props = mergeProps( + { + onIntegrationClicked: () => {}, + }, + props + ); + const [integrations, { refetch: refetchIntegrations }] = createResource( + DeviceService.getIntegrations, + { initialValue: [] } + ); + + function handleRegisterIntegration() { + ShowRegistrationCodeModal.Handler.show(); + } + + DeleteIntegrationModal.onIntegrationDeleted(() => { + refetchIntegrations(); + }); + + function handleDeleteIntegration(integration) { + DeleteIntegrationModal.setIntegration(integration); + DeleteIntegrationModal.Handler.show(); + } + + function handleIntegrationItemClicked(item) { + props.onIntegrationClicked(item.integration); + } + + return ( +
+
+ {props.navigation} +
+ +
+
+ ({ + id: { + html: {integration.getId()}, + }, + name: { + text: integration.getName(), + }, + options: { + html: ( + <> + + + ), + }, + integration: integration, + }))} + class={"flex-fill"} + columns={[ + { + id: "id", + name: "id", + width: 6, + }, + { + id: "name", + name: "Name", + }, + { + id: "options", + name: "", + width: 4, + }, + ]} + > +
+ ); +} + +export default IntegrationListView; diff --git a/www/src/views/integration-view.jsx b/www/src/views/devices/integration-view.jsx similarity index 67% rename from www/src/views/integration-view.jsx rename to www/src/views/devices/integration-view.jsx index 178d774..8aafcad 100644 --- a/www/src/views/integration-view.jsx +++ b/www/src/views/devices/integration-view.jsx @@ -1,8 +1,10 @@ -import { createMemo, createSignal } from "solid-js"; -import Integration from "../data/integration"; -import WebRTCService from "../services/webrtc-service"; +import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import WebRTCService from "../../services/webrtc-service"; +import Integration from "../../data/integration"; +import DeviceService from "../../services/device-service"; +import UrlUtils from "../../tools/url-utils"; -const [integration, setIntegration] = createSignal(null); +const [integration, setIntegration] = createSignal(new Integration()); function IntegrationView(props) { const title = createMemo(() => @@ -22,6 +24,26 @@ function IntegrationView(props) { WebRTCService.onStateChanged(handleConnectionStateChanged); let videoElement = null; + createEffect(() => { + let url = UrlUtils.getUrl(); + url = UrlUtils.addQueryParameter(url, "id", integration()?.getId()); + UrlUtils.setUrl(url); + }); + + onCleanup(() => { + window.removeEventListener("popstate", setIntegrationFromUrl); + }); + + setIntegrationFromUrl(); + window.addEventListener("popstate", setIntegrationFromUrl); + + async function setIntegrationFromUrl() { + let integrationId = UrlUtils.getQueryParameter("id"); + if (!integrationId) return; + let integration = await DeviceService.getIntegration(integrationId) + setIntegration(integration); + } + function handleConnectWebRTC() { let integrationId = integration().getId(); WebRTCService.setVideoElement(videoElement); @@ -43,7 +65,10 @@ function IntegrationView(props) { props.class } > - Integration + + + Integration +

{title}

diff --git a/www/src/views/main-view.jsx b/www/src/views/main-view.jsx index 2eee839..3712d1e 100644 --- a/www/src/views/main-view.jsx +++ b/www/src/views/main-view.jsx @@ -11,7 +11,7 @@ import { import ModalRegistry from "../modals/modal-registry.jsx"; import UserService from "../services/user-service.js"; import UrlUtils from "../tools/url-utils.js"; -import DevicesView from "./devices-view.jsx"; +import DevicesView from "./devices/devices-view.jsx"; import SettingsView from "./settings-view.jsx"; import { @@ -21,12 +21,12 @@ import { REMOTES_VIEW, SETTINGS_VIEW, } from "../data/constants.js"; -import IntegrationView from "./integration-view.jsx"; +import IntegrationView from "./devices/integration-view.jsx"; import RemotesView from "./remotes/remotes-view.jsx"; let [activeView, setActiveView] = createSignal(DEVICES_VIEW); -const MainView = function (props) { +function MainView(props) { props = mergeProps({ onLogout: () => {} }, props); const [userInfo] = createResource(() => UserService.getUserInfo()); @@ -55,7 +55,6 @@ const MainView = function (props) { function setViewFromUrl() { let view = UrlUtils.getQueryParameter("view"); - console.log(view) if (view) { setActiveView(view); } @@ -100,7 +99,8 @@ const MainView = function (props) { if (activeView() === view) return; setActiveView(view); let url = UrlUtils.getUrl(); - url = UrlUtils.addQueryParameter(url, "view", view); + url = UrlUtils.addQueryParameter(url, "view", activeView()); + url = UrlUtils.removeQueryParameter(url, "tab"); UrlUtils.setUrl(url); } @@ -114,7 +114,7 @@ const MainView = function (props) {
- +
); } @@ -211,7 +211,10 @@ const MainView = function (props) { - + @@ -225,7 +228,7 @@ const MainView = function (props) { } return render(); -}; +} MainView.setActiveView = setActiveView; diff --git a/www/src/views/remotes/remotes-view.jsx b/www/src/views/remotes/remotes-view.jsx index 8af6fe5..7f91620 100644 --- a/www/src/views/remotes/remotes-view.jsx +++ b/www/src/views/remotes/remotes-view.jsx @@ -1,10 +1,13 @@ import { + createEffect, createSignal, Match, mergeProps, - Switch + onCleanup, + Switch, } from "solid-js"; +import UrlUtils from "../../tools/url-utils"; import CommandsList from "./commands-list-view"; import RemotesList from "./remotes-list-view"; @@ -12,16 +15,39 @@ function RemotesView(props) { props = mergeProps({ onIntegrationClicked: () => {} }, props); const REMOTES_LIST_VIEW = "remotes_list"; const COMMANDS_LIST_VIEW = "commands_list"; + const VIEWS = [REMOTES_LIST_VIEW, COMMANDS_LIST_VIEW]; const [currentView, setCurrentView] = createSignal(REMOTES_LIST_VIEW); + createEffect(() => { + let url = UrlUtils.getUrl(); + url = UrlUtils.addQueryParameter(url, "tab", currentView()); + UrlUtils.setUrl(url); + }); + + onCleanup(() => { + window.removeEventListener("popstate", setViewFromUrl); + }); + + setViewFromUrl(); + window.addEventListener("popstate", setViewFromUrl); + + function setViewFromUrl() { + let view = UrlUtils.getQueryParameter("tab"); + if (!view) return; + if (!VIEWS.includes(view)) return; + setCurrentView(view); + } + function Navigation(props) { return (