From 076b460e219d69989f1c4f6d714710ae4c9b1498 Mon Sep 17 00:00:00 2001 From: bcd00 Date: Thu, 11 Jan 2024 21:10:49 +0000 Subject: [PATCH] Fixing linting issues --- .eslintrc.json | 17 + apps/ove-bridge-ui/.eslintrc.json | 43 +- apps/ove-bridge-ui/src/app/app.spec.tsx | 5 +- apps/ove-bridge-ui/src/app/app.tsx | 2 + apps/ove-bridge-ui/src/app/nav.tsx | 3 + apps/ove-bridge-ui/src/app/router.tsx | 1 + apps/ove-bridge-ui/src/env.ts | 2 +- apps/ove-bridge-ui/src/main.tsx | 2 +- .../pages/hardware/components/auth/auth.tsx | 13 +- .../hardware/components/devices/devices.tsx | 36 +- .../components/edit-device/edit-device.tsx | 37 +- .../src/pages/hardware/hardware.tsx | 1 + .../auto-mode-configuration.tsx | 4 +- .../configuration/configuration.tsx | 2 +- .../home/components/configuration/hooks.ts | 21 +- .../home/components/key-pass/key-pass.tsx | 7 +- apps/ove-bridge-ui/src/pages/home/home.tsx | 22 +- .../src/pages/live-view/hooks.ts | 4 +- .../src/pages/live-view/live-view.tsx | 7 +- apps/ove-bridge-ui/src/svg.d.ts | 2 +- apps/ove-bridge/.eslintrc.json | 36 +- .../src/app/api/features/bridge/controller.ts | 24 +- .../api/features/bridge/power-scheduler.ts | 28 +- .../src/app/api/features/bridge/routes.ts | 5 +- .../src/app/api/features/bridge/service.ts | 28 +- .../src/app/api/features/bridge/sockets.ts | 12 +- .../features/hardware/hardware-controller.ts | 30 +- .../app/api/features/hardware/mdc-service.ts | 9 +- .../app/api/features/hardware/node-service.ts | 51 +- .../src/app/api/features/hardware/service.ts | 118 +-- apps/ove-bridge/src/app/app.ts | 17 +- .../src/app/events/electron.events.ts | 22 +- .../src/app/events/update.events.ts | 3 +- apps/ove-bridge/src/env.ts | 2 +- apps/ove-bridge/src/ipc-routes.ts | 29 +- apps/ove-client-ui/.eslintrc.json | 43 +- apps/ove-client-ui/src/app/app-controller.ts | 2 +- apps/ove-client-ui/src/app/app.spec.tsx | 1 + apps/ove-client-ui/src/app/app.tsx | 7 +- apps/ove-client-ui/src/main.tsx | 2 +- apps/ove-core-ui-e2e/playwright.config.ts | 6 +- apps/ove-core-ui-e2e/src/app.spec.ts | 6 +- apps/ove-core-ui/.eslintrc.json | 48 +- apps/ove-core-ui/postcss.config.js | 7 +- apps/ove-core-ui/src/app/app.spec.tsx | 14 +- apps/ove-core-ui/src/app/app.tsx | 31 +- apps/ove-core-ui/src/app/router.tsx | 20 +- .../src/components/controller/controller.tsx | 1 + .../src/components/protected-route.tsx | 9 +- .../s3-file-select/s3-file-select.tsx | 24 +- apps/ove-core-ui/src/env.ts | 32 +- apps/ove-core-ui/src/hooks.ts | 19 +- apps/ove-core-ui/src/main.tsx | 7 +- apps/ove-core-ui/src/pages/dashboard/page.tsx | 4 +- .../hardware/components/actions/actions.tsx | 5 +- .../components/actions/mdc-actions.tsx | 6 +- .../components/actions/multi-actions.tsx | 6 +- .../components/actions/node-actions.tsx | 10 +- .../components/actions/projector-actions.tsx | 6 +- .../browser-modals/browser-id-input.tsx | 4 +- .../browser-modals/browser-input.tsx | 7 +- .../browser-modals/browser-status.tsx | 34 +- .../hardware/components/calendar/calendar.tsx | 20 +- .../hardware/components/console/console.tsx | 6 +- .../components/controller/multi-hooks.ts | 320 ++++--- .../components/controller/single-hooks.ts | 274 ++++-- .../components/data-table/columns.tsx | 11 +- .../components/data-table/data-table.tsx | 43 +- .../hardware/components/header/header.tsx | 6 +- .../components/info/info-container.tsx | 70 +- .../pages/hardware/components/info/info.tsx | 17 +- .../hardware/components/live-view/hooks.ts | 6 +- .../components/live-view/live-view.tsx | 8 +- .../components/observatory/observatory.tsx | 6 +- .../paginated-dialog/paginated-dialog.tsx | 6 +- .../hardware/components/popups/popups.tsx | 48 +- .../components/power-mode/power-mode.tsx | 28 +- .../screenshot/screenshot-display.tsx | 27 +- .../screenshot/screenshot-input.tsx | 15 +- .../search-select/search-select.tsx | 4 +- .../hardware/components/toolbar/toolbar.tsx | 1 + .../components/value-modal/value-modal.tsx | 8 +- .../hardware/components/volume/volume.tsx | 7 +- apps/ove-core-ui/src/pages/hardware/hooks.ts | 7 +- apps/ove-core-ui/src/pages/hardware/page.tsx | 53 +- apps/ove-core-ui/src/pages/hardware/types.ts | 33 +- apps/ove-core-ui/src/pages/launcher/page.tsx | 17 +- .../src/pages/launcher/project-card.tsx | 1 + .../src/pages/launcher/projects.tsx | 7 +- apps/ove-core-ui/src/pages/login/page.tsx | 1 + .../pages/project-editor/actions/actions.tsx | 10 +- .../pages/project-editor/canvas/canvas.tsx | 99 +- .../canvas/resize-container.tsx | 4 +- .../config-editor/config-editor.tsx | 4 +- .../controller-editor/controller-editor.tsx | 6 +- .../project-editor/env-editor/env-editor.tsx | 6 +- .../project-editor/file-upload/editor.tsx | 16 +- .../file-upload/file-upload.tsx | 15 +- .../project-editor/file-upload/upload.tsx | 13 +- .../src/pages/project-editor/hooks.ts | 170 ++-- .../launch-config/launch-config.tsx | 1 + .../src/pages/project-editor/loader.tsx | 7 +- .../project-editor/metadata/metadata.tsx | 78 +- .../src/pages/project-editor/page.tsx | 74 +- .../section-config/section-config.tsx | 86 +- .../section-importer/section-importer.tsx | 36 +- .../project-editor/sections/sections.tsx | 86 +- .../space-config/space-config.tsx | 15 +- .../project-editor/state-tabs/state-tabs.tsx | 4 +- .../src/pages/project-editor/types.ts | 2 +- .../src/pages/project-editor/utils.ts | 12 +- apps/ove-core-ui/src/pages/sockets/page.tsx | 14 +- apps/ove-core-ui/src/store.ts | 14 +- apps/ove-core-ui/src/utils.ts | 4 +- apps/ove-core-ui/src/utils/api.ts | 3 +- apps/ove-core-ui/tailwind.config.js | 14 +- apps/ove-core-ui/vite.config.ts | 1 + apps/ove-core/src/env.ts | 50 +- apps/ove-core/src/main.ts | 5 +- apps/ove-core/src/server/app.ts | 7 +- apps/ove-core/src/server/auth/controller.ts | 45 +- apps/ove-core/src/server/auth/router.ts | 13 +- apps/ove-core/src/server/bridge/router.ts | 70 +- apps/ove-core/src/server/context.ts | 11 +- apps/ove-core/src/server/core/controller.ts | 26 +- apps/ove-core/src/server/core/router.ts | 14 +- apps/ove-core/src/server/db.ts | 6 +- apps/ove-core/src/server/hardware/router.ts | 61 +- .../src/server/projects/controller.ts | 20 +- apps/ove-core/src/server/projects/router.ts | 44 +- apps/ove-core/src/server/router.ts | 1 - apps/ove-core/src/server/s3.ts | 36 + apps/ove-core/src/server/socket-setup.ts | 14 +- apps/ove-core/src/server/sockets.ts | 25 +- apps/ove-core/src/server/state.ts | 2 +- apps/ove-core/src/server/trpc.ts | 24 +- libs/mdc-control/src/lib/mdc-control.ts | 78 +- libs/ove-logging/src/index.ts | 2 +- libs/ove-logging/src/lib/constants.ts | 5 +- libs/ove-logging/src/lib/ove-logging.ts | 23 +- libs/ove-server-utils/src/index.ts | 1 + .../ove-server-utils/src/lib/ove-env-utils.ts | 80 +- libs/ove-server-utils/src/lib/ove-fetch.ts | 18 +- .../src/lib/ove-file-utils.ts | 18 +- libs/ove-types/.eslintrc.json | 11 +- libs/ove-types/helpers.d.ts | 36 + libs/ove-types/helpers.js | 44 + libs/ove-types/jest.config.ts | 2 + libs/ove-types/src/index.ts | 5 +- libs/ove-types/src/lib/bridge/service.spec.ts | 39 +- libs/ove-types/src/lib/bridge/service.ts | 80 +- libs/ove-types/src/lib/hardware.spec.ts | 35 +- libs/ove-types/src/lib/hardware.ts | 5 +- .../src/lib/hardware/bridge-transform.spec.ts | 39 +- .../src/lib/hardware/bridge-transform.ts | 84 +- .../ove-types/src/lib/hardware/bridge.spec.ts | 40 +- libs/ove-types/src/lib/hardware/bridge.ts | 14 +- .../src/lib/hardware/client-transform.spec.ts | 37 +- .../src/lib/hardware/client-transform.ts | 46 +- .../ove-types/src/lib/hardware/client.spec.ts | 38 +- libs/ove-types/src/lib/hardware/client.ts | 13 +- .../src/lib/hardware/core-transform.spec.ts | 38 +- .../src/lib/hardware/core-transform.ts | 64 +- libs/ove-types/src/lib/hardware/core.spec.ts | 52 +- libs/ove-types/src/lib/hardware/core.ts | 7 +- .../src/lib/hardware/service.spec.ts | 43 +- libs/ove-types/src/lib/ove-types.spec.ts | 35 +- libs/ove-types/src/lib/ove-types.ts | 15 +- .../src/lib/projects/projects.spec.ts | 29 + libs/ove-types/src/lib/projects/projects.ts | 10 + libs/ove-types/tsconfig.spec.json | 1 + libs/ove-utils/src/index.ts | 1 + libs/ove-utils/src/lib/ove-fetch.ts | 20 +- libs/ove-utils/src/lib/ove-json.ts | 12 +- libs/ove-utils/src/lib/ove-sockets.ts | 48 +- libs/ove-utils/src/lib/ove-utils.ts | 56 +- libs/ui-base-components/.eslintrc.json | 43 +- libs/ui-base-components/postcss.config.js | 9 +- libs/ui-base-components/src/lib/button.tsx | 41 +- libs/ui-base-components/src/lib/card.tsx | 56 +- .../src/lib/navigation-menu.tsx | 101 +- libs/ui-base-components/src/lib/resizable.tsx | 32 +- libs/ui-base-components/src/lib/select.tsx | 85 +- libs/ui-base-components/src/lib/sonner.tsx | 21 +- libs/ui-base-components/src/lib/utils.ts | 6 +- libs/ui-base-components/tailwind.config.js | 12 +- libs/ui-components/.eslintrc.json | 43 +- libs/ui-components/postcss.config.js | 8 +- libs/ui-components/src/index.ts | 2 +- .../src/lib/calendar/calendar.tsx | 1 + libs/ui-components/src/lib/calendar/hooks.ts | 5 +- .../src/lib/calendar/last-updated.tsx | 1 + libs/ui-components/src/lib/dialog/dialog.tsx | 9 +- libs/ui-components/src/lib/dialog/hook.ts | 2 +- libs/ui-components/src/lib/nav.tsx | 53 +- .../ui-components/src/lib/power-mode/hooks.ts | 25 +- .../src/lib/power-mode/power-mode.tsx | 36 +- libs/ui-components/src/lib/snackbar/hook.ts | 2 +- .../src/lib/snackbar/snackbar.tsx | 7 +- .../src/lib/video-streams/video-streams.tsx | 1 + libs/ui-components/tailwind.config.js | 8 +- libs/ui-reorderable-list/src/index.ts | 13 +- .../components/reorderable-item.jsx | 350 +++---- .../components/reorderable-list-group.jsx | 76 +- .../components/reorderable-list.jsx | 256 +++--- .../controllers/list-controller.js | 62 +- .../src/lib/reorderable-list/lib/dom/index.js | 87 +- .../lib/reorderable-list/lib/math/index.js | 10 +- .../lib/reorderable-list/lib/object/index.js | 25 +- .../lib/reorderable-list/models/list-item.js | 23 +- .../src/lib/reorderable-list/models/list.js | 45 +- package-lock.json | 866 ++++++++++++++++-- package.json | 6 +- 213 files changed, 4661 insertions(+), 2315 deletions(-) create mode 100644 apps/ove-core/src/server/s3.ts create mode 100644 libs/ove-types/helpers.d.ts create mode 100644 libs/ove-types/helpers.js create mode 100644 libs/ove-types/src/lib/projects/projects.spec.ts create mode 100644 libs/ove-types/src/lib/projects/projects.ts diff --git a/.eslintrc.json b/.eslintrc.json index a6464969..591518bd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "@nrwl/nx" ], "extends": [ + "plugin:react/recommended", "eslint:recommended", "google" ], @@ -19,6 +20,20 @@ "*.jsx" ], "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ], "@nrwl/nx/enforce-module-boundaries": [ "error", { @@ -42,6 +57,7 @@ "*.tsx" ], "extends": [ + "plugin:react/recommended", "plugin:@nrwl/nx/typescript", "eslint:recommended", "google" @@ -79,6 +95,7 @@ "*.jsx" ], "extends": [ + "plugin:react/recommended", "plugin:@nrwl/nx/javascript", "eslint:recommended", "google" diff --git a/apps/ove-bridge-ui/.eslintrc.json b/apps/ove-bridge-ui/.eslintrc.json index 734ddace..1e382421 100644 --- a/apps/ove-bridge-ui/.eslintrc.json +++ b/apps/ove-bridge-ui/.eslintrc.json @@ -1,17 +1,48 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "plugin:@nrwl/nx/react", + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ] + } }, { - "files": ["*.ts", "*.tsx"], + "files": [ + "*.ts", + "*.tsx" + ], "rules": {} }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/apps/ove-bridge-ui/src/app/app.spec.tsx b/apps/ove-bridge-ui/src/app/app.spec.tsx index 3fabcf6a..707474c3 100644 --- a/apps/ove-bridge-ui/src/app/app.spec.tsx +++ b/apps/ove-bridge-ui/src/app/app.spec.tsx @@ -1,9 +1,8 @@ +import App from "./app"; +import React from "react"; import { render } from "@testing-library/react"; - import { BrowserRouter } from "react-router-dom"; -import App from "./app"; - describe("App", () => { it("should render successfully", () => { const { baseElement } = render( diff --git a/apps/ove-bridge-ui/src/app/app.tsx b/apps/ove-bridge-ui/src/app/app.tsx index db3c1415..b3666279 100644 --- a/apps/ove-bridge-ui/src/app/app.tsx +++ b/apps/ove-bridge-ui/src/app/app.tsx @@ -1,6 +1,8 @@ import Nav from "./nav"; +import React from "react"; import Router from "./router"; // IGNORE PATH - dependency removed at runtime +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type OutboundAPI, type OutboundAPIChannels diff --git a/apps/ove-bridge-ui/src/app/nav.tsx b/apps/ove-bridge-ui/src/app/nav.tsx index bb96aa9f..25b1fefe 100644 --- a/apps/ove-bridge-ui/src/app/nav.tsx +++ b/apps/ove-bridge-ui/src/app/nav.tsx @@ -1,4 +1,7 @@ +import React from "react"; import Logo from "../assets/icon.svg"; +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { Nav as Navigation } from "@ove/ui-components"; import { NavigationMenuLink } from "@ove/ui-base-components"; diff --git a/apps/ove-bridge-ui/src/app/router.tsx b/apps/ove-bridge-ui/src/app/router.tsx index 87fa8569..151f7993 100644 --- a/apps/ove-bridge-ui/src/app/router.tsx +++ b/apps/ove-bridge-ui/src/app/router.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Home from "../pages/home/home"; import Hardware from "../pages/hardware/hardware"; import { Route, Routes } from "react-router-dom"; diff --git a/apps/ove-bridge-ui/src/env.ts b/apps/ove-bridge-ui/src/env.ts index 767f0875..47f07139 100644 --- a/apps/ove-bridge-ui/src/env.ts +++ b/apps/ove-bridge-ui/src/env.ts @@ -1,3 +1,3 @@ import { Logger } from "@ove/ove-logging"; -export const logger = Logger("ove-bridge-ui"); \ No newline at end of file +export const logger = Logger("ove-bridge-ui"); diff --git a/apps/ove-bridge-ui/src/main.tsx b/apps/ove-bridge-ui/src/main.tsx index 2e6d5e20..07978d6f 100644 --- a/apps/ove-bridge-ui/src/main.tsx +++ b/apps/ove-bridge-ui/src/main.tsx @@ -1,4 +1,4 @@ -import { StrictMode } from "react"; +import React, { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; diff --git a/apps/ove-bridge-ui/src/pages/hardware/components/auth/auth.tsx b/apps/ove-bridge-ui/src/pages/hardware/components/auth/auth.tsx index 4e1c5c06..af86c5c2 100644 --- a/apps/ove-bridge-ui/src/pages/hardware/components/auth/auth.tsx +++ b/apps/ove-bridge-ui/src/pages/hardware/components/auth/auth.tsx @@ -1,7 +1,9 @@ import { type Mode } from "../../utils"; import { useForm } from "react-hook-form"; import { type Device } from "@ove/ove-types"; -import { forwardRef, useEffect, useState } from "react"; +import React, { forwardRef, useEffect, useState } from "react"; +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { Dialog, Snackbar, useSnackbar } from "@ove/ui-components"; import styles from "./auth.module.scss"; @@ -17,9 +19,9 @@ const Auth = forwardRef(({ }, ref) => { const [status, setStatus] = useState(null); const { notification, isVisible } = useSnackbar(); - const {register, handleSubmit} = useForm<{pin: string}>(); + const { register, handleSubmit } = useForm<{ pin: string }>(); - const handleAuth = async ({pin}: {pin: string}) => { + const handleAuth = async ({ pin }: { pin: string }) => { if (device === null) throw new Error("Cannot ID null device"); const registered = await window.bridge.registerAuth({ id: device.id, pin }); @@ -40,7 +42,8 @@ const Auth = forwardRef(({ return setMode("overview")}>

Authorise: {device?.id}

-
+ @@ -50,4 +53,6 @@ const Auth = forwardRef(({
; }); +Auth.displayName = "Auth"; + export default Auth; diff --git a/apps/ove-bridge-ui/src/pages/hardware/components/devices/devices.tsx b/apps/ove-bridge-ui/src/pages/hardware/components/devices/devices.tsx index 2136ab57..aaebcfd0 100644 --- a/apps/ove-bridge-ui/src/pages/hardware/components/devices/devices.tsx +++ b/apps/ove-bridge-ui/src/pages/hardware/components/devices/devices.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { type Device, type ServiceType } from "@ove/ove-types"; import { Display, @@ -8,12 +8,14 @@ import { } from "react-bootstrap-icons"; import EditDevice from "../edit-device/edit-device"; import Auth from "../auth/auth"; -import { Mode } from "../../utils"; - -import styles from "./devices.module.scss"; +import { type Mode } from "../../utils"; import { assert } from "@ove/ove-utils"; +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { useDialog } from "@ove/ui-components"; +import styles from "./devices.module.scss"; + type DeviceCardProps = { device: Device setMode: (mode: Mode) => void @@ -53,8 +55,16 @@ const Devices = () => { const [devices, setDevices] = useState([]); const [mode, setMode] = useState("overview"); const [id, setId] = useState(null); - const { ref: authRef, closeDialog: closeAuthDialog, openDialog: openAuthDialog } = useDialog(); - const { ref: editRef, closeDialog: closeEditDialog, openDialog: openEditDialog } = useDialog(); + const { + ref: authRef, + closeDialog: closeAuthDialog, + openDialog: openAuthDialog + } = useDialog(); + const { + ref: editRef, + closeDialog: closeEditDialog, + openDialog: openEditDialog + } = useDialog(); const close = useCallback(() => setMode("overview"), []); @@ -78,18 +88,20 @@ const Devices = () => { openAuthDialog(); break; } - }, [mode, close]); + }, [mode, close, closeAuthDialog, + closeEditDialog, openAuthDialog, openEditDialog]); return

Devices

setMode(mode)} - device={id === null ? null : (devices.find(({ id: deviceId }) => - deviceId === id) ?? null)} /> + ref={editRef} + setMode={mode => setMode(mode)} + device={id === null ? null : (devices.find(({ id: deviceId }) => + deviceId === id) ?? null)} /> deviceId === id))} + device={id === null ? null : + assert(devices.find(({ id: deviceId }) => deviceId === id))} setMode={mode => setMode(mode)} />
{devices.map(device => void device: Device | null @@ -32,12 +34,19 @@ type Form = { mac: string } -const saveDevice_ = (device: Device | null, setMode: (mode: Mode) => void, type: ServiceType, data: Form, e: BaseSyntheticEvent | undefined) => { +const saveDevice_ = ( + device: Device | null, + setMode: (mode: Mode) => void, + type: ServiceType, + data: Form, + e: BaseSyntheticEvent | undefined +) => { const id = device?.id ?? assert(data.id); if (id === "" || id.length < 1) return false; - if ((e?.nativeEvent as unknown as NativeEvent)?.submitter?.name === "delete") { - window.bridge.removeDevice({deviceId: assert(device).id}) + if ((e?.nativeEvent as unknown as NativeEvent) + ?.submitter?.name === "delete") { + window.bridge.removeDevice({ deviceId: assert(device).id }) .catch(console.error).then(() => setMode("overview")); return; } @@ -45,7 +54,8 @@ const saveDevice_ = (device: Device | null, setMode: (mode: Mode) => void, type: if (type === "node") { auth = false; - } else if (data.authUsername !== undefined && data.authPassword !== undefined) { + } else if (data.authUsername !== undefined && + data.authPassword !== undefined) { auth = { username: data.authUsername, password: data.authPassword }; } @@ -73,9 +83,14 @@ const EditDevice = forwardRef(({ device, setMode }, ref) => { - const [type, setType] = useState(device?.type ?? "node"); + const [type, setType] = + useState(device?.type ?? "node"); const { register, handleSubmit } = useForm(); - const saveDevice = useCallback((data: Form, e: BaseSyntheticEvent | undefined) => saveDevice_(device, setMode, type, data, e), [device, setMode, type]); + const saveDevice = useCallback(( + data: Form, + e: BaseSyntheticEvent | undefined + ) => + saveDevice_(device, setMode, type, data, e), [device, setMode, type]); return setMode("overview")}> @@ -126,4 +141,6 @@ const EditDevice = forwardRef(({ ; }); +EditDevice.displayName = "EditDevice"; + export default EditDevice; diff --git a/apps/ove-bridge-ui/src/pages/hardware/hardware.tsx b/apps/ove-bridge-ui/src/pages/hardware/hardware.tsx index ca7f8223..37c205cd 100644 --- a/apps/ove-bridge-ui/src/pages/hardware/hardware.tsx +++ b/apps/ove-bridge-ui/src/pages/hardware/hardware.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Devices from "./components/devices/devices"; const Hardware = () =>
diff --git a/apps/ove-bridge-ui/src/pages/home/components/auto-mode-configuration/auto-mode-configuration.tsx b/apps/ove-bridge-ui/src/pages/home/components/auto-mode-configuration/auto-mode-configuration.tsx index aa3d24b3..1e2a384a 100644 --- a/apps/ove-bridge-ui/src/pages/home/components/auto-mode-configuration/auto-mode-configuration.tsx +++ b/apps/ove-bridge-ui/src/pages/home/components/auto-mode-configuration/auto-mode-configuration.tsx @@ -1,7 +1,7 @@ +import React, { useEffect } from "react"; import { useForm } from "react-hook-form"; import styles from "./auto-mode-configuration.module.scss"; -import { useEffect } from "react"; export type AutoModeConfigurationProps = { closeDialog: () => void @@ -25,7 +25,7 @@ const AutoModeConfiguration = ({ closeDialog }: AutoModeConfigurationProps) => { setValue("end" as const, autoSchedule?.sleep ?? undefined); setValue("days" as const, autoSchedule?.schedule ?? defaultDaySelection); }); - }, []); + }, [setValue]); const onSubmit = ({ start, end, days }: Form) => { window.bridge.setAutoSchedule({ diff --git a/apps/ove-bridge-ui/src/pages/home/components/configuration/configuration.tsx b/apps/ove-bridge-ui/src/pages/home/components/configuration/configuration.tsx index 51c7943a..8db2ed8a 100644 --- a/apps/ove-bridge-ui/src/pages/home/components/configuration/configuration.tsx +++ b/apps/ove-bridge-ui/src/pages/home/components/configuration/configuration.tsx @@ -1,7 +1,7 @@ import { useForm } from "react-hook-form"; import { useEnv, useSocket } from "./hooks"; -import { type BaseSyntheticEvent } from "react"; import { type NativeEvent } from "@ove/ove-types"; +import React, { type BaseSyntheticEvent } from "react"; import styles from "./configuration.module.scss"; diff --git a/apps/ove-bridge-ui/src/pages/home/components/configuration/hooks.ts b/apps/ove-bridge-ui/src/pages/home/components/configuration/hooks.ts index 48a39ff9..06e922c8 100644 --- a/apps/ove-bridge-ui/src/pages/home/components/configuration/hooks.ts +++ b/apps/ove-bridge-ui/src/pages/home/components/configuration/hooks.ts @@ -1,7 +1,15 @@ -import { useEffect, useState } from "react"; import { logger } from "../../../../env"; +import { useEffect, useState } from "react"; + +type Env = { + bridgeName?: string + coreURL?: string + calendarURL?: string +} -export const useEnv = (setValue: (k: "coreURL" | "calendarURL" | "bridgeName", v: string | undefined) => void) => { +export const useEnv = ( + setValue: (k: keyof Env, v: string | undefined) => void +) => { useEffect(() => { window.bridge.getEnv({}).then(env => { if ("oveError" in env) return; @@ -9,16 +17,17 @@ export const useEnv = (setValue: (k: "coreURL" | "calendarURL" | "bridgeName", v setValue("bridgeName", env.bridgeName); setValue("calendarURL", env.calendarURL); }); - }, []); + }, [setValue]); - return (env: { bridgeName?: string, coreURL?: string, calendarURL?: string }) => window.bridge.updateEnv(env).catch(logger.error); + return (env: Env) => window.bridge.updateEnv(env).catch(logger.error); }; export const useSocket = () => { const [connected, setConnected] = useState(false); useEffect(() => { - window.bridge.getSocketStatus({}).then(status => setConnected(typeof status === "boolean" && status)); + window.bridge.getSocketStatus({}).then(status => + setConnected(typeof status === "boolean" && status)); window.bridge.receive("socket-connect", () => { setConnected(true); }); @@ -28,4 +37,4 @@ export const useSocket = () => { }, []); return connected; -}; \ No newline at end of file +}; diff --git a/apps/ove-bridge-ui/src/pages/home/components/key-pass/key-pass.tsx b/apps/ove-bridge-ui/src/pages/home/components/key-pass/key-pass.tsx index 694a7054..822e1c82 100644 --- a/apps/ove-bridge-ui/src/pages/home/components/key-pass/key-pass.tsx +++ b/apps/ove-bridge-ui/src/pages/home/components/key-pass/key-pass.tsx @@ -1,8 +1,11 @@ -import { useEffect, useState } from "react"; +import { Json } from "@ove/ove-utils"; import { Clipboard } from "react-bootstrap-icons"; +import React, { useEffect, useState } from "react"; +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { Snackbar } from "@ove/ui-components"; + import styles from "./key-pass.module.scss"; -import { Json } from "@ove/ove-utils"; const KeyPass = () => { const [displayPublicKey, setDisplayPublicKey] = useState(""); diff --git a/apps/ove-bridge-ui/src/pages/home/home.tsx b/apps/ove-bridge-ui/src/pages/home/home.tsx index 0d1aad5c..d3345a44 100644 --- a/apps/ove-bridge-ui/src/pages/home/home.tsx +++ b/apps/ove-bridge-ui/src/pages/home/home.tsx @@ -1,3 +1,5 @@ +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { Calendar, Dialog, @@ -6,7 +8,7 @@ import { useCalendar, useDialog } from "@ove/ui-components"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import KeyPass from "./components/key-pass/key-pass"; import Configuration from "./components/configuration/configuration"; import AutoModeConfiguration @@ -19,20 +21,27 @@ import { import styles from "./home.module.scss"; -const useFetchCalendar = (controller: typeof window["bridge"]["getCalendar"]) => { - const [response, setResponse] = useState(undefined); +const useFetchCalendar = ( + controller: typeof window["bridge"]["getCalendar"] +) => { + const [response, setResponse] = + useState(undefined); const x = useCalendar(response); useEffect(() => { controller({}).then(setResponse); - }, []); + }, [controller]); return x; }; const Home = () => { const [mode, setMode] = useState<"manual" | "auto" | "eco" | null>("manual"); - const { calendar, lastUpdated, refresh: refreshCalendar } = useFetchCalendar(window.bridge.getCalendar); + const { + calendar, + lastUpdated, + refresh: refreshCalendar + } = useFetchCalendar(window.bridge.getCalendar); const { ref, openDialog, closeDialog } = useDialog(); useEffect(() => { @@ -57,7 +66,8 @@ const Home = () => {
- +
{calendar !== null ? : null} diff --git a/apps/ove-bridge-ui/src/pages/live-view/hooks.ts b/apps/ove-bridge-ui/src/pages/live-view/hooks.ts index 8cf9e733..4fb9b94c 100644 --- a/apps/ove-bridge-ui/src/pages/live-view/hooks.ts +++ b/apps/ove-bridge-ui/src/pages/live-view/hooks.ts @@ -13,8 +13,8 @@ export const useVideoStreams = () => { return () => { window.bridge.stopStreams({}).catch(logger.error); - } + }; }, []); return streams; -}; \ No newline at end of file +}; diff --git a/apps/ove-bridge-ui/src/pages/live-view/live-view.tsx b/apps/ove-bridge-ui/src/pages/live-view/live-view.tsx index 8d52f3f2..0bcaadbb 100644 --- a/apps/ove-bridge-ui/src/pages/live-view/live-view.tsx +++ b/apps/ove-bridge-ui/src/pages/live-view/live-view.tsx @@ -1,9 +1,12 @@ +import React from "react"; import { useVideoStreams } from "./hooks"; +// TODO: investigate circular dependency +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { VideoStreams } from "@ove/ui-components"; const LiveView = () => { const streams = useVideoStreams(); - return + return ; }; -export default LiveView; \ No newline at end of file +export default LiveView; diff --git a/apps/ove-bridge-ui/src/svg.d.ts b/apps/ove-bridge-ui/src/svg.d.ts index a5a700bb..5e52f805 100644 --- a/apps/ove-bridge-ui/src/svg.d.ts +++ b/apps/ove-bridge-ui/src/svg.d.ts @@ -1,4 +1,4 @@ declare module "*.svg" { const content: string; export default content; -} \ No newline at end of file +} diff --git a/apps/ove-bridge/.eslintrc.json b/apps/ove-bridge/.eslintrc.json index 9d9c0db5..24273f1e 100644 --- a/apps/ove-bridge/.eslintrc.json +++ b/apps/ove-bridge/.eslintrc.json @@ -1,17 +1,41 @@ { - "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + } }, { - "files": ["*.ts", "*.tsx"], + "files": [ + "*.ts", + "*.tsx" + ], "rules": {} }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/apps/ove-bridge/src/app/api/features/bridge/controller.ts b/apps/ove-bridge/src/app/api/features/bridge/controller.ts index 7e59ea2b..0247aeea 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/controller.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/controller.ts @@ -7,14 +7,18 @@ import { type TBridgeController } from "@ove/ove-types"; -const wrap = (x: T) => ({meta: {bridge: assert(env.BRIDGE_NAME)}, response: x}); +const wrap = (x: T) => ({ + meta: { bridge: assert(env.BRIDGE_NAME) }, + response: x +}); -export const controller: TBridgeController = Object.entries(service).reduce((acc, [k, route]) => { - if (excludeKeys.includes(k as keyof TAPIRoutes)) return acc; - acc[k] = async (args: Parameters[0]) => { - // @ts-ignore - const res = await route(args); - return wrap(res); - }; - return acc; -}, <{[key: string]: unknown}>{}) as TBridgeController; \ No newline at end of file +export const controller: TBridgeController = Object.entries(service) + .reduce((acc, [k, route]) => { + if (excludeKeys.includes(k as keyof TAPIRoutes)) return acc; + acc[k] = async (args: Parameters[0]) => { + // @ts-expect-error - arg spread + const res = await route(args); + return wrap(res); + }; + return acc; + }, <{ [key: string]: unknown }>{}) as TBridgeController; diff --git a/apps/ove-bridge/src/app/api/features/bridge/power-scheduler.ts b/apps/ove-bridge/src/app/api/features/bridge/power-scheduler.ts index 716023ee..2da92cbb 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/power-scheduler.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/power-scheduler.ts @@ -1,3 +1,5 @@ +/* global process */ + import { env, logger } from "../../../../env"; import * as schedule from "node-schedule"; import { AutoSchedule, CalendarEvent } from "@ove/ove-types"; @@ -13,7 +15,9 @@ export const setManualSchedule = () => { schedule.gracefulShutdown().catch(logger.error); }; -export const setEcoSchedule = async (ecoSchedule: CalendarEvent[]): Promise => { +export const setEcoSchedule = async ( + ecoSchedule: CalendarEvent[] +): Promise => { env.POWER_MODE = "eco"; const groups = Object.values(ecoSchedule.reduce((acc, event) => { @@ -44,20 +48,26 @@ export const setEcoSchedule = async (ecoSchedule: CalendarEvent[]): Promise logger.info(`Started devices with response: ${Json.stringify(response)}`)); + multiDeviceHandler("start", {}, response => + logger.info(`Started devices with response: + ${Json.stringify(response)}`)); } }); schedule.scheduleJob(end, () => { if (process.env.NODE_ENV === "development") { logger.info(`Triggered for ${end.toISOString()}`); } else { - multiDeviceHandler("shutdown", {}, response => logger.info(`Stopped devices with response: ${Json.stringify(response)}`)); + multiDeviceHandler("shutdown", {}, response => + logger.info(`Stopped devices with response: + ${Json.stringify(response)}`)); } }); }); }; -export const setAutoSchedule = async (autoSchedule_: AutoSchedule | undefined): Promise => { +export const setAutoSchedule = async ( + autoSchedule_: AutoSchedule | undefined +): Promise => { let autoSchedule: AutoSchedule | undefined = autoSchedule_; if (autoSchedule !== undefined) { env.AUTO_SCHEDULE = autoSchedule; @@ -84,7 +94,9 @@ export const setAutoSchedule = async (autoSchedule_: AutoSchedule | undefined): if (process.env.NODE_ENV === "development") { logger.info("Waking"); } else { - multiDeviceHandler("shutdown", {}, response => logger.info(`Started devices with response: ${Json.stringify(response)}`)); + multiDeviceHandler("shutdown", {}, response => + logger.info(`Started devices with response: + ${Json.stringify(response)}`)); } }); }); @@ -104,9 +116,11 @@ export const setAutoSchedule = async (autoSchedule_: AutoSchedule | undefined): if (process.env.NODE_ENV === "development") { logger.info("Sleeping"); } else { - multiDeviceHandler("shutdown", {}, response => logger.info(`Shutdown devices with response: ${Json.stringify(response)}`)); + multiDeviceHandler("shutdown", {}, response => + logger.info(`Shutdown devices with response: + ${Json.stringify(response)}`)); } }); }); } -}; \ No newline at end of file +}; diff --git a/apps/ove-bridge/src/app/api/features/bridge/routes.ts b/apps/ove-bridge/src/app/api/features/bridge/routes.ts index 996f59d8..0758d2f8 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/routes.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/routes.ts @@ -49,7 +49,8 @@ export const initBridge = () => { assert(socket).on(k, getHandler(k)); }); - socket.on("connect_error", err => logger.error(`connection error due to ${err.message}`)); + socket.on("connect_error", err => + logger.error(`connection error due to ${err.message}`)); }; -initService(initBridge, initHardware); \ No newline at end of file +initService(initBridge, initHardware); diff --git a/apps/ove-bridge/src/app/api/features/bridge/service.ts b/apps/ove-bridge/src/app/api/features/bridge/service.ts index ff70333e..6929d9b0 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/service.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/service.ts @@ -19,14 +19,20 @@ import { closeSocket, getSocketStatus } from "./sockets"; let initBridge_: (() => void) | null = null; let initHardware_: (() => void) | null = null; -export const initService = (initBridge: () => void, initHardware: () => void) => { +export const initService = ( + initBridge: () => void, + initHardware: () => void +) => { initBridge_ = initBridge; initHardware_ = initHardware; }; export const service: TBridgeService = { - getDevice: ({ deviceId }) => env.HARDWARE.find(({ id }) => id === deviceId) ?? raise(`No device with id: ${deviceId}`), - getDevices: ({ tag }) => tag === undefined ? env.HARDWARE : env.HARDWARE.filter(({ tags }) => tags.includes(tag)), + getDevice: ({ deviceId }) => + env.HARDWARE.find(({ id }) => id === deviceId) ?? + raise(`No device with id: ${deviceId}`), + getDevices: ({ tag }) => tag === undefined ? + env.HARDWARE : env.HARDWARE.filter(({ tags }) => tags.includes(tag)), addDevice: ({ device }) => { env.HARDWARE.push(device); return true; @@ -76,16 +82,22 @@ export const service: TBridgeService = { env.CALENDAR = calendar; return calendar; } catch (e) { - console.error(e); + logger.error(e); return undefined; } }, getSocketStatus: getSocketStatus, getMode: () => env.POWER_MODE, setManualSchedule: () => setManualSchedule(), - setEcoSchedule: ({ ecoSchedule }) => void setEcoSchedule(ecoSchedule).catch(logger.error), - setAutoSchedule: ({ autoSchedule }) => void setAutoSchedule(autoSchedule).catch(logger.error), - getEnv: () => ({ bridgeName: env.BRIDGE_NAME, coreURL: env.CORE_URL, calendarURL: env.CALENDAR_URL }), + setEcoSchedule: ({ ecoSchedule }) => + void setEcoSchedule(ecoSchedule).catch(logger.error), + setAutoSchedule: ({ autoSchedule }) => + void setAutoSchedule(autoSchedule).catch(logger.error), + getEnv: () => ({ + bridgeName: env.BRIDGE_NAME, + coreURL: env.CORE_URL, + calendarURL: env.CALENDAR_URL + }), updateEnv: ({ bridgeName, coreURL, calendarURL }) => { if (initHardware_ === null || initBridge_ === null) return; env.CORE_URL = coreURL; @@ -116,4 +128,4 @@ export const service: TBridgeService = { getPublicKey: () => env.PUBLIC_KEY, getAutoSchedule: () => env.AUTO_SCHEDULE, getGeometry: () => env.GEOMETRY -}; \ No newline at end of file +}; diff --git a/apps/ove-bridge/src/app/api/features/bridge/sockets.ts b/apps/ove-bridge/src/app/api/features/bridge/sockets.ts index 9e5cc926..5a51ff9a 100644 --- a/apps/ove-bridge/src/app/api/features/bridge/sockets.ts +++ b/apps/ove-bridge/src/app/api/features/bridge/sockets.ts @@ -3,7 +3,9 @@ import { TSocketInEvents, TSocketOutEvents } from "@ove/ove-types"; export let socket: Socket | null = null; -export const setSocket = (socket_: Socket | null) => { +export const setSocket = ( + socket_: Socket | null +) => { socket = socket_; }; @@ -16,8 +18,10 @@ export const closeSocket = () => { export const socketConnectListeners: (() => void)[] = []; export const socketDisconnectListeners: (() => void)[] = []; -export const registerSocketConnectedListener = (listener: () => void) => void socketConnectListeners.push(listener); +export const registerSocketConnectedListener = (listener: () => void) => + void socketConnectListeners.push(listener); -export const registerSocketDisconnectListener = (listener: () => void) => void socketDisconnectListeners.push(listener); +export const registerSocketDisconnectListener = (listener: () => void) => + void socketDisconnectListeners.push(listener); -export const getSocketStatus = () => socket?.connected ?? false; \ No newline at end of file +export const getSocketStatus = () => socket?.connected ?? false; diff --git a/apps/ove-bridge/src/app/api/features/hardware/hardware-controller.ts b/apps/ove-bridge/src/app/api/features/hardware/hardware-controller.ts index edb7307d..6398a1c9 100644 --- a/apps/ove-bridge/src/app/api/features/hardware/hardware-controller.ts +++ b/apps/ove-bridge/src/app/api/features/hardware/hardware-controller.ts @@ -1,6 +1,6 @@ import { deviceHandler, - multiDeviceHandler, + multiDeviceHandler } from "./service"; import { BridgeServiceKeys, @@ -24,7 +24,10 @@ export const closeHardwareSocket = () => { export const initHardware = () => { if (env.CORE_URL === undefined || env.BRIDGE_NAME === undefined) return; - socket = io(`ws://${env.CORE_URL}/hardware`, { autoConnect: false, parser: Parser }); + socket = io(`ws://${env.CORE_URL}/hardware`, { + autoConnect: false, + parser: Parser + }); socket.auth = { username: env.BRIDGE_NAME, password: env.PUBLIC_KEY @@ -41,11 +44,22 @@ export const initHardware = () => { BridgeServiceKeys.forEach(k => { - const deviceHandlerInterface = (args: Parameters[1], callback: Parameters[2]) => deviceHandler(k, args, callback).then(() => logger.info(`Handled: ${k}`)); - const multiDeviceHandlerInterface = (args: Parameters[1], callback: Parameters[2]) => multiDeviceHandler(k, args, callback).then(() => logger.info(`Handled: ${k}All`)); - assert(socket).on(k, deviceHandlerInterface as THardwareServerToClientEvents[typeof k]); - assert(socket).on(`${k}All`, multiDeviceHandlerInterface as THardwareServerToClientEvents[`${typeof k}All`]); + const deviceHandlerInterface = ( + args: Parameters[1], + callback: Parameters[2] + ) => deviceHandler(k, args, callback).then(() => + logger.info(`Handled: ${k}`)); + const multiDeviceHandlerInterface = ( + args: Parameters[1], + callback: Parameters[2] + ) => multiDeviceHandler(k, args, callback) + .then(() => logger.info(`Handled: ${k}All`)); + assert(socket).on(k, deviceHandlerInterface as + THardwareServerToClientEvents[typeof k]); + assert(socket).on(`${k}All`, multiDeviceHandlerInterface as + THardwareServerToClientEvents[`${typeof k}All`]); }); - socket.on("connect_error", err => logger.error(`connection error due to ${err.message}`)); -}; \ No newline at end of file + socket.on("connect_error", err => + logger.error(`connection error due to ${err.message}`)); +}; diff --git a/apps/ove-bridge/src/app/api/features/hardware/mdc-service.ts b/apps/ove-bridge/src/app/api/features/hardware/mdc-service.ts index 6f46c2e8..133a526b 100644 --- a/apps/ove-bridge/src/app/api/features/hardware/mdc-service.ts +++ b/apps/ove-bridge/src/app/api/features/hardware/mdc-service.ts @@ -24,7 +24,8 @@ const reboot = async ( if (isError(res)) return res; return new Promise(resolve => setTimeout(() => { - mdc.setPower(0x01, ip, port, "on").then(res => resolve(isError(res) ? res : true)); + mdc.setPower(0x01, ip, port, "on") + .then(res => resolve(isError(res) ? res : true)); }, 1000) ); }; @@ -70,7 +71,8 @@ const getInfo = async ( const isMuted = await mdc.getIsMute(0x01, ip, port); const model = await mdc.getModel(0x01, ip, port); - if (isError(power) || isError(volume) || isError(source) || isError(isMuted) || isError(model)) { + if (isError(power) || isError(volume) || isError(source) || + isError(isMuted) || isError(model)) { return raise("MDC ERROR"); } @@ -143,7 +145,8 @@ const setSource = async ( if (!parsedOpts.success) return undefined; - const res = await mdc.setSource(0x01, ip, port, mdc.sources[parsedOpts.data.source]); + const res = + await mdc.setSource(0x01, ip, port, mdc.sources[parsedOpts.data.source]); return isError(res) ? res : true; }; diff --git a/apps/ove-bridge/src/app/api/features/hardware/node-service.ts b/apps/ove-bridge/src/app/api/features/hardware/node-service.ts index 877de9fc..265c8ad4 100644 --- a/apps/ove-bridge/src/app/api/features/hardware/node-service.ts +++ b/apps/ove-bridge/src/app/api/features/hardware/node-service.ts @@ -2,6 +2,7 @@ import { wake } from "../../utils/wol"; // IGNORE PATH - as importing only type, will not trigger full import on build +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type AppRouter } from "../../../../../../ove-client/src/server/router"; import { createTRPCProxyClient, httpLink } from "@trpc/client"; import fetch from "node-fetch"; @@ -23,9 +24,13 @@ globalAny.AbortController = AbortController; globalAny.fetch = fetch; globalAny.WebSocket = ws; -const fixedEncodeURIComponent = (str: string) => encodeURIComponent(str).replace(/[!'()*]/g, c => "%" + c.charCodeAt(0).toString(16)); +const fixedEncodeURIComponent = (str: string) => + encodeURIComponent(str).replace(/[!'()*]/g, c => + "%" + c.charCodeAt(0).toString(16)); -export const createClient = (device: Device): ReturnType> => +export const createClient = ( + device: Device +): ReturnType> => createTRPCProxyClient({ links: [ httpLink({ @@ -35,7 +40,7 @@ export const createClient = (device: Device): ReturnType) => { if (!parsedOpts.success) return undefined; - return await createClient(device).reboot.mutate(parsedOpts.data as z.infer); + return await createClient(device).reboot + .mutate(parsedOpts.data as z.infer); }; const shutdown = async ( @@ -58,7 +64,8 @@ const shutdown = async ( if (!parsedOpts.success) return undefined; - await createClient(device).shutdown.mutate(parsedOpts.data as z.infer); + await createClient(device).shutdown + .mutate(parsedOpts.data as z.infer); return true; }; @@ -77,7 +84,8 @@ const getInfo = async (device: Device, args: TBridgeServiceArgs<"getInfo">) => { if (!parsedOpts.success) return undefined; - return await createClient(device).getInfo.query(parsedOpts.data); + return await createClient(device).getInfo + .query(parsedOpts.data as z.infer); }; const getStatus = async ( @@ -87,13 +95,10 @@ const getStatus = async ( const statusOptsSchema = z.object({}).strict(); const parsedOpts = statusOptsSchema.safeParse(args); - if (!parsedOpts.success) { - console.log(JSON.stringify(parsedOpts.error)); - } - if (!parsedOpts.success) return undefined; - return await createClient(device).getStatus.query(parsedOpts.data); + return await createClient(device).getStatus + .query(parsedOpts.data as z.infer); }; const execute = async (device: Device, args: TBridgeServiceArgs<"execute">) => { @@ -102,7 +107,8 @@ const execute = async (device: Device, args: TBridgeServiceArgs<"execute">) => { if (!parsedOpts.success) return undefined; - return await createClient(device).execute.mutate(parsedOpts.data as z.infer); + return await createClient(device).execute + .mutate(parsedOpts.data as z.infer); }; const screenshot = async ( @@ -119,19 +125,22 @@ const screenshot = async ( if (!parsedOpts.success) return undefined; - return await createClient(device).screenshot.mutate(parsedOpts.data as z.infer); + return await createClient(device).screenshot + .mutate(parsedOpts.data as z.infer); }; const openBrowser = async ( device: Device, args: TBridgeServiceArgs<"openBrowser"> ) => { - const openBrowserOptsSchema = z.object({ displayId: z.number(), url: z.string() }).strict(); + const openBrowserOptsSchema = + z.object({ displayId: z.number(), url: z.string() }).strict(); const parsedOpts = openBrowserOptsSchema.safeParse(args); if (!parsedOpts.success) return undefined; - return await createClient(device).openBrowser.mutate(parsedOpts.data as z.infer); + return await createClient(device).openBrowser + .mutate(parsedOpts.data as z.infer); }; const getBrowser = async ( @@ -145,7 +154,8 @@ const getBrowser = async ( if (!parsedOpts.success) return undefined; - return await createClient(device).getBrowser.query(parsedOpts.data); + return await createClient(device).getBrowser + .query(parsedOpts.data as z.infer); }; const closeBrowser = async ( @@ -157,7 +167,8 @@ const closeBrowser = async ( if (!parsedOpts.success) return undefined; - return await createClient(device).closeBrowser.mutate(parsedOpts.data as z.infer); + return await createClient(device).closeBrowser + .mutate(parsedOpts.data as z.infer); }; const closeBrowsers = async ( @@ -169,7 +180,8 @@ const closeBrowsers = async ( if (!parsedOpts.success) return undefined; - return await createClient(device).closeBrowsers.mutate(parsedOpts.data as z.infer); + return await createClient(device).closeBrowsers + .mutate(parsedOpts.data as z.infer); }; const getBrowsers = async ( @@ -181,7 +193,8 @@ const getBrowsers = async ( if (!parsedOpts.success) return undefined; - return await createClient(device).getBrowsers.query(parsedOpts.data); + return await createClient(device).getBrowsers + .query(parsedOpts.data as z.infer); }; const NodeService: TBridgeHardwareService = { diff --git a/apps/ove-bridge/src/app/api/features/hardware/service.ts b/apps/ove-bridge/src/app/api/features/hardware/service.ts index c8118310..9c32d2e9 100644 --- a/apps/ove-bridge/src/app/api/features/hardware/service.ts +++ b/apps/ove-bridge/src/app/api/features/hardware/service.ts @@ -27,7 +27,9 @@ export const wrapCallback = ( }; export const getDevices = async (tag?: string) => { - const devices = env.HARDWARE.filter(({ tags }) => tag === undefined || tags.includes(tag)); + const devices = + env.HARDWARE.filter(({ tags }) => + tag === undefined || tags.includes(tag)); if (devices.length === 0) { const tagStatus = tag !== undefined ? ` with tag: ${tag}` : ""; @@ -52,16 +54,17 @@ const filterUndefinedResponse = (obj: { response: T | undefined; }): obj is { deviceId: string; response: T } => obj.response !== undefined; -const getServiceForProtocol = (protocol: ServiceType): TBridgeHardwareService => { - switch (protocol) { - case "node": - return NodeService; - case "pjlink": - return PJLinkService; - case "mdc": - return MDCService; - } -}; +const getServiceForProtocol = + (protocol: ServiceType): TBridgeHardwareService => { + switch (protocol) { + case "node": + return NodeService; + case "pjlink": + return PJLinkService; + case "mdc": + return MDCService; + } + }; const applyService = async ( service: TBridgeHardwareService, @@ -69,19 +72,22 @@ const applyService = async ( args: TBridgeServiceArgs, device: Device ): Promise>> => { - if ((Object.keys(service) as Array).includes(k)) { + if ((Object.keys(service) as Array) + .includes(k)) { return await assert(service[k])(device, args); } else return undefined; }; -const without = (object: T) => (...parts: Array): U => { - return (Object.keys(object) as Array).reduce((acc, key) => { - if (!parts.includes(key as any)) { - acc[key] = object[key]; - } - return acc; - }, {} as T) as unknown as U; -}; +const without = (object: T) => + (...parts: Array): U => { + return (Object.keys(object) as Array) + .reduce((acc, key) => { + if (!parts.includes(key as K)) { + acc[key] = object[key]; + } + return acc; + }, {} as T) as unknown as U; + }; export const deviceHandler = async ( k: Key, @@ -96,7 +102,8 @@ export const deviceHandler = async ( return; } - const serviceArgs: TBridgeServiceArgs = without>(args)("deviceId"); + const serviceArgs: TBridgeServiceArgs = + without>(args)("deviceId"); let response: Awaited>>; try { response = await applyService( @@ -119,42 +126,43 @@ export const deviceHandler = async ( callback(response); }; -export const multiDeviceHandler = async ( - k: Key, - args: z.infer, - cb: (response: z.infer) => void -) => { - const callback = wrapCallback(cb); - const devices = await getDevices(args.tag); - - if (is(OVEExceptionSchema, devices)) { - callback(devices); - return; - } +export const multiDeviceHandler = + async ( + k: Key, + args: z.infer, + cb: (response: z.infer) => void + ) => { + const callback = wrapCallback(cb); + const devices = await getDevices(args.tag); + + if (is(OVEExceptionSchema, devices)) { + callback(devices); + return; + } - delete args["tag"]; - const result = await Promise.all( - devices.map(device => - applyService( - getServiceForProtocol(device.type), - k, - args as TBridgeServiceArgs, - device + delete args["tag"]; + const result = await Promise.all( + devices.map(device => + applyService( + getServiceForProtocol(device.type), + k, + args as TBridgeServiceArgs, + device + ) ) - ) - ); + ); - if (isAll(z.undefined(), result)) { - callback(raise("Command not available on devices")); - return; - } + if (isAll(z.undefined(), result)) { + callback(raise("Command not available on devices")); + return; + } - const response = result - .map((x, i) => ({ - deviceId: devices[i].id, - response: x - })) - .filter(filterUndefinedResponse); + const response = result + .map((x, i) => ({ + deviceId: devices[i].id, + response: x + })) + .filter(filterUndefinedResponse); - callback(response); -}; + callback(response); + }; diff --git a/apps/ove-bridge/src/app/app.ts b/apps/ove-bridge/src/app/app.ts index 3ed1a8d9..2e02ddad 100644 --- a/apps/ove-bridge/src/app/app.ts +++ b/apps/ove-bridge/src/app/app.ts @@ -106,14 +106,15 @@ const init = (app: App, browserWindow: typeof BW, sc: Screen) => { application.on("activate", onActivate); }; -const triggerIPC: OutboundAPI = Object.entries(outboundChannels).reduce((acc, [k, channel]) => { - const key = k as keyof OutboundAPI; - acc[k] = (args: Parameters) => { - if (mainWindow === null) return; - mainWindow.webContents.send(channel, args); - }; - return acc; -}, >{}) as OutboundAPI; +const triggerIPC: OutboundAPI = Object.entries(outboundChannels) + .reduce((acc, [k, channel]) => { + const key = k as keyof OutboundAPI; + acc[k] = (args: Parameters) => { + if (mainWindow === null) return; + mainWindow.webContents.send(channel, args); + }; + return acc; + }, >{}) as OutboundAPI; export default { isDevelopmentMode, diff --git a/apps/ove-bridge/src/app/events/electron.events.ts b/apps/ove-bridge/src/app/events/electron.events.ts index 3f4a3d72..2a1ac33e 100644 --- a/apps/ove-bridge/src/app/events/electron.events.ts +++ b/apps/ove-bridge/src/app/events/electron.events.ts @@ -9,7 +9,7 @@ import { inboundChannels } from "../../ipc-routes"; import { app, ipcMain, type IpcMain } from "electron"; import { registerSocketConnectedListener, - registerSocketDisconnectListener, + registerSocketDisconnectListener } from "../api/features/bridge/sockets"; import App from "../app"; import { type InboundAPI } from "@ove/ove-types"; @@ -23,15 +23,17 @@ process.on("SIGINT", () => { schedule.gracefulShutdown().then(() => process.exit(0)); }); -const IPCService: InboundAPI = Object.entries(service).reduce((acc, [k, route]) => { - acc[k] = async (args: Parameters[0]) => { - logger.info(`Handling: ${k}`); - // @ts-ignore - const res = await route(args); - return Array.isArray(res) || typeof res === "object" ? Json.copy(res) : res; // fixes error with IPC memory allocation - }; - return acc; -}, <{[key: string]: unknown}>{}) as InboundAPI; +const IPCService: InboundAPI = Object.entries(service) + .reduce((acc, [k, route]) => { + acc[k] = async (args: Parameters[0]) => { + logger.info(`Handling: ${k}`); + // @ts-expect-error – arg spread + const res = await route(args); + return Array.isArray(res) || typeof res === "object" ? + Json.copy(res) : res; // fixes error with IPC memory allocation + }; + return acc; + }, <{ [key: string]: unknown }>{}) as InboundAPI; (Object.keys(inboundChannels) as Array).forEach(k => { ipcMain.handle(inboundChannels[k], (_event, ...args) => diff --git a/apps/ove-bridge/src/app/events/update.events.ts b/apps/ove-bridge/src/app/events/update.events.ts index 1d0c13d8..76370f79 100644 --- a/apps/ove-bridge/src/app/events/update.events.ts +++ b/apps/ove-bridge/src/app/events/update.events.ts @@ -13,7 +13,8 @@ export default () => { autoUpdater.on("update-downloaded", info => { const dialogOpts = { - type: "info" as "info" | "error" | "none" | "question" | "warning" | undefined, + type: "info" as "info" | "error" | "none" | + "question" | "warning" | undefined, buttons: ["Restart", "Later"], title: "Application Update", message: diff --git a/apps/ove-bridge/src/env.ts b/apps/ove-bridge/src/env.ts index 8b939861..cfdf59ce 100644 --- a/apps/ove-bridge/src/env.ts +++ b/apps/ove-bridge/src/env.ts @@ -76,4 +76,4 @@ const configPath = path.join(app.getPath("userData"), "ove-bridge-config.json"); export const env = setupConfig(configPath, defaultConfig, schema, staticConfig); export const logger = Logger(env.APP_NAME, env.LOG_LEVEL, env.LOGGING_SERVER); -logger.info(`Loaded configuration from ${configPath}`); \ No newline at end of file +logger.info(`Loaded configuration from ${configPath}`); diff --git a/apps/ove-bridge/src/ipc-routes.ts b/apps/ove-bridge/src/ipc-routes.ts index 02b98e36..5ed21fbe 100644 --- a/apps/ove-bridge/src/ipc-routes.ts +++ b/apps/ove-bridge/src/ipc-routes.ts @@ -25,43 +25,20 @@ export const inboundChannels: InboundAPIChannels = { }; export type InboundAPIChannels = { - [Key in keyof InboundAPI]: string + [_Key in keyof InboundAPI]: string } -// client -> server -// export type InboundAPI = { -// getAppVersion: () => Promise -// getDevicesToAuth: () => Promise -// getPublicKey: () => Promise -// registerAuth: (id: string, pin: string) => Promise -// updateEnv: (env: {bridgeName?: string, coreURL?: string, calendarURL?: string}) => Promise -// getEnv: () => Promise<{bridgeName?: string, coreURL?: string, calendarURL?: string}> -// getDevices: () => Promise -// saveDevice: (device: Device) => Promise -// deleteDevice: (deviceId: string) => Promise -// setAutoSchedule: (schedule?: AutoSchedule) => Promise -// setEcoSchedule: (schedule: CalendarEvent[]) => Promise -// clearSchedule: () => Promise -// getMode: () => Promise -// getSocketStatus: () => Promise -// getCalendar: () => Promise -// getAutoSchedule: () => Promise -// getStreams: () => Promise -// startStreams: () => Promise -// stopStreams: () => Promise -// } - export const outboundChannels: OutboundAPIChannels = { socketConnect: "socket-connect", socketDisconnect: "socket-disconnect" }; export type OutboundAPIChannels = { - [Key in keyof OutboundAPI]: string + [_Key in keyof OutboundAPI]: string } // server -> client export type OutboundAPI = { socketConnect: () => void socketDisconnect: () => void -} \ No newline at end of file +} diff --git a/apps/ove-client-ui/.eslintrc.json b/apps/ove-client-ui/.eslintrc.json index 734ddace..1e382421 100644 --- a/apps/ove-client-ui/.eslintrc.json +++ b/apps/ove-client-ui/.eslintrc.json @@ -1,17 +1,48 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "plugin:@nrwl/nx/react", + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ] + } }, { - "files": ["*.ts", "*.tsx"], + "files": [ + "*.ts", + "*.tsx" + ], "rules": {} }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/apps/ove-client-ui/src/app/app-controller.ts b/apps/ove-client-ui/src/app/app-controller.ts index 4fe3b183..fbd35cde 100644 --- a/apps/ove-client-ui/src/app/app-controller.ts +++ b/apps/ove-client-ui/src/app/app-controller.ts @@ -8,4 +8,4 @@ export const usePin = () => { }, []); return pin; -}; \ No newline at end of file +}; diff --git a/apps/ove-client-ui/src/app/app.spec.tsx b/apps/ove-client-ui/src/app/app.spec.tsx index 6bce40af..4f278396 100644 --- a/apps/ove-client-ui/src/app/app.spec.tsx +++ b/apps/ove-client-ui/src/app/app.spec.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { render } from "@testing-library/react"; import App from "./app"; diff --git a/apps/ove-client-ui/src/app/app.tsx b/apps/ove-client-ui/src/app/app.tsx index 28dbf961..d7cf004f 100644 --- a/apps/ove-client-ui/src/app/app.tsx +++ b/apps/ove-client-ui/src/app/app.tsx @@ -1,6 +1,6 @@ -import "../styles.scss"; -import styles from "./app.module.scss"; +import React from "react"; // IGNORE PATH - dependency removed at runtime +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type InboundAPI, type OutboundAPI, @@ -8,6 +8,9 @@ import { } from "../../../ove-client/src/ipc-routes"; import { usePin } from "./app-controller"; +import "../styles.scss"; +import styles from "./app.module.scss"; + declare global { // noinspection JSUnusedGlobalSymbols interface Window { diff --git a/apps/ove-client-ui/src/main.tsx b/apps/ove-client-ui/src/main.tsx index be9f3eab..f9e99519 100644 --- a/apps/ove-client-ui/src/main.tsx +++ b/apps/ove-client-ui/src/main.tsx @@ -1,4 +1,4 @@ -import { StrictMode } from "react"; +import React, { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import App from "./app/app"; diff --git a/apps/ove-core-ui-e2e/playwright.config.ts b/apps/ove-core-ui-e2e/playwright.config.ts index 4552195d..e9accff9 100644 --- a/apps/ove-core-ui-e2e/playwright.config.ts +++ b/apps/ove-core-ui-e2e/playwright.config.ts @@ -1,9 +1,9 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; +import type { PlaywrightTestConfig } from "@playwright/test"; -import { baseConfig } from '../../playwright.config.base'; +import { baseConfig } from "../../playwright.config.base"; const config: PlaywrightTestConfig = { - ...baseConfig, + ...baseConfig }; export default config; diff --git a/apps/ove-core-ui-e2e/src/app.spec.ts b/apps/ove-core-ui-e2e/src/app.spec.ts index 49889ac3..f1224058 100644 --- a/apps/ove-core-ui-e2e/src/app.spec.ts +++ b/apps/ove-core-ui-e2e/src/app.spec.ts @@ -1,7 +1,7 @@ -import { expect, test } from '@playwright/test'; +import { expect, test } from "@playwright/test"; -test('should start page', async ({ page }) => { - await page.goto('/'); +test("should start page", async ({ page }) => { + await page.goto("/"); expect(true).toBeTruthy(); }); diff --git a/apps/ove-core-ui/.eslintrc.json b/apps/ove-core-ui/.eslintrc.json index 734ddace..acdb193f 100644 --- a/apps/ove-core-ui/.eslintrc.json +++ b/apps/ove-core-ui/.eslintrc.json @@ -1,17 +1,51 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "plugin:@nrwl/nx/react", + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ] + } }, { - "files": ["*.ts", "*.tsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx" + ], + "rules": { + "require-jsdoc": "off", + "no-invalid-this": "off" + } }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/apps/ove-core-ui/postcss.config.js b/apps/ove-core-ui/postcss.config.js index c72626d6..21ee5d74 100644 --- a/apps/ove-core-ui/postcss.config.js +++ b/apps/ove-core-ui/postcss.config.js @@ -1,6 +1,7 @@ -const { join } = require('path'); +const { join } = require("path"); -// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build +// Note: If you use library-specific PostCSS/Tailwind +// configuration then you should remove the `postcssConfig` build // option from your application's configuration (i.e. project.json). // // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries @@ -8,7 +9,7 @@ const { join } = require('path'); module.exports = { plugins: { tailwindcss: { - config: join(__dirname, 'tailwind.config.js'), + config: join(__dirname, "tailwind.config.js"), }, autoprefixer: {}, }, diff --git a/apps/ove-core-ui/src/app/app.spec.tsx b/apps/ove-core-ui/src/app/app.spec.tsx index 49ccea01..4b2a2839 100644 --- a/apps/ove-core-ui/src/app/app.spec.tsx +++ b/apps/ove-core-ui/src/app/app.spec.tsx @@ -1,11 +1,11 @@ -import { render } from '@testing-library/react'; +import React from "react"; +import { render } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; -import { BrowserRouter } from 'react-router-dom'; +import App from "./app"; -import App from './app'; - -describe('App', () => { - it('should render successfully', () => { +describe("App", () => { + it("should render successfully", () => { const { baseElement } = render( @@ -14,7 +14,7 @@ describe('App', () => { expect(baseElement).toBeTruthy(); }); - it('should have a greeting as the title', () => { + it("should have a greeting as the title", () => { const { getByText } = render( diff --git a/apps/ove-core-ui/src/app/app.tsx b/apps/ove-core-ui/src/app/app.tsx index 0e731153..1dc3e608 100644 --- a/apps/ove-core-ui/src/app/app.tsx +++ b/apps/ove-core-ui/src/app/app.tsx @@ -1,3 +1,5 @@ +/* global HeadersInit */ + import { env } from "../env"; import Router from "./router"; import superjson from "superjson"; @@ -6,7 +8,7 @@ import { trpc } from "../utils/api"; import { httpLink } from "@trpc/client"; import { Nav } from "@ove/ui-components"; import { HddStack } from "react-bootstrap-icons"; -import { useEffect, useState, useMemo } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import { NavigationMenuLink, Toaster } from "@ove/ui-base-components"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; @@ -18,11 +20,14 @@ export const App = () => { title: "Hardware", item: null, card:
    + className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] + lg:grid-cols-[.75fr_1fr]">
  • @@ -39,12 +44,16 @@ export const App = () => {
  • Sockets

    + className="line-clamp-2 text-sm leading-snug + text-muted-foreground"> Socket.IO Admin UI

    @@ -71,7 +80,7 @@ export const App = () => { } ]; - const createTrpcClient = () => trpc.createClient({ + const createTrpcClient = useCallback(() => trpc.createClient({ links: [ httpLink({ url: `${env.CORE_URL}/api/v${env.CORE_API_VERSION}/trpc`, @@ -89,7 +98,8 @@ export const App = () => { if (responses.some(r => r.error.data.httpStatus === 401)) { const refreshedTokens = await refresh(); if (options?.headers !== undefined) { - options.headers["authorization" as keyof HeadersInit] = `Bearer ${refreshedTokens?.access}`; + options.headers["authorization" as keyof HeadersInit] = + `Bearer ${refreshedTokens?.access}`; } return await fetch(url, options); } @@ -98,7 +108,8 @@ export const App = () => { if (res.status === 401) { const refreshedTokens = await refresh(); if (options?.headers !== undefined) { - options.headers["authorization" as keyof HeadersInit] = `Bearer ${refreshedTokens?.access}`; + options.headers["authorization" as keyof HeadersInit] = + `Bearer ${refreshedTokens?.access}`; } return await fetch(url, options); } @@ -108,13 +119,13 @@ export const App = () => { }) ], transformer: superjson - }); + }), [refresh, tokens?.access]); const [trpcClient, setTrpcClient] = useState(createTrpcClient); useEffect(() => { setTrpcClient(createTrpcClient); - }, [loggedIn]); + }, [loggedIn, createTrpcClient]); const queryClient = useMemo(() => new QueryClient({ defaultOptions: { diff --git a/apps/ove-core-ui/src/app/router.tsx b/apps/ove-core-ui/src/app/router.tsx index af57bc1f..c6fe3f8b 100644 --- a/apps/ove-core-ui/src/app/router.tsx +++ b/apps/ove-core-ui/src/app/router.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Launcher from "../pages/launcher/page"; import Login from "../pages/login/page"; import Hardware from "../pages/hardware/page"; @@ -6,9 +7,13 @@ import ProtectedRoute from "../components/protected-route"; import Sockets from "../pages/sockets/page"; import ProjectEditorLoader from "../pages/project-editor/loader"; -type RouterProps = {loggedIn: boolean, login: (username: string, password: string) => Promise, username: string | null} +type RouterProps = { + loggedIn: boolean, + login: (username: string, password: string) => Promise, + username: string | null +} -const Router = ({loggedIn, login, username}: RouterProps) => { +const Router = ({ loggedIn, login, username }: RouterProps) => { return { /> } + element={ + } /> { /> } /> + element={ + } /> } /> + element={ + } /> ; }; diff --git a/apps/ove-core-ui/src/components/controller/controller.tsx b/apps/ove-core-ui/src/components/controller/controller.tsx index 78539c78..596d9601 100644 --- a/apps/ove-core-ui/src/components/controller/controller.tsx +++ b/apps/ove-core-ui/src/components/controller/controller.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { ArrowClockwise, FileEarmarkText, diff --git a/apps/ove-core-ui/src/components/protected-route.tsx b/apps/ove-core-ui/src/components/protected-route.tsx index d8dbcadd..ab91e0d7 100644 --- a/apps/ove-core-ui/src/components/protected-route.tsx +++ b/apps/ove-core-ui/src/components/protected-route.tsx @@ -1,15 +1,18 @@ -import { type ReactNode } from "react"; +/* global JSX */ + +import React, { type ReactNode } from "react"; import { Navigate } from "react-router-dom"; /** * Only allows navigation to a route if a condition is met. * Otherwise, it redirects to a different specified route. + * @return {ReactNode} */ const ProtectedRoute = ({ condition, redirectTo, children -}: ConditionalRouteProps): JSX.Element => condition ? <>{children} : +}: ConditionalRouteProps): JSX.Element => condition ?
    {children}
    : ; export type ConditionalRouteProps = { @@ -25,4 +28,4 @@ export type ConditionalRouteProps = { children?: ReactNode } -export default ProtectedRoute; \ No newline at end of file +export default ProtectedRoute; diff --git a/apps/ove-core-ui/src/components/s3-file-select/s3-file-select.tsx b/apps/ove-core-ui/src/components/s3-file-select/s3-file-select.tsx index cdec32bd..d8255d6a 100644 --- a/apps/ove-core-ui/src/components/s3-file-select/s3-file-select.tsx +++ b/apps/ove-core-ui/src/components/s3-file-select/s3-file-select.tsx @@ -1,6 +1,6 @@ import { type FieldValues } from "react-hook-form"; -import { type BaseSyntheticEvent, useEffect } from "react"; -import { type File } from "../../pages/project-editor/hooks"; +import React, { type BaseSyntheticEvent, useEffect } from "react"; +import { type File } from "@ove/ove-types"; import styles from "./s3-file-select.module.scss"; @@ -30,14 +30,15 @@ const S3FileSelect = ({ const name = watch("fileName"); useEffect(() => { - if (name === null || name === undefined || name === "-- select an option --") return; + if (name === null || name === undefined || + name === "-- select an option --") return; const file = fromURL(url ?? null); if (file !== null && file.name === name) { setValue("fileVersion", file.version.toString()); } else { setValue("fileVersion", getLatest(name).version.toString()); } - }, [name]); + }, [fromURL, setValue, getLatest, url, name]); return
    @@ -47,17 +48,22 @@ const S3FileSelect = ({ - {files.map(({ name }) => name).filter((name, i, arr) => arr.indexOf(name) === i).map(name => - )} + {files + .map(({ name }) => name) + .filter((name, i, arr) => arr.indexOf(name) === i) + .map(name => + )}
    ; diff --git a/apps/ove-core-ui/src/env.ts b/apps/ove-core-ui/src/env.ts index b1b41493..199630b2 100644 --- a/apps/ove-core-ui/src/env.ts +++ b/apps/ove-core-ui/src/env.ts @@ -2,18 +2,18 @@ import { z } from "zod"; import { Logger } from "@ove/ove-logging"; interface ImportMeta { - env: ImportMetaEnv + env: ImportMetaEnv; } interface ImportMetaEnv { - VITE_BASE_URL: string - VITE_CORE_URL: string - VITE_LOG_LEVEL?: string - VITE_LOGGING_SERVER?: string - VITE_VIDEO_STREAM_URL?: string - VITE_PROJECT_LAUNCHER: string - VITE_MODE: string - VITE_DISABLE_AUTH: string + VITE_BASE_URL: string; + VITE_CORE_URL: string; + VITE_LOG_LEVEL?: string; + VITE_LOGGING_SERVER?: string; + VITE_VIDEO_STREAM_URL?: string; + VITE_PROJECT_LAUNCHER: string; + VITE_MODE: string; + VITE_DISABLE_AUTH: string; } const env_ = (import.meta as unknown as ImportMeta).env; @@ -25,15 +25,21 @@ const schema = z.strictObject({ LOG_LEVEL: z.number().optional(), LOGGING_SERVER: z.string().optional(), VIDEO_STREAM_URL: z.string().optional(), - MODE: z.union([z.literal("production"), z.literal("development"), z.literal("test")]), + MODE: z.union([ + z.literal("production"), + z.literal("development"), + z.literal("test") + ]), DISABLE_AUTH: z.boolean() -}).refine(x => x.MODE === "test" || !x.DISABLE_AUTH); // only disable auth if under test + // only disable auth if under test +}).refine(x => x.MODE === "test" || !x.DISABLE_AUTH); const parsedConfig = schema.parse({ BASE_URL: env_.VITE_BASE_URL, CORE_URL: env_.VITE_CORE_URL, PROJECT_LAUNCHER: env_.VITE_PROJECT_LAUNCHER, - LOG_LEVEL: env_.VITE_LOG_LEVEL !== undefined ? parseInt(env_.VITE_LOG_LEVEL) : undefined, + LOG_LEVEL: env_.VITE_LOG_LEVEL !== undefined ? + parseInt(env_.VITE_LOG_LEVEL) : undefined, LOGGING_SERVER: env_.VITE_LOGGING_SERVER, VIDEO_STREAM_URL: env_.VITE_VIDEO_STREAM_URL, MODE: env_.VITE_MODE, @@ -50,4 +56,4 @@ export const env = { ...staticConfig } as const; -export const logger = Logger(env.APP_NAME, env.LOG_LEVEL, env.LOGGING_SERVER); \ No newline at end of file +export const logger = Logger(env.APP_NAME, env.LOG_LEVEL, env.LOGGING_SERVER); diff --git a/apps/ove-core-ui/src/hooks.ts b/apps/ove-core-ui/src/hooks.ts index 8a8f9407..a9f75028 100644 --- a/apps/ove-core-ui/src/hooks.ts +++ b/apps/ove-core-ui/src/hooks.ts @@ -11,6 +11,12 @@ export const useAuth = () => { const setTokens = useStore(state => state.setTokens); const [username, setUsername] = useState(null); + const logout = useCallback(() => { + setTokens(null); + localStorage.removeItem("tokens"); + navigate("/", { replace: true }); + }, [navigate, setTokens]); + const login = useCallback(async (username: string, password: string) => { try { const res = await createAuthClient(username, password).login.mutate(); @@ -27,13 +33,7 @@ export const useAuth = () => { logger.error(e); logout(); } - }, []); - - const logout = useCallback(() => { - setTokens(null); - localStorage.removeItem("tokens"); - navigate("/", { replace: true }); - }, []); + }, [logout, navigate, setTokens]); const refresh = useCallback(async () => { if (tokens === null) return; @@ -43,7 +43,7 @@ export const useAuth = () => { logout(); return; } - const refreshedTokens = {access: res, refresh: tokens.refresh}; + const refreshedTokens = { access: res, refresh: tokens.refresh }; localStorage.setItem("tokens", Json.stringify(refreshedTokens)); setTokens(refreshedTokens); return refreshedTokens; @@ -51,8 +51,7 @@ export const useAuth = () => { logout(); return; } - - }, [tokens]); + }, [tokens, logout, setTokens]); return { loggedIn: tokens !== null || env.DISABLE_AUTH, diff --git a/apps/ove-core-ui/src/main.tsx b/apps/ove-core-ui/src/main.tsx index 5d5dbdee..d3bcd9db 100644 --- a/apps/ove-core-ui/src/main.tsx +++ b/apps/ove-core-ui/src/main.tsx @@ -1,5 +1,5 @@ import App from "./app/app"; -import { StrictMode } from "react"; +import React, { StrictMode } from "react"; import { Helmet, HelmetProvider } from "react-helmet-async"; import * as ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; @@ -7,7 +7,7 @@ import { BrowserRouter } from "react-router-dom"; interface ImportMeta { env: { BASE_URL: string - } + }; } const root = ReactDOM.createRoot( @@ -20,7 +20,8 @@ root.render( next-ove - + diff --git a/apps/ove-core-ui/src/pages/dashboard/page.tsx b/apps/ove-core-ui/src/pages/dashboard/page.tsx index 2bced86d..36c1f7f6 100644 --- a/apps/ove-core-ui/src/pages/dashboard/page.tsx +++ b/apps/ove-core-ui/src/pages/dashboard/page.tsx @@ -1,3 +1,5 @@ -const Dashboard = () => <>; +import React from "react"; + +const Dashboard = () =>
    ; export default Dashboard; diff --git a/apps/ove-core-ui/src/pages/hardware/components/actions/actions.tsx b/apps/ove-core-ui/src/pages/hardware/components/actions/actions.tsx index 5745e399..f237ac45 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/actions/actions.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/actions/actions.tsx @@ -1,3 +1,4 @@ +import React from "react"; import NodeActions from "./node-actions"; import { type ActionController } from "../../types"; import MDCActions from "./mdc-actions"; @@ -5,11 +6,11 @@ import ProjectorActions from "./projector-actions"; const Actions = ({ device, bridgeId }: ActionController) => { if (device.type === "node") { - return + return ; } else if (device.type === "mdc") { return ; } else { - return + return ; } }; diff --git a/apps/ove-core-ui/src/pages/hardware/components/actions/mdc-actions.tsx b/apps/ove-core-ui/src/pages/hardware/components/actions/mdc-actions.tsx index b0c5f107..b26eb204 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/actions/mdc-actions.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/actions/mdc-actions.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { type ActionController } from "../../types"; import { ArrowClockwise, @@ -6,12 +7,13 @@ import { PlayCircle, StopCircle, VolumeDown, VolumeMute, VolumeUp } from "react-bootstrap-icons"; +import { useStore } from "../../../../store"; import styles from "./actions.module.scss"; -import { useStore } from "../../../../store"; const MDCActions = ({ device, bridgeId }: ActionController) => { - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); return
    ; }; -export default MultiActions; \ No newline at end of file +export default MultiActions; diff --git a/apps/ove-core-ui/src/pages/hardware/components/actions/node-actions.tsx b/apps/ove-core-ui/src/pages/hardware/components/actions/node-actions.tsx index b3e9a73b..74c81875 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/actions/node-actions.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/actions/node-actions.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { ArrowClockwise, ArrowRepeat, @@ -11,13 +12,14 @@ import { WindowPlus, WindowX } from "react-bootstrap-icons"; +import { useStore } from "../../../../store"; +import { type ActionController } from "../../types"; import styles from "./actions.module.scss"; -import { type ActionController } from "../../types"; -import { useStore } from "../../../../store"; const NodeActions = ({ device, bridgeId }: ActionController) => { - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); return
    @@ -127,4 +129,4 @@ const NodeActions = ({ device, bridgeId }: ActionController) => {
    ; }; -export default NodeActions; \ No newline at end of file +export default NodeActions; diff --git a/apps/ove-core-ui/src/pages/hardware/components/actions/projector-actions.tsx b/apps/ove-core-ui/src/pages/hardware/components/actions/projector-actions.tsx index 30e938a2..9b9b8c1f 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/actions/projector-actions.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/actions/projector-actions.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { type ActionController } from "../../types"; import { ArrowClockwise, @@ -6,12 +7,13 @@ import { PlayCircle, StopCircle, VolumeDown, VolumeMute, VolumeUp } from "react-bootstrap-icons"; +import { useStore } from "../../../../store"; import styles from "./actions.module.scss"; -import { useStore } from "../../../../store"; const ProjectorActions = ({ device, bridgeId }: ActionController) => { - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); return
    , - filterFn: (row, columnId, filterValue) => (row.getValue(columnId) as string).startsWith(filterValue) + filterFn: (row, columnId, filterValue) => + (row.getValue(columnId) as string).startsWith(filterValue) }, { accessorKey: "hostname", @@ -82,7 +86,8 @@ export const columns: ColumnDef[] = [ , filterFn: (row, columnId, filterValue) => { - return (row.getValue(columnId) as string[]).find(tag => tag.startsWith(filterValue)) !== undefined + return (row.getValue(columnId) as string[]).find(tag => + tag.startsWith(filterValue)) !== undefined; } }, { @@ -100,4 +105,4 @@ export const columns: ColumnDef[] = [ header: "Actions", cell: ({ row }) => row.getValue("actions") } -]; \ No newline at end of file +]; diff --git a/apps/ove-core-ui/src/pages/hardware/components/data-table/data-table.tsx b/apps/ove-core-ui/src/pages/hardware/components/data-table/data-table.tsx index 01819ee3..2f3cceb3 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/data-table/data-table.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/data-table/data-table.tsx @@ -9,7 +9,7 @@ import { getSortedRowModel, useReactTable } from "@tanstack/react-table"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import styles from "./data-table.module.scss"; @@ -67,29 +67,34 @@ const DataTable = ({ useEffect(() => { table.getColumn(filterType)?.setFilterValue(filter); - }, [filter]); + }, [filter, filterType, table]); return
    - {table.getHeaderGroups().map(group => - {group.headers.map(header => )} - )} + {table.getHeaderGroups().map(group => + {group.headers.map(header => )} + )} - {table.getRowModel().rows?.length ? table.getRowModel().rows.map(row => - - {row.getVisibleCells().map(cell => )} - ) : - - } + {table.getRowModel().rows?.length ? table.getRowModel().rows.map(row => + + {row.getVisibleCells().map(cell => )} + ) : + + }
    - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} -
    + {header.isPlaceholder ? null : + flexRender(header.column.columnDef.header, header.getContext())} +
    {flexRender(cell.column.columnDef.cell, cell.getContext())} -
    - No results. -
    + {flexRender(cell.column.columnDef.cell, cell.getContext())} +
    + No results. +
    @@ -103,4 +108,4 @@ const DataTable = ({
    ; }; -export default DataTable; \ No newline at end of file +export default DataTable; diff --git a/apps/ove-core-ui/src/pages/hardware/components/header/header.tsx b/apps/ove-core-ui/src/pages/hardware/components/header/header.tsx index 631a4bdc..97353933 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/header/header.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/header/header.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { useStore } from "../../../../store"; import { Calendar, CameraVideo, Power } from "react-bootstrap-icons"; @@ -7,7 +8,8 @@ const Header = ({ name, isOnline }: { name: string, isOnline: boolean }) => { - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); return

    Observatory {name} - {isOnline ? "online" : "offline"}

    @@ -37,4 +39,4 @@ const Header = ({ name, isOnline }: {
    ; }; -export default Header; \ No newline at end of file +export default Header; diff --git a/apps/ove-core-ui/src/pages/hardware/components/info/info-container.tsx b/apps/ove-core-ui/src/pages/hardware/components/info/info-container.tsx index 8fd7973f..524de960 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/info/info-container.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/info/info-container.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Info from "./info"; import { useStore } from "../../../../store"; import { type InfoTypes } from "../../../../utils"; @@ -18,7 +19,7 @@ const InfoContainer = () => { if ("length" in curInfo.data) { if (!("oveError" in curInfo.data[infoIdx])) { info = (curInfo.data[infoIdx] as { response: object }).response; - deviceId = (curInfo.data[infoIdx] as {deviceId: string}).deviceId; + deviceId = (curInfo.data[infoIdx] as { deviceId: string }).deviceId; } } else { info = curInfo.data; @@ -26,42 +27,45 @@ const InfoContainer = () => { } } - const getMaxLen = (obj: {data: object} | {data: object[]} | null) => { + const getMaxLen = (obj: { data: object } | { data: object[] } | null) => { if (obj === null) return null; if ("length" in obj.data) return obj.data.length; return null; - } + }; - return {curInfo !== null ?
    -
    -

    Info - {deviceId}

    - - -
    - {info === null ?
    ERROR
    : } -
    : null}
    ; + return + {curInfo !== null ? +
    +
    +

    Info - {deviceId}

    + + +
    + {info === null ?
    ERROR
    : } +
    : null}
    ; }; export default InfoContainer; diff --git a/apps/ove-core-ui/src/pages/hardware/components/info/info.tsx b/apps/ove-core-ui/src/pages/hardware/components/info/info.tsx index 1edea3fd..a2330919 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/info/info.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/info/info.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { Json } from "@ove/ove-utils"; import styles from "./info.module.scss"; @@ -5,21 +6,19 @@ import styles from "./info.module.scss"; const Info = ({ info }: { info: object }) =>
    - - - - - {Object.keys(info).map(k => - + + + + + {Object.keys(info).map(k => - - )} + )}
    PropertyValue
    PropertyValue
    {k}

    {Json.stringify(info[k as keyof typeof info])}

    ; -export default Info; \ No newline at end of file +export default Info; diff --git a/apps/ove-core-ui/src/pages/hardware/components/live-view/hooks.ts b/apps/ove-core-ui/src/pages/hardware/components/live-view/hooks.ts index b935ac6c..66c01d9b 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/live-view/hooks.ts +++ b/apps/ove-core-ui/src/pages/hardware/components/live-view/hooks.ts @@ -9,7 +9,7 @@ export const useStreams = (bridgeId: string, isOpen: boolean) => { useEffect(() => () => { stopStreams.mutateAsync({ bridgeId }).catch(logger.error); - }, []); + }, [bridgeId, stopStreams]); useEffect(() => { if (isOpen) { @@ -17,7 +17,7 @@ export const useStreams = (bridgeId: string, isOpen: boolean) => { } else { stopStreams.mutateAsync({ bridgeId }).catch(logger.error); } - }, [isOpen]); + }, [isOpen, bridgeId, startStreams, stopStreams]); return streams; -}; \ No newline at end of file +}; diff --git a/apps/ove-core-ui/src/pages/hardware/components/live-view/live-view.tsx b/apps/ove-core-ui/src/pages/hardware/components/live-view/live-view.tsx index cbbb4a88..e29026a3 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/live-view/live-view.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/live-view/live-view.tsx @@ -1,8 +1,14 @@ +import React from "react"; import { useStreams } from "./hooks"; import { isError } from "@ove/ove-types"; import { VideoStreams } from "@ove/ui-components"; -const LiveView = ({ bridgeId, isOpen }: { bridgeId: string, isOpen: boolean }) => { +type LiveViewProps = { + bridgeId: string + isOpen: boolean +} + +const LiveView = ({ bridgeId, isOpen }: LiveViewProps) => { const streams = useStreams(bridgeId, isOpen); return streams.status === "success" && !isError(streams.data.response) ? diff --git a/apps/ove-core-ui/src/pages/hardware/components/observatory/observatory.tsx b/apps/ove-core-ui/src/pages/hardware/components/observatory/observatory.tsx index 4acf49fd..27027d14 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/observatory/observatory.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/observatory/observatory.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Header from "../header/header"; import Actions from "../actions/actions"; import Toolbar from "../toolbar/toolbar"; @@ -37,7 +38,8 @@ const Observatory = ({ name, isOnline, showNotification }: { } = useHardware(isOnline, name); const [filter, setFilter] = useState(""); const [filterType, setFilterType] = useState<"id" | "tags">("id"); - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); useSingleController(name, showNotification, updateStatus); useMultiController(name, filter, showNotification, updateStatusAll); @@ -50,7 +52,7 @@ const Observatory = ({ name, isOnline, showNotification }: { deviceId: filter === "" ? null : filter, pending: false }); - }, [filter]); + }, [filter, filterType, name, setDeviceAction]); return
    diff --git a/apps/ove-core-ui/src/pages/hardware/components/paginated-dialog/paginated-dialog.tsx b/apps/ove-core-ui/src/pages/hardware/components/paginated-dialog/paginated-dialog.tsx index 9d6886c8..bfc53856 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/paginated-dialog/paginated-dialog.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/paginated-dialog/paginated-dialog.tsx @@ -1,3 +1,5 @@ +/* global JSX */ +import React from "react"; import { assert } from "@ove/ove-utils"; import { useStore } from "../../../../store"; @@ -25,7 +27,9 @@ const PaginatedDialog = ({ onClick={() => setIdx(idx === 0 ? 0 : idx - 1)}>Previous
    : null} diff --git a/apps/ove-core-ui/src/pages/hardware/components/popups/popups.tsx b/apps/ove-core-ui/src/pages/hardware/components/popups/popups.tsx index c02ad4f0..2c81c068 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/popups/popups.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/popups/popups.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Console from "../console/console"; import { useStore } from "../../../../store"; import LiveView from "../live-view/live-view"; @@ -11,23 +12,40 @@ import Volume from "../volume/volume"; import Calendar from "../calendar/calendar"; import PowerMode from "../power-mode/power-mode"; -const Popups = ({ isOpen }: {isOpen: boolean}) => { +const Popups = ({ isOpen }: { isOpen: boolean }) => { const deviceAction = useStore(state => state.hardwareConfig.deviceAction); - if (deviceAction.bridgeId === null) return <>; + if (deviceAction.bridgeId === null) return null; switch (deviceAction.action) { - case null: return <>; - case "monitoring": return ; - case "calendar": return ; - case "power_mode": return ; - case "info": return ; - case "execute": return ; - case "screenshot": return deviceAction.pending ? : ; - case "browser": return deviceAction.pending ? : ; - case "browser_open": return ; - case "browser_close": return ; - case "input_change": return
    Input Change
    ; // TODO - implement - case "volume": return deviceAction.pending ? : <>; - default: return
    ERROR
    ; + case null: + return null; + case "monitoring": + return ; + case "calendar": + return ; + case "power_mode": + return ; + case "info": + return ; + case "execute": + return ; + case "screenshot": + return deviceAction.pending ? + : ; + case "browser": + return deviceAction.pending ? + : ; + case "browser_open": + return ; + case "browser_close": + return ; + case "input_change": + return
    Input Change
    ; // TODO - implement + case "volume": + return deviceAction.pending ? : null; + default: + return
    ERROR
    ; } }; diff --git a/apps/ove-core-ui/src/pages/hardware/components/power-mode/power-mode.tsx b/apps/ove-core-ui/src/pages/hardware/components/power-mode/power-mode.tsx index 9828d5fe..58c611cd 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/power-mode/power-mode.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/power-mode/power-mode.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import React, { useEffect } from "react"; import { trpc } from "../../../../utils/api"; import { useStore } from "../../../../store"; import { type CalendarEvent } from "@ove/ove-types"; @@ -10,9 +10,14 @@ const useFetchPowerMode = (bridgeId: string, calendar: CalendarEvent[]) => { const setEco = trpc.bridge.setEcoSchedule.useMutation(); return { - setManualSchedule: async () => (await setManual.mutateAsync({ bridgeId })).response, - setAutoSchedule: async () => (await setAuto.mutateAsync({bridgeId})).response, - setEcoSchedule: async () => (await setEco.mutateAsync({bridgeId, ecoSchedule: calendar ?? []})).response + setManualSchedule: async () => + (await setManual.mutateAsync({ bridgeId })).response, + setAutoSchedule: async () => + (await setAuto.mutateAsync({ bridgeId })).response, + setEcoSchedule: async () => (await setEco.mutateAsync({ + bridgeId, + ecoSchedule: calendar ?? [] + })).response }; }; @@ -21,15 +26,20 @@ const PowerMode = () => { const calendar = useStore(state => state.hardwareConfig.calendar); const mode = useStore(state => state.hardwareConfig.mode); const setMode = useStore(state => state.hardwareConfig.setMode); - const controller = useFetchPowerMode(deviceAction.bridgeId ?? "", calendar ?? []); - const getMode = trpc.bridge.getMode.useQuery({bridgeId: deviceAction.bridgeId ?? ""}); + const controller = useFetchPowerMode(deviceAction.bridgeId ?? + "", calendar ?? []); + const getMode = trpc.bridge.getMode + .useQuery({ bridgeId: deviceAction.bridgeId ?? "" }); useEffect(() => { - if (getMode.status !== "success" || typeof getMode.data.response !== "string") return; + if (getMode.status !== "success" || + typeof getMode.data.response !== "string") return; setMode(getMode.data.response); - }, [getMode.status, getMode.isRefetching]); + }, [getMode.status, getMode.isRefetching, + getMode.data?.response, setMode]); - return + return ; }; export default PowerMode; diff --git a/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-display.tsx b/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-display.tsx index 35824d36..1962ce64 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-display.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-display.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { assert } from "@ove/ove-utils"; import { useStore } from "../../../../store"; import PaginatedDialog from "../paginated-dialog/paginated-dialog"; @@ -8,7 +9,8 @@ const ScreenshotDisplay = () => { const paginationIdx = useStore(state => state.hardwareConfig.paginationIdx); const deviceAction = useStore(state => state.hardwareConfig.deviceAction); const curScreenshots = useStore(state => state.hardwareConfig.screenshots); - const screenshotConfig = useStore(state => state.hardwareConfig.screenshotConfig); + const screenshotConfig = useStore(state => + state.hardwareConfig.screenshotConfig); let deviceId: string | null = null; let screenshots: string[] | null = null; @@ -30,17 +32,18 @@ const ScreenshotDisplay = () => { } } - return state !== "no_data" ? -
    -

    Info - {deviceId}

    -
      - {assert(screenshots).map((screenshot, i) =>
    • {screenshotConfig?.method === "response" ? {`Screenshot : screenshot}
    • )} -
    -
    -
    : null; + return state !== "no_data" ? + +
    +

    Info - {deviceId}

    +
      + {assert(screenshots).map((screenshot, i) =>
    • {screenshotConfig?.method === "response" ? {`Screenshot : screenshot}
    • )} +
    +
    +
    : null; }; export default ScreenshotDisplay; diff --git a/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-input.tsx b/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-input.tsx index 9e5446de..b737fa12 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-input.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/screenshot/screenshot-input.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import React, { useState } from "react"; import { useForm } from "react-hook-form"; import { useStore } from "../../../../store"; import { type ScreenshotMethod } from "@ove/ove-types"; @@ -13,8 +13,10 @@ type Form = { const ScreenshotInput = () => { const [screens, setScreens] = useState([]); const { register, handleSubmit, setValue } = useForm(); - const setScreenshotConfig = useStore(state => state.hardwareConfig.setScreenshotConfig); - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); + const setScreenshotConfig = useStore(state => + state.hardwareConfig.setScreenshotConfig); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); const deviceAction = useStore(state => state.hardwareConfig.deviceAction); const onSubmit = ({ method, screen }: Form) => { @@ -22,8 +24,11 @@ const ScreenshotInput = () => { setScreens(cur => cur.includes(screen) ? cur : [...cur, screen]); setValue("screen", ""); } else { - setScreenshotConfig({method, screens: screens.map(screen => parseInt(screen))}); - setDeviceAction({...deviceAction, pending: false}); + setScreenshotConfig({ + method, + screens: screens.map(screen => parseInt(screen)) + }); + setDeviceAction({ ...deviceAction, pending: false }); } }; diff --git a/apps/ove-core-ui/src/pages/hardware/components/search-select/search-select.tsx b/apps/ove-core-ui/src/pages/hardware/components/search-select/search-select.tsx index 98fe45a7..e170f7c1 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/search-select/search-select.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/search-select/search-select.tsx @@ -1,5 +1,5 @@ import { useForm } from "react-hook-form"; -import { useCallback, useEffect, useRef } from "react"; +import React, { useCallback, useEffect, useRef } from "react"; import styles from "./search-select.module.scss"; @@ -22,7 +22,7 @@ const SearchSelect = ({ values, setFilter, filter }: SearchSelectProps) => { useEffect(() => { const data = watch(() => handleSubmit(onSubmit)()); return () => data.unsubscribe(); - }, []); + }, [handleSubmit, onSubmit, watch]); return diff --git a/apps/ove-core-ui/src/pages/hardware/components/toolbar/toolbar.tsx b/apps/ove-core-ui/src/pages/hardware/components/toolbar/toolbar.tsx index cc71014e..fcade65a 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/toolbar/toolbar.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/toolbar/toolbar.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { type HardwareInfo } from "../../types"; import MultiActions from "../actions/multi-actions"; import SearchSelect from "../search-select/search-select"; diff --git a/apps/ove-core-ui/src/pages/hardware/components/value-modal/value-modal.tsx b/apps/ove-core-ui/src/pages/hardware/components/value-modal/value-modal.tsx index b2d87b31..91ad88c5 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/value-modal/value-modal.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/value-modal/value-modal.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { FieldValues, Path, useForm } from "react-hook-form"; import styles from "./value-modal.module.scss"; @@ -9,7 +10,12 @@ type ValueModalProps = { onSubmit: (form: T) => void } -const ValueModal = ({k, label, header, onSubmit}: ValueModalProps) => { +const ValueModal = ({ + k, + label, + header, + onSubmit +}: ValueModalProps) => { const { handleSubmit, register } = useForm(); return
    diff --git a/apps/ove-core-ui/src/pages/hardware/components/volume/volume.tsx b/apps/ove-core-ui/src/pages/hardware/components/volume/volume.tsx index 85a5e76c..da8bc143 100644 --- a/apps/ove-core-ui/src/pages/hardware/components/volume/volume.tsx +++ b/apps/ove-core-ui/src/pages/hardware/components/volume/volume.tsx @@ -1,9 +1,12 @@ +import React from "react"; import ValueModal from "../value-modal/value-modal"; import { useStore } from "../../../../store"; const Volume = () => { - const setDeviceAction = useStore(state => state.hardwareConfig.setDeviceAction); - const deviceAction = useStore(state => state.hardwareConfig.deviceAction); + const setDeviceAction = useStore(state => + state.hardwareConfig.setDeviceAction); + const deviceAction = useStore(state => + state.hardwareConfig.deviceAction); const setVolume = useStore(state => state.hardwareConfig.setVolume); const onSubmit = ({ volume }: { volume: string }) => { diff --git a/apps/ove-core-ui/src/pages/hardware/hooks.ts b/apps/ove-core-ui/src/pages/hardware/hooks.ts index 8ef09651..077bc5de 100644 --- a/apps/ove-core-ui/src/pages/hardware/hooks.ts +++ b/apps/ove-core-ui/src/pages/hardware/hooks.ts @@ -6,7 +6,8 @@ import { type DeviceStatus, type HardwareInfo } from "./types"; export const useHardware = (isOnline: boolean, bridgeId: string) => { const [hardware, setHardware] = useState([]); - const getHardware = trpc.bridge.getDevices.useQuery({ bridgeId }, { enabled: isOnline }); + const getHardware = trpc.bridge.getDevices + .useQuery({ bridgeId }, { enabled: isOnline }); useEffect(() => { if (!isOnline) return; @@ -16,7 +17,7 @@ export const useHardware = (isOnline: boolean, bridgeId: string) => { device, status: null }))); - }, [getHardware.status]); + }, [getHardware.status, getHardware.data.response, isOnline]); const updateStatus = (deviceId: string, status: "off" | "running" | null) => { setHardware(cur => { @@ -51,4 +52,4 @@ export const useHardware = (isOnline: boolean, bridgeId: string) => { }; return { hardware, updateStatus, updateStatusAll }; -}; \ No newline at end of file +}; diff --git a/apps/ove-core-ui/src/pages/hardware/page.tsx b/apps/ove-core-ui/src/pages/hardware/page.tsx index c806fca1..cff44398 100644 --- a/apps/ove-core-ui/src/pages/hardware/page.tsx +++ b/apps/ove-core-ui/src/pages/hardware/page.tsx @@ -4,7 +4,7 @@ import { type DeviceAction } from "./types"; import Popups from "./components/popups/popups"; import { Helmet, HelmetProvider } from "react-helmet-async"; import Observatory from "./components/observatory/observatory"; -import { type CSSProperties, useCallback, useEffect } from "react"; +import React, { type CSSProperties, useCallback, useEffect } from "react"; import { Dialog, Snackbar, useDialog, useSnackbar } from "@ove/ui-components"; import styles from "./page.module.scss"; @@ -15,25 +15,33 @@ const Hardware = () => { const { ref, closeDialog, openDialog, isOpen } = useDialog(); const deviceAction = useStore(state => state.hardwareConfig.deviceAction); const reset = useStore(state => state.hardwareConfig.reset); - const setPaginationIdx = useStore(state => state.hardwareConfig.setPaginationIdx); + const setPaginationIdx = useStore(state => + state.hardwareConfig.setPaginationIdx); - const isSpecial = (action: DeviceAction["action"]) => action === "info" || action === "execute" || action === "screenshot" || action === "monitoring" || action === "calendar" || action === "power_mode"; - const getStyle = useCallback((deviceAction: DeviceAction): CSSProperties | undefined => { - switch (deviceAction.action) { - case "monitoring": return {width: "90vw", height: "90vh"}; - case "volume": - case "browser_open": - case "browser": - case "browser_close": return {width: "25vw", height: "20vh"}; - case "calendar": return {width: "65vw", height: "80svh"}; - default: return undefined; - } - }, []); + const isSpecial = (action: DeviceAction["action"]) => + action === "info" || action === "execute" || action === "screenshot" || + action === "monitoring" || action === "calendar" || action === "power_mode"; + const getStyle = useCallback( + (deviceAction: DeviceAction): CSSProperties | undefined => { + switch (deviceAction.action) { + case "monitoring": + return { width: "90vw", height: "90vh" }; + case "volume": + case "browser_open": + case "browser": + case "browser_close": + return { width: "25vw", height: "20vh" }; + case "calendar": + return { width: "65vw", height: "80svh" }; + default: + return undefined; + } + }, []); useEffect(() => { if (isOpen) return; reset(); - }, [isOpen]); + }, [isOpen, reset]); useEffect(() => { setPaginationIdx(0); @@ -43,7 +51,7 @@ const Hardware = () => { } else { openDialog(); } - }, [deviceAction]); + }, [deviceAction, closeDialog, openDialog, setPaginationIdx]); return
    @@ -51,12 +59,11 @@ const Hardware = () => { next-ove - Hardware

    Hardware Manager

    - {getObservatories.status === "success" && !("oveError" in getObservatories.data) ? getObservatories.data?.map(({ - name, - isOnline - }) => - ) : null} + {getObservatories.status === "success" && + !("oveError" in getObservatories.data) ? + getObservatories.data?.map(({ name, isOnline }) => + ) : null} @@ -66,4 +73,4 @@ const Hardware = () => { ; }; -export default Hardware; \ No newline at end of file +export default Hardware; diff --git a/apps/ove-core-ui/src/pages/hardware/types.ts b/apps/ove-core-ui/src/pages/hardware/types.ts index f0402ef4..7229001c 100644 --- a/apps/ove-core-ui/src/pages/hardware/types.ts +++ b/apps/ove-core-ui/src/pages/hardware/types.ts @@ -10,8 +10,35 @@ export type ActionController = { bridgeId: string } -export type Action = "status" | "info" | "start" | "shutdown" | "reboot" | "execute" | "screenshot" | "input_change" | "browser" | "browser_open" | "browser_close" | "browsers_close" | "volume" | "mute" | "unmute" | "audio_mute" | "audio_unmute" | "video_mute" | "video_unmute" | "monitoring" | "calendar" | "power_mode"; +export type Action = + "status" + | "info" + | "start" + | "shutdown" + | "reboot" + | "execute" + | "screenshot" + | "input_change" + | "browser" + | "browser_open" + | "browser_close" + | "browsers_close" + | "volume" + | "mute" + | "unmute" + | "audio_mute" + | "audio_unmute" + | "video_mute" + | "video_unmute" + | "monitoring" + | "calendar" + | "power_mode"; -export type DeviceAction = { bridgeId: string | null, deviceId: string | null, action: Action | null, pending: boolean }; +export type DeviceAction = { + bridgeId: string | null, + deviceId: string | null, + action: Action | null, + pending: boolean +}; -export type DeviceStatus = "running" | "off" | null; \ No newline at end of file +export type DeviceStatus = "running" | "off" | null; diff --git a/apps/ove-core-ui/src/pages/launcher/page.tsx b/apps/ove-core-ui/src/pages/launcher/page.tsx index 854d54a2..3cf6eeba 100644 --- a/apps/ove-core-ui/src/pages/launcher/page.tsx +++ b/apps/ove-core-ui/src/pages/launcher/page.tsx @@ -1,22 +1,27 @@ import { env } from "../../env"; -import { useState } from "react"; +import React, { useState } from "react"; import { ToggleLeft, ToggleRight } from "lucide-react"; import styles from "./launcher.module.scss"; import Projects from "./projects"; -const Launcher = ({loggedIn}: {loggedIn: boolean}) => { +const Launcher = ({ loggedIn }: { loggedIn: boolean }) => { const [mode, setMode] = useState<"legacy" | "modern">("legacy"); return
    - {mode === "legacy" ? - - :
    {loggedIn ? : <>}
    } + : +
    {loggedIn ? : null}
    }
    ; }; diff --git a/apps/ove-core-ui/src/pages/launcher/project-card.tsx b/apps/ove-core-ui/src/pages/launcher/project-card.tsx index 3d160683..b4444568 100644 --- a/apps/ove-core-ui/src/pages/launcher/project-card.tsx +++ b/apps/ove-core-ui/src/pages/launcher/project-card.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { type Project } from "@prisma/client"; import { useNavigate } from "react-router-dom"; import { Button } from "@ove/ui-base-components"; diff --git a/apps/ove-core-ui/src/pages/launcher/projects.tsx b/apps/ove-core-ui/src/pages/launcher/projects.tsx index c6918c45..75f9fff4 100644 --- a/apps/ove-core-ui/src/pages/launcher/projects.tsx +++ b/apps/ove-core-ui/src/pages/launcher/projects.tsx @@ -1,10 +1,11 @@ +import React from "react"; import { trpc } from "../../utils/api"; import { isError } from "@ove/ove-types"; import ProjectCard from "./project-card"; import styles from "./launcher.module.scss"; import { Dialog, useDialog } from "@ove/ui-components"; import Controller from "../../components/controller/controller"; -import {Plus} from "react-bootstrap-icons"; +import { Plus } from "react-bootstrap-icons"; const Projects = () => { const { ref, closeDialog, isOpen, openDialog } = useDialog(); @@ -26,9 +27,9 @@ const Projects = () => { }} hiddenStyle={{ padding: 0 }}>
    - {isOpen ?
    : <>} + {isOpen ?
    : null} - : <>; + : null; }; export default Projects; diff --git a/apps/ove-core-ui/src/pages/login/page.tsx b/apps/ove-core-ui/src/pages/login/page.tsx index 1621f0fe..7b7ff4e2 100644 --- a/apps/ove-core-ui/src/pages/login/page.tsx +++ b/apps/ove-core-ui/src/pages/login/page.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { useForm } from "react-hook-form"; import styles from "./page.module.scss"; diff --git a/apps/ove-core-ui/src/pages/project-editor/actions/actions.tsx b/apps/ove-core-ui/src/pages/project-editor/actions/actions.tsx index 8a79646f..e937c0f8 100644 --- a/apps/ove-core-ui/src/pages/project-editor/actions/actions.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/actions/actions.tsx @@ -7,13 +7,13 @@ import { Upload } from "react-bootstrap-icons"; import { toast } from "sonner"; -import { type ReactNode } from "react"; -import { type Actions } from "../hooks"; +import React, { type ReactNode } from "react"; +import { type Actions as TActions } from "../hooks"; import styles from "./actions.module.scss"; type ActionsProps = { - setAction: (action: Actions | null) => void + setAction: (action: TActions | null) => void save: () => void } @@ -21,7 +21,7 @@ type Icon = { icon: ReactNode, color: `#${string}`, title: string, - action: Actions | null + action: TActions | null } const Actions = ({ setAction, save }: ActionsProps) => { @@ -58,7 +58,7 @@ const Actions = ({ setAction, save }: ActionsProps) => { action: null }]; - const handler = (action: Actions | null) => { + const handler = (action: TActions | null) => { if (action === null) { save(); toast("Project saved successfully!"); diff --git a/apps/ove-core-ui/src/pages/project-editor/canvas/canvas.tsx b/apps/ove-core-ui/src/pages/project-editor/canvas/canvas.tsx index 2907f8a6..7b398b27 100644 --- a/apps/ove-core-ui/src/pages/project-editor/canvas/canvas.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/canvas/canvas.tsx @@ -1,14 +1,20 @@ import * as d3 from "d3"; import { type Geometry } from "../types"; import { type Section } from "@prisma/client"; -import { type MutableRefObject, useRef } from "react"; +import React, { type MutableRefObject, useRef } from "react"; import styles from "./canvas.module.scss"; import { dataTypes } from "../utils"; type PreviewProps = { sections: Section[] - space: { width: number, height: number, rows: number, columns: number, cells: Geometry[] } + space: { + width: number, + height: number, + rows: number, + columns: number, + cells: Geometry[] + } container: { width: number, height: number } dragSection: (id: string, x: number, y: number) => void select: (id: string) => void @@ -24,12 +30,16 @@ function drawSpaces({ sections, dragSection, select, - selected, + selected }: PreviewProps, svg_: MutableRefObject) { - const x = d3.scaleLinear().range([0, container.width]).domain([0, space.width]); - const inverseX = d3.scaleLinear().range([0, space.width]).domain([0, container.width]); - const y = d3.scaleLinear().range([0, container.height]).domain([0, space.height]); - const inverseY = d3.scaleLinear().range([0, space.height]).domain([0, container.height]); + const x = d3.scaleLinear() + .range([0, container.width]).domain([0, space.width]); + const inverseX = d3.scaleLinear() + .range([0, space.width]).domain([0, container.width]); + const y = d3.scaleLinear() + .range([0, container.height]).domain([0, space.height]); + const inverseY = d3.scaleLinear() + .range([0, space.height]).domain([0, container.height]); const svg = d3.select(svg_.current) .attr("width", () => container.width) @@ -53,16 +63,21 @@ function drawSpaces({ .data(() => sections) .enter() .append("rect") - .call(d3.drag().on("start", dragStart).on("drag", dragging).on("end", dragEnd) as any) + .call(d3.drag().on("start", dragStart) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .on("drag", dragging).on("end", dragEnd) as any) .attr("x", d => x(d.x)) .attr("y", d => y(d.y)) .attr("width", d => x(d.width)) .attr("height", d => y(d.height)) .attr("id", d => `section-${d.id}`) - .style("fill", d => dataTypes.find(({name}) => name === d.dataType.toLowerCase())!.color) + .style("fill", d => + dataTypes.find(({ name }) => name === + d.dataType.toLowerCase())!.color) .classed(styles.section, true) .append("title") - .text(d => `Section No.: ${d.ordering}\nAsset URL: ${d.asset}`); + .text(d => + `Section No.: ${d.ordering}\nAsset URL: ${d.asset}`); function dragStart(this: Element) { const section = d3.select(this); @@ -72,8 +87,12 @@ function drawSpaces({ const clampX = (x: number, w: number) => { for (const cell of space.cells) { - if (Math.abs(cell.x - x) < ((space.width / space.columns) * THRESHOLDX)) return cell.x; - if (Math.abs((cell.x + cell.width) - (x + w)) < ((space.width / space.columns) * THRESHOLDX)) return (cell.x + cell.width) - w; + if (Math.abs(cell.x - x) < ((space.width / space.columns) * + THRESHOLDX)) return cell.x; + if (Math.abs((cell.x + cell.width) - (x + w)) < + ((space.width / space.columns) * THRESHOLDX)) { + return (cell.x + cell.width) - w; + } } return x; @@ -81,8 +100,12 @@ function drawSpaces({ const clampY = (y: number, h: number) => { for (const cell of space.cells) { - if (Math.abs(cell.y - y) < ((space.height / space.rows) * THRESHOLDY)) return cell.y; - if (Math.abs((cell.y + cell.height) - (y + h)) < ((space.height / space.rows) * THRESHOLDY)) return (cell.y + cell.height) - h; + if (Math.abs(cell.y - y) < ((space.height / space.rows) * + THRESHOLDY)) return cell.y; + if (Math.abs((cell.y + cell.height) - (y + h)) < + ((space.height / space.rows) * THRESHOLDY)) { + return (cell.y + cell.height) - h; + } } return y; @@ -94,27 +117,44 @@ function drawSpaces({ subject: { x: number, y: number } }) { const section = d3.select(this); - const label = d3.select(`#label-${section.attr("id").slice(8)}`); + const label = + d3.select(`#label-${section.attr("id").slice(8)}`); - const nx = Math.max(0, Math.min(x(space.width) - parseFloat(section.attr("width")), x(event.subject.x) + (event.x - event.subject.x))); - const ny = Math.max(0, Math.min(y(space.height) - parseFloat(section.attr("height")), y(event.subject.y) + (event.y - event.subject.y))); + const nx = Math.max(0, Math.min(x(space.width) - + parseFloat(section.attr("width")), x(event.subject.x) + + (event.x - event.subject.x))); + const ny = Math.max(0, Math.min(y(space.height) - + parseFloat(section.attr("height")), y(event.subject.y) + + (event.y - event.subject.y))); section - .attr("x", x(clampX(inverseX(nx), inverseX(parseFloat(section.attr("width")))))) - .attr("y", y(clampY(inverseY(ny), inverseY(parseFloat(section.attr("height")))))); + .attr("x", x(clampX(inverseX(nx), + inverseX(parseFloat(section.attr("width")))))) + .attr("y", y(clampY(inverseY(ny), + inverseY(parseFloat(section.attr("height")))))); label - .attr("x", ((+nx) + (+section.attr("width")) / 2) - (selected === section.attr("id").slice(8) ? sectionTextSize * 2 : sectionTextSize) * 0.25) - .attr("y", ((+ny) + (+section.attr("height")) / 2) + (selected === section.attr("id").slice(8) ? sectionTextSize * 2 : sectionTextSize) * 0.5); + .attr("x", ((+nx) + (+section.attr("width")) / 2) - + (selected === section.attr("id").slice(8) ? + sectionTextSize * 2 : sectionTextSize) * 0.25) + .attr("y", ((+ny) + (+section.attr("height")) / 2) + + (selected === section.attr("id").slice(8) ? + sectionTextSize * 2 : sectionTextSize) * 0.5); } function dragEnd(this: Element) { const section = d3.select(this); section.style("stroke", "black"); - dragSection(section.attr("id").slice(8), inverseX(parseFloat(section.attr("x"))) / space.width, inverseY(parseFloat(section.attr("y"))) / space.height); + dragSection( + section.attr("id").slice(8), + inverseX(parseFloat(section.attr("x"))) / space.width, + inverseY(parseFloat(section.attr("y"))) / space.height + ); } - const minSectionHeight = d3.min(sections.map(d => x(d.height)))!; - const minSectionWidth = d3.min(sections.map(d => x(d.width)))!; + const minSectionHeight = d3.min(sections.map(d => + x(d.height)))!; + const minSectionWidth = d3.min(sections.map(d => + x(d.width)))!; const sectionTextSize = Math.min(minSectionHeight, minSectionWidth) / 4; svg.selectAll(".section-label") .data(() => sections) @@ -122,9 +162,14 @@ function drawSpaces({ .append("text") .text(d => d.ordering) .attr("id", d => `label-${d.id}`) - .attr("x", d => x((+d.x) + (+d.width) / 2) - (d.id === selected ? sectionTextSize * 2 : sectionTextSize) * 0.25) - .attr("y", d => y((+d.y) + (+d.height) / 2) + (d.id === selected ? sectionTextSize * 2 : sectionTextSize) * 0.5) - .style("font-size", d => `${d.id === selected ? sectionTextSize * 2 : sectionTextSize}px`) + .attr("x", d => + x((+d.x) + (+d.width) / 2) - (d.id === selected ? + sectionTextSize * 2 : sectionTextSize) * 0.25) + .attr("y", d => + y((+d.y) + (+d.height) / 2) + (d.id === selected ? + sectionTextSize * 2 : sectionTextSize) * 0.5) + .style("font-size", d => + `${d.id === selected ? sectionTextSize * 2 : sectionTextSize}px`) .style("font-weight", d => d.id === selected ? 700 : 400) .classed(styles.label, true); } diff --git a/apps/ove-core-ui/src/pages/project-editor/canvas/resize-container.tsx b/apps/ove-core-ui/src/pages/project-editor/canvas/resize-container.tsx index 06a27b24..ffdaa511 100644 --- a/apps/ove-core-ui/src/pages/project-editor/canvas/resize-container.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/canvas/resize-container.tsx @@ -1,4 +1,4 @@ -import { type MutableRefObject, type ReactNode, useEffect } from "react"; +import React, { type MutableRefObject, type ReactNode, useEffect } from "react"; import styles from "./resize-container.module.scss"; @@ -30,7 +30,7 @@ const ResizeContainer = ({ if (container.ref.current === null) return; observer.unobserve(container.ref.current); }; - }, [container.update, container.ref.current]); + }, [container, container.update, useContentRect]); return
    { useEffect(() => () => { if (dataRef.current === config) return; setConfig(dataRef.current === "" ? "{}" : dataRef.current); - }, []); + }, [config, setConfig]); return { if (dataRef.current === initial) return; update(dataRef.current); }; - }, []); + }, [env, getData, initial, update]); return { const [data, setData] = useState(file?.data ?? ""); const [name, setName] = useState(file?.name ?? ""); - const [language, setLanguage] = useState(file?.language ?? "markdown"); + const [language, setLanguage] = + useState(file?.language ?? "markdown"); - const languageToMode = (language: Language) => language === "csv" ? "text" : language; + const languageToMode = (language: Language) => + language === "csv" ? "text" : language; useEffect(() => { if (data === file?.data && language === file?.language) return; setData(""); - }, [language]); + }, [language, data, file?.data, file?.language]); const addFileExtension = (name: string) => { - const extension = language === "latex" ? ".tex" : (language === "markdown" ? ".md" : `.${language}`); + const extension = language === "latex" ? ".tex" : + (language === "markdown" ? ".md" : `.${language}`); return `${name}${extension}`; }; @@ -80,7 +83,8 @@ const Editor = ({ close, file, save }: EditorProps) => {
    - {files.filter(file => file.name === name).map(({ version }) => version).map(version => + {files.filter(file => + file.name === name).map(({ version }) => version).map(version => )} @@ -67,7 +68,9 @@ const Upload = ({ onClick={() => fileRef?.current?.click()}>Browse... + className={styles.selected}> + {file?.[0]?.name ?? "No file chosen"} +
    diff --git a/apps/ove-core-ui/src/pages/project-editor/hooks.ts b/apps/ove-core-ui/src/pages/project-editor/hooks.ts index 7b466a14..6d0270e9 100644 --- a/apps/ove-core-ui/src/pages/project-editor/hooks.ts +++ b/apps/ove-core-ui/src/pages/project-editor/hooks.ts @@ -11,7 +11,7 @@ import { type Rect, type Space } from "./types"; import { type ProjectMetadata } from "./metadata/metadata"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { trpc } from "../../utils/api"; -import { isError } from "@ove/ove-types"; +import { isError, type File } from "@ove/ove-types"; export const useContainer = (space: Space) => { const [width, setWidth] = useState(100); @@ -21,12 +21,15 @@ export const useContainer = (space: Space) => { const update = useCallback((contentRect?: Rect) => { if (ref.current === null) return; const contentRect_ = contentRect ?? ref.current.getBoundingClientRect(); - const multiple = Math.min(contentRect_.width / space.width, contentRect_.height / space.height); + const multiple = Math.min( + contentRect_.width / space.width, + contentRect_.height / space.height + ); setWidth(multiple * space.width); setHeight(multiple * space.height); - }, [space.width, space.height, ref.current]); + }, [space.width, space.height]); - useEffect(update, [space.width, space.height]); + useEffect(update, [space.width, space.height, update]); return { width, height, ref, update }; }; @@ -44,23 +47,28 @@ export const useSpace = () => { setRows(space.rows || 4); }; - const cells = Array.from({ length: rows }, (_x, row) => Array.from({ length: columns }, (_y, col) => ({ - x: (width / columns) * col, - y: (height / rows) * row, - width: width / columns, - height: height / rows - }))).flat(); + const cells = Array.from( + { length: rows }, + (_x, row) => + Array.from({ length: columns }, (_y, col) => ({ + x: (width / columns) * col, + y: (height / rows) * row, + width: width / columns, + height: height / rows + }))).flat(); return { rows, columns, width, height, update, cells }; }; -const order = (sections: Section[]) => [...sections.sort((a, b) => a.ordering - b.ordering)]; +const order = (sections: Section[]) => + [...sections.sort((a, b) => a.ordering - b.ordering)]; export const useSections = (projectId: string, initialSections: Section[]) => { const [sections_, setSections_] = useState(order(initialSections)); const [selected, setSelected] = useState(null); const setConfig = useStore(state => state.setConfig); - const setSections = (handler: (cur: Section[]) => Section[]) => setSections_(cur => order(handler(cur))); + const setSections = (handler: (cur: Section[]) => Section[]) => + setSections_(cur => order(handler(cur))); useEffect(() => { if (selected === null) { @@ -69,7 +77,7 @@ export const useSections = (projectId: string, initialSections: Section[]) => { const config = sections_.find(({ id }) => id === selected)!.config; setConfig(config === null ? "{}" : JSON.stringify(config)); } - }, [selected, sections_]); + }, [selected, sections_, setConfig]); // GET IT – NEW ORDER/BLUE MONDAY. I'M SO FUNNY. const reorder = (id: string, blueMonday: number, sections: Section[]) => { @@ -93,7 +101,8 @@ export const useSections = (projectId: string, initialSections: Section[]) => { ordering: cur.length, id: newSectionId }]); - return reorder(newSectionId, parseInt(section.ordering.toString()), newSections); + return reorder(newSectionId, + parseInt(section.ordering.toString()), newSections); }); setSelected(null); }; @@ -118,17 +127,21 @@ export const useSections = (projectId: string, initialSections: Section[]) => { }; const updateState = (state: string, name: string) => { - setSections(cur => cur.map(section => !section.states.includes(state) ? section : { - ...section, - states: section.states.map(c => c === state ? name : c) - })); + setSections(cur => + cur.map(section => + !section.states.includes(state) ? section : { + ...section, + states: section.states.map(c => c === state ? name : c) + })); }; const addToState = (id: string, state: string) => { - setSections(cur => cur.map(section => section.id !== id || section.states.includes(state) ? section : { - ...section, - states: section.states.concat([state]) - })); + setSections(cur => + cur.map(section => + section.id !== id || section.states.includes(state) ? section : { + ...section, + states: section.states.concat([state]) + })); }; const removeFromState = (id: string, state: string) => { @@ -177,8 +190,11 @@ export const useSections = (projectId: string, initialSections: Section[]) => { return { all: sections_, - getSections: (state: string) => sections_.filter(({ states }) => states.includes(state)), - getSectionsToImport: (from: string, to: string) => sections_.filter(({ states }) => states.includes(from) && !states.includes(to)), + getSections: (state: string) => sections_ + .filter(({ states }) => states.includes(state)), + getSectionsToImport: (from: string, to: string) => + sections_.filter(({ states }) => + states.includes(from) && !states.includes(to)), setSections, dragSection, reorder, @@ -190,7 +206,9 @@ export const useSections = (projectId: string, initialSections: Section[]) => { removeFromState, generateSection, updateSection, - states: sections_.flatMap(({ states }) => states).filter((x, i, arr) => arr.indexOf(x) === i) + states: sections_ + .flatMap(({ states }) => states) + .filter((x, i, arr) => arr.indexOf(x) === i) }; }; @@ -214,12 +232,17 @@ export const useActions = () => { } else { openDialog(); } - }, [action]); + }, [action, closeDialog, openDialog]); return { dialog: ref, setAction, action, isOpen }; }; -export const useCustomStates = (initialStates: string[], selectSection: (selected: string | null) => void, updateStateForSections: (state: string, name: string) => void, removeStateFromSection: (state: string) => void) => { +export const useCustomStates = ( + initialStates: string[], + selectSection: (selected: string | null) => void, + updateStateForSections: (state: string, name: string) => void, + removeStateFromSection: (state: string) => void +) => { const [customStates, setCustomStates] = useState(["__default__"]); const [selected, setSelected] = useState("__default__"); @@ -229,7 +252,11 @@ export const useCustomStates = (initialStates: string[], selectSection: (selecte }; return { - states: customStates.slice(0, 1).concat(initialStates).concat(customStates.slice(1)).filter((x, i, arr) => arr.indexOf(x) === i), + states: customStates + .slice(0, 1) + .concat(initialStates) + .concat(customStates.slice(1)) + .filter((x, i, arr) => arr.indexOf(x) === i), removeState: (state: string) => { setCustomStates(cur => cur.filter(c => c !== state)); removeStateFromSection(state); @@ -280,19 +307,29 @@ const loadNewProject = (username: string): (Project & { isSaved: false }); -export const useProject = (username: string | null, projectId: string | null) => { +export const useProject = ( + username: string | null, + projectId: string | null +) => { const tags_ = trpc.projects.getTags.useQuery(); - const project_ = trpc.projects.getProject.useQuery({ projectId: projectId ?? "__ERROR__" }, { enabled: projectId !== null }); + const project_ = trpc.projects.getProject.useQuery( + { projectId: projectId ?? "__ERROR__" }, + { enabled: projectId !== null } + ); const [project, setProject] = useState<(Project & { layout: Section[] }) | null>(null); - const tags = useMemo(() => (tags_.status === "success" && !isError(tags_.data) ? tags_.data : []).concat(project?.tags ?? []).filter((x, i, arr) => arr.indexOf(x) === i), [tags_.data, tags_.status]); + const tags = useMemo(() => (tags_.status === "success" && + !isError(tags_.data) ? tags_.data : []).concat(project?.tags ?? []) + .filter((x, i, arr) => + arr.indexOf(x) === i), [tags_.data, tags_.status, project?.tags]); useEffect(() => { - if (project_.status !== "success" || project_.data === null || isError(project_.data)) return; + if (project_.status !== "success" || project_.data === null || + isError(project_.data)) return; setProject(project_.data); - }, [project_.status]); + }, [project_.status, project_.data]); useEffect(() => { if (username === null || projectId !== null) return; @@ -310,24 +347,20 @@ export const useProject = (username: string | null, projectId: string | null) => }; }; -export type File = { - name: string, - version: number, - assetId: string, - isGlobal: boolean -} - export const useFiles = (projectId: string) => { - const files_ = trpc.projects.getFiles.useQuery({projectId}); + const files_ = trpc.projects.getFiles.useQuery({ projectId }); const [files, setFiles] = useState([]); useEffect(() => { if (files_.status !== "success" || isError(files_.data)) return; setFiles(files_.data); - }, [files_.status]); + }, [files_.status, files_.data]); const addFile = (name: string, data: string, assetId?: string) => { - if (files.find(file => file.name === name && file.isGlobal) !== undefined) return null; // TODO: add snackbar failure message + if (files.find(file => + file.name === name && file.isGlobal) !== undefined) { + return null; // TODO: add snackbar failure message + } if (assetId === undefined) { assetId = nanoid(16); setFiles(cur => [...cur, { @@ -352,11 +385,15 @@ export const useFiles = (projectId: string) => { return { assetId, name }; }; - const generateThumbnail = () => addFile(`thumbnail-gen-${nanoid(2)}`, "thumbnail data"); // TODO: replace with real thumbnail generation + const generateThumbnail = () => addFile( + `thumbnail-gen-${nanoid(2)}`, "thumbnail data" + ); // TODO: replace with real thumbnail generation const getLatest = useCallback((id: string) => { - const latest = Math.max(...files.filter(file => file.assetId === id || file.name === id).map(({ version }) => version)); - return files.find(file => (file.assetId === id || file.name === id) && file.version === latest)!; + const latest = Math.max(...files.filter(file => + file.assetId === id || file.name === id).map(({ version }) => version)); + return files.find(file => + (file.assetId === id || file.name === id) && file.version === latest)!; }, [files]); const getData = async (file: File) => ""; // TODO: replace with server call @@ -368,7 +405,8 @@ export const useFiles = (projectId: string) => { return files.find(({ name, version - }) => name === sections.at(-2)! && version === parseInt(sections.at(-1)!)) ?? null; + }) => name === sections.at(-2)! && + version === parseInt(sections.at(-1)!)) ?? null; }; return { @@ -384,26 +422,33 @@ export const useFiles = (projectId: string) => { export const useCollaboration = (project: Project, username: string) => { const users_ = trpc.projects.getUsers.useQuery(); - const invites_ = trpc.projects.getInvitesForProject.useQuery({ projectId: project.id }); + const invites_ = trpc.projects.getInvitesForProject + .useQuery({ projectId: project.id }); const [users, setUsers] = useState([]); const [invites, setInvites] = useState([]); useEffect(() => { if (users_.status !== "success" || isError(users_.data)) return; setUsers(users_.data); - }, [users_.status]); + }, [users_.status, users_.data]); useEffect(() => { if (invites_.status !== "success" || isError(invites_.data)) return; setInvites(invites_.data); - }, [invites_.status]); - - const uninvited = users.filter(user => project.collaboratorIds.find(id => id === user.id) === undefined && invites.find(({ - recipientId, - status - }) => recipientId === user.id && status === "pending") === undefined); - const accepted = project.collaboratorIds.map(id => users.find(user => user.id === id)!); - const invited = invites.filter(({ status }) => status === "pending").map(({ recipientId }) => users.find(({ id }) => id === recipientId)!); + }, [invites_.status, invites_.data]); + + const uninvited = users + .filter(user => project.collaboratorIds + .find(id => id === user.id) === undefined && invites.find(({ + recipientId, + status + }) => recipientId === user.id && status === "pending") === undefined); + const accepted = project.collaboratorIds + .map(id => users.find(user => user.id === id)!); + const invited = invites + .filter(({ status }) => status === "pending") + .map(({ recipientId }) => users.find(({ id }) => + id === recipientId)!); const inviteCollaborator = (id: string) => { setInvites(cur => [...cur, { @@ -419,8 +464,10 @@ export const useCollaboration = (project: Project, username: string) => { const removeCollaborator = (id: string) => { if (id === project.creatorId) return; const user = users.find(user => user.id === id)!; - if (user.id === users.find(({ id }) => id === project.creatorId)!.username) return; - setInvites(cur => cur.filter(x => x.recipientId !== id && x.status !== "declined")); + if (user.id === users.find(({ id }) => + id === project.creatorId)!.username) return; + setInvites(cur => cur.filter(x => + x.recipientId !== id && x.status !== "declined")); }; return { @@ -443,9 +490,10 @@ export const useObservatories = () => { }>>({}); useEffect(() => { - if (observatories_.status !== "success" || isError(observatories_.data)) return; + if (observatories_.status !== "success" || + isError(observatories_.data)) return; setObservatories(observatories_.data); - }, [observatories_.status]); + }, [observatories_.status, observatories_.data]); return { observatories }; }; diff --git a/apps/ove-core-ui/src/pages/project-editor/launch-config/launch-config.tsx b/apps/ove-core-ui/src/pages/project-editor/launch-config/launch-config.tsx index fc3bb7c5..a0aa6bd0 100644 --- a/apps/ove-core-ui/src/pages/project-editor/launch-config/launch-config.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/launch-config/launch-config.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { type Actions } from "../hooks"; import { toProject } from "../utils"; import { useForm } from "react-hook-form"; diff --git a/apps/ove-core-ui/src/pages/project-editor/loader.tsx b/apps/ove-core-ui/src/pages/project-editor/loader.tsx index 541a96a6..63e40556 100644 --- a/apps/ove-core-ui/src/pages/project-editor/loader.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/loader.tsx @@ -3,13 +3,14 @@ import { useProject } from "./hooks"; import { useQuery } from "../../hooks"; import { trpc } from "../../utils/api"; import { isError } from "@ove/ove-types"; -import { useMemo } from "react"; +import React, { useMemo } from "react"; const Loader = ({ username }: { username: string }) => { const query = useQuery(); const user = trpc.getUserID.useQuery(); - const userId = useMemo(() => user.status === "success" && !isError(user.data) ? user.data.id : null, [user.status, user.data]); + const userId = useMemo(() => user.status === "success" && + !isError(user.data) ? user.data.id : null, [user.status, user.data]); const { project, @@ -17,7 +18,7 @@ const Loader = ({ username }: { username: string }) => { tags } = useProject(userId, query.get("project")); - return project === null || userId === null ? <> : + return project === null || userId === null ? null : ; }; diff --git a/apps/ove-core-ui/src/pages/project-editor/metadata/metadata.tsx b/apps/ove-core-ui/src/pages/project-editor/metadata/metadata.tsx index 516e64b1..a3a99265 100644 --- a/apps/ove-core-ui/src/pages/project-editor/metadata/metadata.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/metadata/metadata.tsx @@ -1,15 +1,18 @@ -import { useState } from "react"; +import React, { useState } from "react"; +import { type Actions } from "../hooks"; +import { actionColors } from "../utils"; import { useForm } from "react-hook-form"; +import { type File } from "@ove/ove-types"; import { Brush, X } from "react-bootstrap-icons"; -import { type Actions, type File } from "../hooks"; -import { type Project, User } from "@prisma/client"; +import { Button } from "@ove/ui-base-components"; +import { type Project, type User } from "@prisma/client"; import S3FileSelect from "../../../components/s3-file-select/s3-file-select"; import styles from "./metadata.module.scss"; -import { Button } from "@ove/ui-base-components"; -import { actionColors } from "../utils"; -export type ProjectMetadata = Pick +export type ProjectMetadata = Pick type MetadataProps = { project: ProjectMetadata @@ -68,8 +71,10 @@ const Metadata = ({ } = useForm({ defaultValues: { ...project, - fileName: fromURL(project.thumbnail ?? "")?.name ?? "-- select an option --", - fileVersion: fromURL(project.thumbnail ?? "")?.version.toString() ?? "-- select an option --" + fileName: fromURL(project.thumbnail ?? "")?.name ?? + "-- select an option --", + fileVersion: fromURL(project.thumbnail ?? "")?.version.toString() ?? + "-- select an option --" } }); @@ -88,12 +93,15 @@ const Metadata = ({ return; } if (metadata.publication !== "") { - setPublications(cur => cur.includes(metadata.publication) ? cur : [...cur, metadata.publication]); + setPublications(cur => + cur.includes(metadata.publication) ? cur : + [...cur, metadata.publication]); setValue("publication", ""); return; } if (metadata.collaborator !== "") { - if (uninvited.find(({ username }) => username === metadata.collaborator) !== undefined) { + if (uninvited.find(({ username }) => + username === metadata.collaborator) !== undefined) { setCollaborators_(cur => [...cur, { username: metadata.collaborator, status: "added" @@ -103,9 +111,15 @@ const Metadata = ({ return; } const { fileName, fileVersion, ...config } = metadata; - const added = collaborators_.filter(({ status }) => status === "added").map(({ username }) => uninvited.find(x => x.username === username)!); + const added = collaborators_ + .filter(({ status }) => status === "added") + .map(({ username }) => + uninvited.find(x => x.username === username)!); const all = invited.concat(accepted); - const removed = collaborators_.filter(({ status }) => status === "removed").map(({ username }) => all.find(x => x.username === username && x.id !== project.creatorId)).filter(Boolean) as User[]; + const removed = collaborators_ + .filter(({ status }) => status === "removed") + .map(({ username }) => all.find(x => x.username === username && + x.id !== project.creatorId)).filter(Boolean) as User[]; added.forEach(x => inviteCollaborator(x.id)); removed.forEach(x => removeCollaborator(x.id)); updateProject({ @@ -113,8 +127,10 @@ const Metadata = ({ ...config, publications, tags, - collaboratorIds: [...project.collaboratorIds.filter(x => removed.find(({ id }) => id === x) === undefined)], - thumbnail: fileName !== null && fileVersion !== null ? toURL(fileName, parseInt(fileVersion)) : null + collaboratorIds: [...project.collaboratorIds.filter(x => + removed.find(({ id }) => id === x) === undefined)], + thumbnail: fileName !== null && fileVersion !== null ? + toURL(fileName, parseInt(fileVersion)) : null }); setAction(null); }; @@ -141,12 +157,16 @@ const Metadata = ({
      - {tags.map((tag, i) =>
    • {tag} - -
    • )} + {tags.map((tag, i) => { + const backgroundColor = actionColors[i % tags.length]; + return
    • {tag} + +
    • ; + })}
    • {publication}
    • )} @@ -177,7 +198,9 @@ const Metadata = ({
        Accepted:
        - {accepted.filter(x => collaborators_.find(y => x.username === y.username) === undefined).map(({ username }) => + {accepted.filter(x => collaborators_ + .find(y => + x.username === y.username) === undefined).map(({ username }) =>
      • {username}
      • )}
        @@ -212,7 +238,9 @@ const Metadata = ({ placeholder="Invite Collaborator:" list="collaborator-backing" /> - {uninvited.filter(({ username }) => collaborators_.find(c => c.username === username) === undefined).map(({ username }) => + {uninvited.filter(({ username }) => collaborators_ + .find(c => + c.username === username) === undefined).map(({ username }) => )} diff --git a/apps/ove-core-ui/src/pages/project-editor/page.tsx b/apps/ove-core-ui/src/pages/project-editor/page.tsx index b508b2a8..c3212594 100644 --- a/apps/ove-core-ui/src/pages/project-editor/page.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/page.tsx @@ -22,11 +22,11 @@ import ResizeContainer from "./canvas/resize-container"; import ConfigEditor from "./config-editor/config-editor"; import LaunchConfig from "./launch-config/launch-config"; import SectionConfig from "./section-config/section-config"; -import { useState, type CSSProperties, useEffect } from "react"; import Controller from "../../components/controller/controller"; import SectionImporter from "./section-importer/section-importer"; import ControllerEditor from "./controller-editor/controller-editor"; import Metadata, { type ProjectMetadata } from "./metadata/metadata"; +import React, { useState, type CSSProperties, useEffect } from "react"; import styles from "./page.module.scss"; import { @@ -35,26 +35,40 @@ import { ResizablePanelGroup } from "@ove/ui-base-components"; -const getDialogStyling = (action: ActionsT | null): CSSProperties | undefined => { - if (action === "launch") return { - width: "20vw", - aspectRatio: "4/3.25", - borderRadius: "0.25rem" - }; - if (action !== null && ["controller", "custom-config", "env"].includes(action)) return { - width: "60vw", borderRadius: "0.25rem" - }; +const getDialogStyling = ( + action: ActionsT | null +): CSSProperties | undefined => { + if (action === "launch") { + return { + width: "20vw", + aspectRatio: "4/3.25", + borderRadius: "0.25rem" + }; + } + if (action !== null && + ["controller", "custom-config", "env"].includes(action)) { + return { + width: "60vw", borderRadius: "0.25rem" + }; + } return { borderRadius: "0.25rem" }; }; -const getInnerDialogStyling = (action: ActionsT | null): CSSProperties | undefined => { - if (action !== null && ["controller", "custom-config", "env"].includes(action)) return { - padding: "0" - }; - if (action === "import-section") return { - padding: "1rem" - }; +const getInnerDialogStyling = ( + action: ActionsT | null +): CSSProperties | undefined => { + if (action !== null && + ["controller", "custom-config", "env"].includes(action)) { + return { + padding: "0" + }; + } + if (action === "import-section") { + return { + padding: "1rem" + }; + } return undefined; }; @@ -94,7 +108,12 @@ const ProjectEditor = ({ const { dialog, isOpen, action, setAction } = useActions(); const container = useContainer(space); const sections = useSections(project.id, project.layout); - const states = useCustomStates(sections.states, sections.select, sections.updateState, sections.removeState); + const states = useCustomStates( + sections.states, + sections.select, + sections.updateState, + sections.removeState + ); const { assets, toURL, @@ -104,7 +123,8 @@ const ProjectEditor = ({ getData, generateThumbnail } = useFiles(project.id); - const [innerDialogStyle, setInnerDialogStyle] = useState(); + const [innerDialogStyle, setInnerDialogStyle] = + useState(); const { invited, accepted, @@ -140,7 +160,9 @@ const ProjectEditor = ({ case "controller": { const controller = getLatest("control"); return addFile(controller.name, data, controller.assetId)} />; + update={data => + addFile(controller.name, data, + controller.assetId)} />; } case "upload": return addFile(env.name, data, env.assetId)} />; + update={data => + addFile(env.name, data, env.assetId)} />; } case "live": return ; default: - return <>; + return null; } }; @@ -231,11 +254,8 @@ const ProjectEditor = ({ hiddenStyle={innerDialogStyle ?? getInnerDialogStyling(action)}> {getDialogContent()} - { - isOpen ?
        : <> - } -
    - ; + {isOpen ?
    : null} +
; }; export default ProjectEditor; diff --git a/apps/ove-core-ui/src/pages/project-editor/section-config/section-config.tsx b/apps/ove-core-ui/src/pages/project-editor/section-config/section-config.tsx index b4112d42..5b0faabd 100644 --- a/apps/ove-core-ui/src/pages/project-editor/section-config/section-config.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/section-config/section-config.tsx @@ -1,22 +1,31 @@ -import { type Actions, type File } from "../hooks"; -import { useForm, UseFormRegister, UseFormSetValue } from "react-hook-form"; +import { z } from "zod"; +import { dataTypes } from "../utils"; +import { type Actions } from "../hooks"; +import { type File } from "@ove/ove-types"; +import { + type DataType, + type Geometry as TGeometry, + type Space +} from "../types"; +import { + useForm, + type UseFormRegister, + type UseFormSetValue +} from "react-hook-form"; import { useStore } from "../../../store"; -import { useEffect, useState } from "react"; import { type Section } from "@prisma/client"; -import { DataType, type Geometry, type Space } from "../types"; +import React, { useEffect, useState } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Grid, Brush, Fullscreen } from "react-bootstrap-icons"; import S3FileSelect from "../../../components/s3-file-select/s3-file-select"; import styles from "./section-config.module.scss"; -import { dataTypes } from "../utils"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; type SectionConfigProps = { sections: Section[] selected: string | null updateSection: (section: Omit) => void - space: Space & { cells: Geometry[] } + space: Space & { cells: TGeometry[] } projectId: string state: string setAction: (action: Actions | null) => void @@ -28,7 +37,10 @@ type SectionConfigProps = { const getDataTypeFromFile = (file: File) => { for (const dt of dataTypes) { - if (dt.extensions.some(extension => file.name.toUpperCase().endsWith(extension))) return dt.name; + if (dt.extensions.some(extension => + file.name.toUpperCase().endsWith(extension))) { + return dt.name; + } } return null; }; @@ -42,17 +54,19 @@ const sort = (k: keyof DataType, a: DataType, b: DataType) => { const toPercentage = (x: number) => parseFloat(`${(x * 100)}`.slice(0, 5)); const fromPercentage = (x: number) => parseFloat(x.toString()) / 100; -const getRow = (y: number, space: Space & { cells: Geometry[] }) => { +const getRow = (y: number, space: Space & { cells: TGeometry[] }) => { if (y === 0) return 0; if (y === 1) return space.rows; for (let i = 0; i < space.cells.length; i++) { - if (space.cells[i].y === y * space.height) return Math.floor(i / space.columns); + if (space.cells[i].y === y * space.height) { + return Math.floor(i / space.columns); + } } return null; }; -const getColumn = (x: number, space: Space & { cells: Geometry[] }) => { +const getColumn = (x: number, space: Space & { cells: TGeometry[] }) => { if (x === 0) return 0; if (x === 1) return space.columns; for (let i = 0; i < space.cells.length; i++) { @@ -102,14 +116,15 @@ const SectionConfig = ({ setValue, resetField, watch - } = useForm>({ resolver: zodResolver(SectionConfigSchema) }); + } = useForm>( + { resolver: zodResolver(SectionConfigSchema) }); const [mode, setMode] = useState<"custom" | "grid">("custom"); const config = useStore(state => state.config); const [fileName, fileVersion] = watch(["fileName", "fileVersion"]); useEffect(() => { setValue("config", formatConfig(config)); - }, [config]); + }, [config, setValue]); useEffect(() => { if (selected === null) { @@ -148,23 +163,29 @@ const SectionConfig = ({ setValue("fileName", file.name); setValue("fileVersion", file.version.toString()); } - }, [selected, sections]); + }, [selected, sections, fromURL, resetField, setValue, space]); const onSubmit = (section: z.infer) => { updateSection({ - x: mode === "custom" ? fromPercentage(section.x!) : section.columnFrom! / space.columns, - y: mode === "custom" ? fromPercentage(section.y!) : section.rowFrom! / space.rows, - width: mode === "custom" ? fromPercentage(section.width!) : (section.columnTo! - section.columnFrom!) * (1 / space.columns), - height: mode === "custom" ? fromPercentage(section.height!) : (section.rowTo! - section.rowFrom!) * (1 / space.rows), + x: mode === "custom" ? fromPercentage(section.x!) : + section.columnFrom! / space.columns, + y: mode === "custom" ? fromPercentage(section.y!) : + section.rowFrom! / space.rows, + width: mode === "custom" ? fromPercentage(section.width!) : + (section.columnTo! - section.columnFrom!) * (1 / space.columns), + height: mode === "custom" ? fromPercentage(section.height!) : + (section.rowTo! - section.rowFrom!) * (1 / space.rows), config: JSON.parse(config), assetId: files.find(({ name, version - }) => name === section.fileName && version.toString() === section.fileVersion)?.assetId ?? null, + }) => name === section.fileName && + version.toString() === section.fileVersion)?.assetId ?? null, asset: section.asset, dataType: section.dataType, states: [state], - ordering: sections.find(section => section.id === selected)?.ordering ?? sections.length, + ordering: sections.find(section => + section.id === selected)?.ordering ?? sections.length, projectId: projectId }); }; @@ -174,20 +195,24 @@ const SectionConfig = ({ setValue("fileName", file?.name ?? null); setValue("fileVersion", file?.version?.toString() ?? null); if (file !== null) { - setValue("dataType", getDataTypeFromFile(file) ?? "-- select an option --"); + setValue("dataType", getDataTypeFromFile(file) ?? + "-- select an option --"); } }; useEffect(() => { - if (fileName !== null && fileName !== undefined && fileName !== "-- select an option --" && fileVersion !== null && fileVersion !== undefined && fileVersion !== "-- select an option --") { + if (fileName !== null && fileName !== undefined && + fileName !== "-- select an option --" && fileVersion !== null && + fileVersion !== undefined && fileVersion !== "-- select an option --") { setValue("asset", toURL(fileName, parseInt(fileVersion))); const file = files.find(({ name, version }) => name === fileName && version === parseInt(fileVersion))!; - setValue("dataType", getDataTypeFromFile(file) ?? "-- select an option --"); + setValue("dataType", getDataTypeFromFile(file) ?? + "-- select an option --"); } - }, [fileName, fileVersion]); + }, [fileName, fileVersion, files, setValue, toURL]); return

Section Config

@@ -203,7 +228,10 @@ const SectionConfig = ({
- onAssetChange(e?.target?.value) })} /> + + onAssetChange(e?.target?.value) + })} /> @@ -242,16 +270,18 @@ const Geometry = ({ mode, register, setMode, setValue, space }: { setValue("rowTo", space.rows); }; + const backgroundColor = mode === "custom" ? "#dadedf" : undefined; + return
+ style={{ fontWeight: state === selected ? 700 : 400 }}> + {formatState(state)} + ; })} @@ -58,15 +61,24 @@ const SectionImporter = ({

Select Section

    - {sections.map(section =>
  • name === section.dataType.toLowerCase())!.color }}> - -
  • )} + {sections.map(section => { + const backgroundColor = colors.find(({ name }) => + name === section.dataType.toLowerCase())!.color; + const asset = section.asset.length < 25 ? + section.asset : `${section.asset.slice(0, 24)}...`; + return
  • + +
  • ; + })}
diff --git a/apps/ove-core-ui/src/pages/project-editor/sections/sections.tsx b/apps/ove-core-ui/src/pages/project-editor/sections/sections.tsx index 826306f3..81b0a713 100644 --- a/apps/ove-core-ui/src/pages/project-editor/sections/sections.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/sections/sections.tsx @@ -1,14 +1,14 @@ +import { dataTypes } from "../utils"; +import { Json } from "@ove/ove-utils"; import { type Actions } from "../hooks"; import { Import, X } from "lucide-react"; -import { useRef, useState } from "react"; import { type Section } from "@prisma/client"; +import React, { useRef, useState } from "react"; import { PlusCircle } from "react-bootstrap-icons"; import ResizeContainer from "../canvas/resize-container"; import { ReorderableItem, ReorderableList } from "@ove/ui-reorderable-list"; import styles from "./sections.module.scss"; -import { dataTypes } from "../utils"; -import { Json } from "@ove/ove-utils"; type SectionsProps = { sections: Section[] @@ -38,12 +38,14 @@ const Sections = ({ const getCharacterLimit = (bounds: { width: number } | undefined) => { if (listRef.current === null || bounds === undefined) return; - const rem = parseFloat(getComputedStyle(document.body).fontSize.replace("px", "")); + const rem = parseFloat(getComputedStyle(document.body) + .fontSize.replace("px", "")); setCharacterLimit(Math.floor((bounds.width - (4 * rem)) / (rem * 0.5))); }; const setSectionsHandler = (curList: Section[], newList: Section[]) => { - const newListOldOrder = Json.copy(newList).sort((a, b) => a.ordering - b.ordering); + const newListOldOrder = Json.copy(newList) + .sort((a, b) => a.ordering - b.ordering); let startOrder: number | null = null; let endOrder: number | null = null; newList.forEach((section, i) => { @@ -56,10 +58,15 @@ const Sections = ({ } }); - return curList.slice(0, startOrder!).concat([curList[endOrder!]].concat(curList.slice(startOrder!, endOrder!)).map((section, i) => ({ - ...section, - ordering: i - }))).concat(curList.slice(endOrder! + 1)); + return curList + .slice(0, startOrder!) + .concat([curList[endOrder!]] + .concat(curList.slice(startOrder!, endOrder!)) + .map((section, i) => ({ + ...section, + ordering: i + }))) + .concat(curList.slice(endOrder! + 1)); }; return
@@ -72,39 +79,50 @@ const Sections = ({ update: getCharacterLimit }} useContentRect={true}> setSections(curList => setSectionsHandler(curList, newList))} + onListUpdate={newList => + setSections(curList => + setSectionsHandler(curList, newList as Section[]))} list={sections} style={{}}> - {sections.map(section => ( - -
  • name === section.dataType.toLowerCase())!.color, - borderWidth: selected === section.id ? "2px" : "1px" - }}> - -
    - -
    -
  • -
    - ))} +
    + +
    + + + ); + })}
    {numStates > 1 ? : <>} + : null}
    diff --git a/apps/ove-core-ui/src/pages/project-editor/space-config/space-config.tsx b/apps/ove-core-ui/src/pages/project-editor/space-config/space-config.tsx index 0c3edeef..65b0aa11 100644 --- a/apps/ove-core-ui/src/pages/project-editor/space-config/space-config.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/space-config/space-config.tsx @@ -1,13 +1,13 @@ import { z } from "zod"; +import { toast } from "sonner"; import { Json } from "@ove/ove-utils"; import { useForm } from "react-hook-form"; import { type Geometry, type Rect, type Space } from "../types"; import { type NativeEvent } from "@ove/ove-types"; import { zodResolver } from "@hookform/resolvers/zod"; -import { type BaseSyntheticEvent, useEffect, useRef } from "react"; +import React, { type BaseSyntheticEvent, useEffect, useRef } from "react"; import styles from "./space-config.module.scss"; -import { toast } from "sonner"; type SpaceConfigProps = { space: Space & { cells: Geometry[], update: (space: Space) => void } @@ -18,7 +18,9 @@ const getPreset = (presets: { [key: string]: Space }, curSpace: Space) => { const reduced = reduce(curSpace); const elem = Object.entries(presets).find(([_k, v]) => { const presetReduced = reduce(v); - return presetReduced.height === reduced.height && presetReduced.width === reduced.width && v.columns === curSpace.columns && v.rows === curSpace.rows; + return presetReduced.height === reduced.height && + presetReduced.width === reduced.width && + v.columns === curSpace.columns && v.rows === curSpace.rows; }); return elem?.[0] ?? "-- select an option --"; }; @@ -47,7 +49,7 @@ const SpaceConfig = ({ register, handleSubmit, setValue, - formState: {errors} + formState: { errors } } = useForm({ resolver: zodResolver(SpaceConfigSchema), defaultValues: { @@ -66,7 +68,8 @@ const SpaceConfig = ({ preset: string | null }, e: BaseSyntheticEvent | undefined) => { const { preset, ...newSpace } = config; - if ((e?.nativeEvent as unknown as NativeEvent)?.submitter?.name !== undefined) { + if ((e?.nativeEvent as unknown as NativeEvent) + ?.submitter?.name !== undefined) { if (!Json.equals(newSpace, curSpace)) { setValue("preset", getPreset(presets, newSpace)); } @@ -96,7 +99,7 @@ const SpaceConfig = ({

    :

    - + diff --git a/apps/ove-core-ui/src/pages/project-editor/state-tabs/state-tabs.tsx b/apps/ove-core-ui/src/pages/project-editor/state-tabs/state-tabs.tsx index c85fbfda..a0782560 100644 --- a/apps/ove-core-ui/src/pages/project-editor/state-tabs/state-tabs.tsx +++ b/apps/ove-core-ui/src/pages/project-editor/state-tabs/state-tabs.tsx @@ -1,4 +1,4 @@ -import { +import React, { type ReactNode, type FocusEvent, useEffect, @@ -150,7 +150,7 @@ const EditTab = ({ onSubmit }: EditTabProps) => { handleSubmit } = useForm>(); - useEffect(() => () => resetField("name"), []); + useEffect(() => () => resetField("name"), [resetField]); return diff --git a/apps/ove-core-ui/src/pages/project-editor/types.ts b/apps/ove-core-ui/src/pages/project-editor/types.ts index c2aab836..adce8b97 100644 --- a/apps/ove-core-ui/src/pages/project-editor/types.ts +++ b/apps/ove-core-ui/src/pages/project-editor/types.ts @@ -18,4 +18,4 @@ export type DataType = { displayName: string color: string extensions: string[] -} \ No newline at end of file +} diff --git a/apps/ove-core-ui/src/pages/project-editor/utils.ts b/apps/ove-core-ui/src/pages/project-editor/utils.ts index 677d6549..a4cf46cc 100644 --- a/apps/ove-core-ui/src/pages/project-editor/utils.ts +++ b/apps/ove-core-ui/src/pages/project-editor/utils.ts @@ -65,4 +65,14 @@ export const dataTypes: DataType[] = [ } ]; -export const actionColors = ["#ef476f", "#f78c6b", "#ffd166", "#06d6a0", "#118ab2", "#002147", "#FA9E78", "#FDEBDC", "#6B9A9B"]; +export const actionColors = [ + "#ef476f", + "#f78c6b", + "#ffd166", + "#06d6a0", + "#118ab2", + "#002147", + "#FA9E78", + "#FDEBDC", + "#6B9A9B" +]; diff --git a/apps/ove-core-ui/src/pages/sockets/page.tsx b/apps/ove-core-ui/src/pages/sockets/page.tsx index 7e26d018..e1469f06 100644 --- a/apps/ove-core-ui/src/pages/sockets/page.tsx +++ b/apps/ove-core-ui/src/pages/sockets/page.tsx @@ -1,9 +1,11 @@ +import React from "react"; import { env } from "../../env"; -const Sockets = () => { - return
    - -
    -}; +const Sockets = () => +
    + +
    ; -export default Sockets; \ No newline at end of file +export default Sockets; diff --git a/apps/ove-core-ui/src/store.ts b/apps/ove-core-ui/src/store.ts index 319d25bb..5d040717 100644 --- a/apps/ove-core-ui/src/store.ts +++ b/apps/ove-core-ui/src/store.ts @@ -78,9 +78,10 @@ const getCurrentTokens = () => { export const useStore = create(set => ({ tokens: getCurrentTokens(), - setTokens: (tokens: Tokens | null) => tokens === null ? set({ tokens: null }) : set({ tokens: { ...tokens } }), + setTokens: (tokens: Tokens | null) => + tokens === null ? set({ tokens: null }) : set({ tokens: { ...tokens } }), config: "{}", - setConfig: (config: string) => set({config}), + setConfig: (config: string) => set({ config }), hardwareConfig: { info: null, setInfo: info => set(state => ({ @@ -119,7 +120,12 @@ export const useStore = create(set => ({ reset: () => set(state => ({ hardwareConfig: { ...state.hardwareConfig, - deviceAction: {bridgeId: null, deviceId: null, pending: false, action: null}, + deviceAction: { + bridgeId: null, + deviceId: null, + pending: false, + action: null + }, info: null, paginationIdx: 0, command: null, @@ -234,4 +240,4 @@ export const useStore = create(set => ({ } })) } -})); \ No newline at end of file +})); diff --git a/apps/ove-core-ui/src/utils.ts b/apps/ove-core-ui/src/utils.ts index be0b6790..32d96879 100644 --- a/apps/ove-core-ui/src/utils.ts +++ b/apps/ove-core-ui/src/utils.ts @@ -3,6 +3,7 @@ import superjson from "superjson"; import { type Tokens } from "@ove/ove-types"; import { createTRPCProxyClient, httpLink } from "@trpc/client"; // IGNORE PATH - dependency removed at runtime +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type AppRouter } from "../../ove-core/src/server/router"; export type InfoTypes = "general" | "system" | "cpu" | "memory" @@ -10,7 +11,8 @@ export type InfoTypes = "general" | "system" | "cpu" | "memory" const fixedEncodeURI = (str: string) => encodeURI(str).replace(/[!'()*]/g, c => "%" + c.charCodeAt(0).toString(16)); -export const createClient = (tokens: Tokens) => createClient_(`Bearer ${tokens.refresh}`); +export const createClient = (tokens: Tokens) => + createClient_(`Bearer ${tokens.refresh}`); export const createAuthClient = (username: string, password: string) => { const auth = fixedEncodeURI(window.btoa(`${username}:${password}`)); return createClient_(`Basic ${auth}`); diff --git a/apps/ove-core-ui/src/utils/api.ts b/apps/ove-core-ui/src/utils/api.ts index c510c79d..a7c6d2dd 100644 --- a/apps/ove-core-ui/src/utils/api.ts +++ b/apps/ove-core-ui/src/utils/api.ts @@ -9,6 +9,7 @@ import { createTRPCReact } from "@trpc/react-query"; import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; // IGNORE PATH - dependency removed at runtime +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type AppRouter } from "../../../ove-core/src/server/router"; /** @@ -25,4 +26,4 @@ export type RouterInputs = inferRouterInputs; * Inference helper for outputs * @example type HelloOutput = RouterOutputs['example']['hello'] **/ -export type RouterOutputs = inferRouterOutputs; \ No newline at end of file +export type RouterOutputs = inferRouterOutputs; diff --git a/apps/ove-core-ui/tailwind.config.js b/apps/ove-core-ui/tailwind.config.js index c9145f25..4d83e3c2 100644 --- a/apps/ove-core-ui/tailwind.config.js +++ b/apps/ove-core-ui/tailwind.config.js @@ -1,14 +1,14 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind'); -const { join } = require('path'); -const { fontFamily } = require('tailwindcss/defaultTheme'); +const { createGlobPatternsForDependencies } = require("@nrwl/react/tailwind"); +const { join } = require("path"); +const { fontFamily } = require("tailwindcss/defaultTheme"); -/** @type {import('tailwindcss').Config} */ +/** @type {import("tailwindcss").Config} */ module.exports = { darkMode: ["class"], content: [ join( __dirname, - '{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}' + "{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}" ), ...createGlobPatternsForDependencies(__dirname), ], @@ -57,8 +57,8 @@ module.exports = { }, }, borderRadius: { - lg: `var(--radius)`, - md: `calc(var(--radius) - 2px)`, + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, fontFamily: { diff --git a/apps/ove-core-ui/vite.config.ts b/apps/ove-core-ui/vite.config.ts index b5b0a381..8956c68b 100644 --- a/apps/ove-core-ui/vite.config.ts +++ b/apps/ove-core-ui/vite.config.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line spaced-comment /// import react from "@vitejs/plugin-react"; import viteTsConfigPaths from "vite-tsconfig-paths"; diff --git a/apps/ove-core/src/env.ts b/apps/ove-core/src/env.ts index 26f4a751..56e1d509 100644 --- a/apps/ove-core/src/env.ts +++ b/apps/ove-core/src/env.ts @@ -1,10 +1,10 @@ -/* global Proxy, process, console */ +/* global process, __dirname, Buffer */ import { z } from "zod"; import dotenv from "dotenv"; import * as path from "path"; -import {nanoid} from "nanoid"; -import {createHmac} from "crypto"; +import { nanoid } from "nanoid"; +import { createHmac } from "crypto"; import { Logger } from "@ove/ove-logging"; import { setupConfigWithRefinement } from "@ove/ove-server-utils"; @@ -15,7 +15,11 @@ dotenv.config(); * This way you can ensure the server isn't built with invalid env vars. */ const baseSchema = z.strictObject({ - NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), + NODE_ENV: z.union([ + z.literal("development"), + z.literal("production"), + z.literal("test") + ]), LOG_LEVEL: z.number().optional(), LOGGING_SERVER: z.string().optional(), PORT: z.number(), @@ -25,13 +29,25 @@ const baseSchema = z.strictObject({ REFRESH_TOKEN_SECRET: z.string(), ACCESS_TOKEN_PASSPHRASE: z.string(), REFRESH_TOKEN_PASSPHRASE: z.string(), - SOCKET_ADMIN: z.strictObject({USERNAME: z.string(), PASSWORD: z.string()}).optional(), - ASSET_STORE_CREDENTIALS: z.strictObject({ACCESS_KEY: z.string(), SECRET_KEY: z.string()}).optional(), + SOCKET_ADMIN: z.strictObject({ + USERNAME: z.string(), + PASSWORD: z.string() + }).optional(), + ASSET_STORE_CONFIG: z.strictObject({ + ACCESS_KEY: z.string(), + SECRET_KEY: z.string(), + END_POINT: z.string(), + PORT: z.number(), + USE_SSL: z.boolean(), + GLOBAL_BUCKETS: z.string().array() + }).optional(), DISABLE_AUTH: z.boolean(), TEST_USER: z.string().optional() }); -const schema = baseSchema.refine(config => (config.NODE_ENV === "test" && config.TEST_USER !== undefined) || !config.DISABLE_AUTH); +const schema = baseSchema.refine(config => + (config.NODE_ENV === "test" && config.TEST_USER !== undefined) || + !config.DISABLE_AUTH); const staticConfig = { APP_NAME: "ove-core", @@ -42,10 +58,16 @@ const staticConfig = { } as const; const accessTokenPassphrase = nanoid(16); -const accessTokenSecret = Buffer.from(createHmac("sha256", accessTokenPassphrase).digest("hex")).toString("base64"); +const accessTokenSecret = Buffer + .from(createHmac("sha256", accessTokenPassphrase) + .digest("hex")) + .toString("base64"); const refreshTokenPassphrase = nanoid(16); -const refreshTokenSecret = Buffer.from(createHmac("sha256", refreshTokenPassphrase).digest("hex")).toString("base64"); +const refreshTokenSecret = Buffer + .from(createHmac("sha256", refreshTokenPassphrase) + .digest("hex")) + .toString("base64"); const defaultConfig: z.infer = { NODE_ENV: process.env.NODE_ENV, @@ -56,12 +78,18 @@ const defaultConfig: z.infer = { ACCESS_TOKEN_SECRET: accessTokenSecret, REFRESH_TOKEN_SECRET: refreshTokenSecret, REFRESH_TOKEN_PASSPHRASE: refreshTokenPassphrase, - DISABLE_AUTH: process.env.NODE_ENV === "test" + DISABLE_AUTH: false }; const configPath = path.join(__dirname, "config", "config.json"); -export const env = setupConfigWithRefinement(configPath, defaultConfig, schema, staticConfig, Object.keys(baseSchema.shape)); +export const env = setupConfigWithRefinement( + configPath, + defaultConfig, + schema, + staticConfig, + Object.keys(baseSchema.shape) +); export const logger = Logger(env.APP_NAME, env.LOG_LEVEL, env.LOGGING_SERVER); logger.info(`Loaded configuration from ${configPath}`); diff --git a/apps/ove-core/src/main.ts b/apps/ove-core/src/main.ts index 64a3bc84..4ac073de 100644 --- a/apps/ove-core/src/main.ts +++ b/apps/ove-core/src/main.ts @@ -25,13 +25,14 @@ app.use(`/api/v${env.API_VERSION}/trpc`, trpcExpress.createExpressMiddleware({ app.use(`/api/v${env.API_VERSION}`, createOpenApiExpressMiddleware({ router: appRouter, createContext -}) as any); +}) as Parameters[1]); app.use("/", swaggerUi.serve); app.get("/", swaggerUi.setup(openApiDocument)); if (env.NODE_ENV === "development") { - FileUtils.saveSwagger(path.join(`v${env.API_VERSION}`, "core.swagger.json"), openApiDocument); + FileUtils.saveSwagger( + path.join(`v${env.API_VERSION}`, "core.swagger.json"), openApiDocument); } app.use("/assets", express.static(path.join(__dirname, "assets"))); diff --git a/apps/ove-core/src/server/app.ts b/apps/ove-core/src/server/app.ts index a5c21b79..f441126b 100644 --- a/apps/ove-core/src/server/app.ts +++ b/apps/ove-core/src/server/app.ts @@ -1,11 +1,14 @@ +/* global __dirname */ + import * as path from "path"; import express from "express"; import { env, logger } from "../env"; export const app = express(); -app.use("/admin", express.static(path.join(__dirname, "..", "..", "..", "node_modules", "@socket.io", "admin-ui", "ui", "dist"))); +app.use("/admin", express.static(path.join(__dirname, "..", "..", "..", + "node_modules", "@socket.io", "admin-ui", "ui", "dist"))); export const server = app.listen(env.PORT, env.HOSTNAME, () => { logger.info(`Listening at ${env.HOSTNAME}:${env.PORT}`); -}).on("error", logger.error); \ No newline at end of file +}).on("error", logger.error); diff --git a/apps/ove-core/src/server/auth/controller.ts b/apps/ove-core/src/server/auth/controller.ts index fa68f6f8..801ceba1 100644 --- a/apps/ove-core/src/server/auth/controller.ts +++ b/apps/ove-core/src/server/auth/controller.ts @@ -1,3 +1,5 @@ +/* global Buffer */ + import { env } from "../../env"; import { TRPCError } from "@trpc/server"; import * as bcrypt from "bcrypt"; @@ -17,10 +19,12 @@ const generateToken = ( ); const login = async (ctx: Context): Promise => { - if (ctx.user === null) throw new TRPCError({ - code: "UNAUTHORIZED", - message: "No credentials provided" - }); + if (ctx.user === null) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "No credentials provided" + }); + } const [username, password] = decodeURIComponent( Buffer.from(ctx.user, "base64url").toString()).split(":"); const user = await ctx.prisma.user.findUnique({ @@ -29,10 +33,12 @@ const login = async (ctx: Context): Promise => { } }); - if (user === null) throw new TRPCError({ - code: "UNAUTHORIZED", - message: `No user found with username: ${username}` - }); + if (user === null) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: `No user found with username: ${username}` + }); + } let authorised = false; @@ -45,10 +51,12 @@ const login = async (ctx: Context): Promise => { }); } - if (!authorised) throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Incorrect password" - }); + if (!authorised) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Incorrect password" + }); + } const accessToken = generateToken(username, env.ACCESS_TOKEN_SECRET, "24h"); const refreshToken = generateToken(username, env.REFRESH_TOKEN_SECRET); @@ -70,10 +78,12 @@ const login = async (ctx: Context): Promise => { }; const getToken = async (ctx: Context) => { - if (ctx.user === null) throw new TRPCError({ - code: "UNAUTHORIZED", - message: "No credentials provided" - }); + if (ctx.user === null) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "No credentials provided" + }); + } const tokenRecord = await ctx.prisma.refreshToken.findUnique({ where: { token: ctx.user @@ -100,7 +110,8 @@ const getToken = async (ctx: Context) => { return generateToken(user.username, env.ACCESS_TOKEN_SECRET, "24h"); }; -const getUser = (prisma: PrismaClient, username: string) => prisma.user.findUniqueOrThrow({ where: { username } }); +const getUser = (prisma: PrismaClient, username: string) => + prisma.user.findUniqueOrThrow({ where: { username } }); const controller = { login, getToken, getUser }; diff --git a/apps/ove-core/src/server/auth/router.ts b/apps/ove-core/src/server/auth/router.ts index 85d7a69a..a44ecd94 100644 --- a/apps/ove-core/src/server/auth/router.ts +++ b/apps/ove-core/src/server/auth/router.ts @@ -17,7 +17,10 @@ export const authRouter = router({ login: procedure .meta({ openapi: { method: "POST", path: "/login" } }) .input(z.void()) - .output(z.union([OVEExceptionSchema, z.object({ access: z.string(), refresh: z.string() })])) + .output(z.union([OVEExceptionSchema, z.object({ + access: z.string(), + refresh: z.string() + })])) .mutation(async ({ ctx }) => { logger.info("Logging in user"); return safe(() => controller.login(ctx)); @@ -31,11 +34,11 @@ export const authRouter = router({ return safe(() => controller.getToken(ctx)); }), getUserID: protectedProcedure - .meta({ openapi: {method: "GET", path: "/user"} }) + .meta({ openapi: { method: "GET", path: "/user" } }) .input(z.void()) - .output(z.union([OVEExceptionSchema, z.object({id: z.string()})])) - .query(async ({ctx}) => { + .output(z.union([OVEExceptionSchema, z.object({ id: z.string() })])) + .query(async ({ ctx }) => { logger.info("Getting user"); return safe(() => controller.getUser(ctx.prisma, ctx.user)); }) -}); \ No newline at end of file +}); diff --git a/apps/ove-core/src/server/bridge/router.ts b/apps/ove-core/src/server/bridge/router.ts index caf19a61..d8aef43b 100644 --- a/apps/ove-core/src/server/bridge/router.ts +++ b/apps/ove-core/src/server/bridge/router.ts @@ -3,51 +3,59 @@ import { APIRoutes, type TAPIRoutes, type TBridgeService, - type TIsGet, + type TIsGet } from "@ove/ove-types"; import { state } from "../state"; import { io } from "./sockets"; import { assert } from "@ove/ove-utils"; import { logger } from "../../env"; -const getSocket = (socketId: string) => io.sockets.get(assert(state.bridgeClients.get(socketId))); - -const generateProcedure = (k: Key) => protectedProcedure - .meta(APIRoutes[k].meta) - .input(APIRoutes[k].input) - .output(APIRoutes[k].output); - -const generateQuery = (k: Key) => generateProcedure(k) - .query(({ input }) => { - if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); - const { bridgeId, ...args } = input; - logger.info(`Handling ${k}`); - - return new Promise(resolve => { - // @ts-ignore - getSocket(bridgeId).emit(k, args, resolve); +const getSocket = (socketId: string) => + io.sockets.get(assert(state.bridgeClients.get(socketId))); + +const generateProcedure = + (k: Key) => protectedProcedure + .meta(APIRoutes[k].meta) + .input(APIRoutes[k].input) + .output(APIRoutes[k].output); + +const generateQuery = + (k: Key) => generateProcedure(k) + .query(({ input }) => { + if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); + const { bridgeId, ...args } = input; + logger.info(`Handling ${k}`); + + return new Promise(resolve => { + // @ts-expect-error arg spread + getSocket(bridgeId).emit(k, args, resolve); + }); }); - }); - -const generateMutation = (k: Key) => generateProcedure(k) - .mutation(({ input }) => { - if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); - const { bridgeId, ...args } = input; - logger.info(`Handling ${k}`); - return new Promise(resolve => { - // @ts-ignore - getSocket(bridgeId).emit(k, args, resolve); +const generateMutation = + (k: Key) => generateProcedure(k) + .mutation(({ input }) => { + if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); + const { bridgeId, ...args } = input; + logger.info(`Handling ${k}`); + + return new Promise(resolve => { + // @ts-expect-error - arg spread + getSocket(bridgeId).emit(k, args, resolve); + }); }); - }); type Router = { - [Key in keyof TAPIRoutes]: TIsGet>, ReturnType>> + [Key in keyof TAPIRoutes]: + TIsGet>, + ReturnType>> } const routes: Router = Object.entries(APIRoutes).reduce((acc, [k, route]) => { - acc[k] = route.meta.openapi.method === "GET" ? generateQuery(k as keyof typeof APIRoutes) : generateMutation(k as keyof typeof APIRoutes); + acc[k] = route.meta.openapi.method === "GET" ? + generateQuery(k as keyof typeof APIRoutes) : + generateMutation(k as keyof typeof APIRoutes); return acc; }, >{}) as Router; -export const bridgeRouter = router(routes); \ No newline at end of file +export const bridgeRouter = router(routes); diff --git a/apps/ove-core/src/server/context.ts b/apps/ove-core/src/server/context.ts index b9b1b0e4..958b6968 100644 --- a/apps/ove-core/src/server/context.ts +++ b/apps/ove-core/src/server/context.ts @@ -4,10 +4,13 @@ import { } from "@trpc/server/dist/adapters/node-http"; import { prisma } from "./db"; -export const createContext = async ({ - req -}: NodeHTTPCreateContextFnOptions) => { - const user: string | null = req.headers.authorization ? (req.headers.authorization as string).split(" ").at(-1) ?? null : null; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ContextOptions = NodeHTTPCreateContextFnOptions + +export const createContext = async ({ req }: ContextOptions) => { + const user: string | null = req.headers.authorization ? + (req.headers.authorization as string).split(" ").at(-1) ?? null : + null; return { user, prisma diff --git a/apps/ove-core/src/server/core/controller.ts b/apps/ove-core/src/server/core/controller.ts index b4fd72b6..b5790116 100644 --- a/apps/ove-core/src/server/core/controller.ts +++ b/apps/ove-core/src/server/core/controller.ts @@ -22,17 +22,21 @@ const getObservatories = async (ctx: Context) => { }; const getObservatoryBounds = async (ctx: Context) => { - const observatories = (await getObservatories(ctx)).filter(({ isOnline }) => isOnline); - return (await Promise.all(observatories.map(async ({ name }) => io.sockets.get(state.bridgeClients.get(name)!)!.emitWithAck("getGeometry", {})))).reduce((acc, x) => { - if (isError(x.response) || x.response === undefined) return acc; - acc[x.meta.bridge] = x.response; - return acc; - }, >{}); + const observatories = + (await getObservatories(ctx)).filter(({ isOnline }) => isOnline); + return (await Promise.all(observatories.map(async ({ name }) => + io.sockets.get(state.bridgeClients.get(name)!)! + .emitWithAck("getGeometry", {})))) + .reduce((acc, x) => { + if (isError(x.response) || x.response === undefined) return acc; + acc[x.meta.bridge] = x.response; + return acc; + }, >{}); }; const controller = { diff --git a/apps/ove-core/src/server/core/router.ts b/apps/ove-core/src/server/core/router.ts index 38c6c916..b4a10bae 100644 --- a/apps/ove-core/src/server/core/router.ts +++ b/apps/ove-core/src/server/core/router.ts @@ -17,15 +17,21 @@ export const coreRouter = router({ .meta({ openapi: { method: "GET", path: "/bridges", protect: true } }) .input(z.void()) .output(z.union([ObservatorySchema.array(), OVEExceptionSchema])) - .query(async ({ctx}) => { + .query(async ({ ctx }) => { return safe(logger, () => controller.getObservatories(ctx)); }), getObservatoryBounds: protectedProcedure - .meta({openapi: {method: "GET", path: "/bridges/bounds", protect: true}}) + .meta({ + openapi: { + method: "GET", + path: "/bridges/bounds", + protect: true + } + }) .input(z.void()) .output(z.union([ObservatoryBoundsSchema, OVEExceptionSchema])) - .query(async ({ctx}) => { + .query(async ({ ctx }) => { logger.info("Getting observatory bounds"); return safe(logger, () => controller.getObservatoryBounds(ctx)); }) -}); \ No newline at end of file +}); diff --git a/apps/ove-core/src/server/db.ts b/apps/ove-core/src/server/db.ts index 0497aa4d..40499158 100644 --- a/apps/ove-core/src/server/db.ts +++ b/apps/ove-core/src/server/db.ts @@ -1,7 +1,9 @@ -import {PrismaClient} from "@prisma/client"; +/* global globalThis */ + +import { PrismaClient } from "@prisma/client"; import { env } from "../env"; -const globalForPrisma = globalThis as unknown as {prisma: PrismaClient}; +const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma || new PrismaClient(); if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/apps/ove-core/src/server/hardware/router.ts b/apps/ove-core/src/server/hardware/router.ts index 5828a359..fb28cee2 100644 --- a/apps/ove-core/src/server/hardware/router.ts +++ b/apps/ove-core/src/server/hardware/router.ts @@ -15,41 +15,48 @@ import { logger } from "../../env"; const getSocket: (socketId: string) => Socket< THardwareClientToServerEvents, THardwareServerToClientEvents -> = (socketId: string) => assert(io.sockets.get(assert(state.hardwareClients.get(socketId)))); +> = (socketId: string) => + assert(io.sockets.get(assert(state.hardwareClients.get(socketId)))); -const generateProcedure = (k: Key) => protectedProcedure - .meta(CoreAPI[k].meta) - .input(CoreAPI[k].args) - .output(CoreAPI[k].bridge); +const generateProcedure = + (k: Key) => protectedProcedure + .meta(CoreAPI[k].meta) + .input(CoreAPI[k].args) + .output(CoreAPI[k].bridge); -const generateQuery = (k: Key) => generateProcedure(k) - .query>(({ input }): Promise> => { - if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); - const { bridgeId, ...args } = input; - logger.info(`Handling: ${k}`); - // @ts-ignore - return getSocket(bridgeId).emitWithAck(k, args); - }); +const generateQuery = + (k: Key) => generateProcedure(k) + .query>(({ input }): Promise> => { + if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); + const { bridgeId, ...args } = input; + logger.info(`Handling: ${k}`); + // @ts-expect-error arg spread + return getSocket(bridgeId).emitWithAck(k, args); + }); -const generateMutation = (k: Key) => generateProcedure(k) - .mutation>(({ input }): Promise> => { - if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); - const { bridgeId, ...args } = input; - logger.info(`Handling: ${k}`); - // @ts-ignore - return getSocket(bridgeId).emitWithAck(k, args); - }); +const generateMutation = + (k: Key) => generateProcedure(k) + .mutation>( + ({ input }): Promise> => { + if (input === undefined) throw new Error("ILLEGAL UNDEFINED"); + const { bridgeId, ...args } = input; + logger.info(`Handling: ${k}`); + // @ts-expect-error arg spread + return getSocket(bridgeId).emitWithAck(k, args); + }); export type CoreRouter = { - [Key in keyof TCoreAPI]: TCoreAPI[Key]["meta"]["openapi"]["method"] extends "GET" ? - ReturnType> : ReturnType> + [Key in keyof TCoreAPI]: + TCoreAPI[Key]["meta"]["openapi"]["method"] extends "GET" ? + ReturnType> : + ReturnType> } const routes: CoreRouter = Object.entries(CoreAPI).reduce((acc, [k, route]) => { - acc[k] = route.meta.openapi.method === "GET" - ? generateQuery(k as keyof typeof CoreAPI) - : generateMutation(k as keyof typeof CoreAPI); + acc[k] = route.meta.openapi.method === "GET" ? + generateQuery(k as keyof typeof CoreAPI) : + generateMutation(k as keyof typeof CoreAPI); return acc; }, <{ [key: string]: unknown }>{}) as CoreRouter; -export const hardwareRouter = router(routes); \ No newline at end of file +export const hardwareRouter = router(routes); diff --git a/apps/ove-core/src/server/projects/controller.ts b/apps/ove-core/src/server/projects/controller.ts index d6c09d53..48ae94e4 100644 --- a/apps/ove-core/src/server/projects/controller.ts +++ b/apps/ove-core/src/server/projects/controller.ts @@ -21,7 +21,11 @@ const getProjectsForUser = async (prisma: PrismaClient, username: string) => { }); }; -const getProject = async (prisma: PrismaClient, username: string, id: string) => { +const getProject = async ( + prisma: PrismaClient, + username: string, + id: string +) => { const user = await prisma.user.findUnique({ where: { username } }); return prisma.project.findUnique({ @@ -39,7 +43,7 @@ const getProject = async (prisma: PrismaClient, username: string, id: string) => const getTagsForUser = async (prisma: PrismaClient, username: string) => { const projects = await getProjectsForUser(prisma, username); - return projects.flatMap(({tags}) => tags); + return projects.flatMap(({ tags }) => tags); }; const getUsers = async (prisma: PrismaClient) => prisma.user.findMany(); @@ -52,13 +56,19 @@ const getInvitesForProject = async (prisma: PrismaClient, projectId: string) => }); const getGlobalFiles = async (): ReturnType => { - if (env.NODE_ENV === "production") throw new Error("NOT IMPLEMENTED"); // TODO: implement + // TODO: implement + if (env.NODE_ENV === "production") throw new Error("NOT IMPLEMENTED"); return []; }; const getFiles = async () => { - if (env.NODE_ENV === "production") throw new Error("NOT IMPLEMENTED"); // TODO: implement - return (await getGlobalFiles()).map(x => ({...x, isGlobal: true})).concat([]); + // TODO: implement + if (env.NODE_ENV === "production") throw new Error("NOT IMPLEMENTED"); + return (await getGlobalFiles()).map(x => ({ + ...x, + isGlobal: true, + useLatest: false + })).concat([]); }; const controller: Controller = { diff --git a/apps/ove-core/src/server/projects/router.ts b/apps/ove-core/src/server/projects/router.ts index 50379878..769e1b49 100644 --- a/apps/ove-core/src/server/projects/router.ts +++ b/apps/ove-core/src/server/projects/router.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { safe } from "@ove/ove-utils"; import { logger } from "../../env"; import controller from "./controller"; -import { OVEExceptionSchema } from "@ove/ove-types"; +import { FileSchema, OVEExceptionSchema } from "@ove/ove-types"; import { PrismaClient, type Invite, @@ -65,23 +65,16 @@ const InviteSchema: z.ZodType = z.strictObject({ recipientId: z.string() }); -const FileSchema = z.strictObject({ - name: z.string(), - assetId: z.string(), - version: z.number(), - isGlobal: z.boolean() -}); - export type Controller = { - getProjectsForUser: (prisma: PrismaClient, user: string) => Promise<(Project & { - layout: Section[] - })[]> - getProject: (prisma: PrismaClient, user: string, id: string) => Promise<(Project & { - layout: Section[] - }) | null> - getTagsForUser: (prisma: PrismaClient, user: string) => Promise + getProjectsForUser: (prisma: PrismaClient, user: string) => + Promise<(Project & { layout: Section[] })[]> + getProject: (prisma: PrismaClient, user: string, id: string) => + Promise<(Project & { layout: Section[] }) | null> + getTagsForUser: (prisma: PrismaClient, user: string) => + Promise getUsers: (prisma: PrismaClient) => Promise - getInvitesForProject: (prisma: PrismaClient, projectId: string) => Promise + getInvitesForProject: (prisma: PrismaClient, projectId: string) => + Promise getFiles: (projectId: string) => Promise[]> } @@ -92,7 +85,8 @@ export const projectsRouter = router({ .output(z.union([OVEExceptionSchema, ProjectSchema.array()])) .query(async ({ ctx }) => { logger.info(`Getting projects for user ${ctx.user}`); - return await safe(logger, () => controller.getProjectsForUser(ctx.prisma, ctx.user)); + return await safe(logger, () => + controller.getProjectsForUser(ctx.prisma, ctx.user)); }), getProject: protectedProcedure .meta({ method: "GET", path: "/project/{projectId}", protected: true }) @@ -100,7 +94,8 @@ export const projectsRouter = router({ .output(z.union([OVEExceptionSchema, ProjectSchema.nullable()])) .query(async ({ ctx, input: { projectId } }) => { logger.info(`Getting project ${projectId}`); - return await safe(logger, () => controller.getProject(ctx.prisma, ctx.user, projectId)); + return await safe(logger, () => + controller.getProject(ctx.prisma, ctx.user, projectId)); }), getTags: protectedProcedure .meta({ method: "GET", path: "/projects/tags", protected: true }) @@ -108,7 +103,8 @@ export const projectsRouter = router({ .output(z.union([OVEExceptionSchema, z.string().array()])) .query(async ({ ctx }) => { logger.info("Getting tags for projects"); - return await safe(logger, () => controller.getTagsForUser(ctx.prisma, ctx.user)); + return await safe(logger, () => + controller.getTagsForUser(ctx.prisma, ctx.user)); }), getUsers: protectedProcedure .meta({ method: "GET", path: "/users", protected: true }) @@ -128,7 +124,8 @@ export const projectsRouter = router({ .output(z.union([InviteSchema.array(), OVEExceptionSchema])) .query(async ({ ctx, input: { projectId } }) => { logger.info(`Getting invites for ${projectId}`); - return await safe(logger, () => controller.getInvitesForProject(ctx.prisma, projectId)); + return await safe(logger, () => + controller.getInvitesForProject(ctx.prisma, projectId)); }), getFiles: protectedProcedure .meta({ @@ -148,7 +145,12 @@ export const projectsRouter = router({ path: "/project/{projectId}/file", protected: true }) - .input(z.strictObject({projectId: z.string(), name: z.string(), version: z.number(), assetId: z.string()})) + .input(z.strictObject({ + projectId: z.string(), + name: z.string(), + version: z.number(), + assetId: z.string() + })) .output(z.union([z.string(), OVEExceptionSchema])) .query(async ({ input }) => { logger.info(`Getting data for ${input.name} v${input.version}`); diff --git a/apps/ove-core/src/server/router.ts b/apps/ove-core/src/server/router.ts index e003be50..42ee1009 100644 --- a/apps/ove-core/src/server/router.ts +++ b/apps/ove-core/src/server/router.ts @@ -12,7 +12,6 @@ const baseRouter = router({ projects: projectsRouter }); -// @ts-ignore export const appRouter = mergeRouters(baseRouter, authRouter); export type AppRouter = typeof appRouter; diff --git a/apps/ove-core/src/server/s3.ts b/apps/ove-core/src/server/s3.ts new file mode 100644 index 00000000..e50434ae --- /dev/null +++ b/apps/ove-core/src/server/s3.ts @@ -0,0 +1,36 @@ +/* global globalThis */ + +import Minio from "minio"; +import { env } from "../env"; + +const globalForS3 = globalThis as unknown as {s3: Minio.Client | null}; + +const createMinio = () => { + if (globalForS3.s3 !== undefined) return globalForS3.s3; + if (env.ASSET_STORE_CONFIG === undefined) return null; + return new Minio.Client({ + endPoint: env.ASSET_STORE_CONFIG.END_POINT, + port: env.ASSET_STORE_CONFIG.PORT, + useSSL: env.ASSET_STORE_CONFIG.USE_SSL, + accessKey: env.ASSET_STORE_CONFIG.ACCESS_KEY, + secretKey: env.ASSET_STORE_CONFIG.SECRET_KEY + }); +}; +export const s3 = createMinio(); + +if (env.NODE_ENV !== "production") globalForS3.s3 = s3; + +const listObjects = (s3_: Minio.Client, bucketName: string) => + new Promise<(Minio.BucketItem & {versionId: string})[]>((resolve, reject) => { + const stream = s3_ + // @ts-expect-error – missing optional arguments parameter in library type + .listObjects(bucketName, "", true, { IncludeVersion: true }); + const data: (Minio.BucketItem & {versionId: string})[] = []; + stream.on("data", obj => data.push(obj as typeof data[0])); + stream.on("end", () => resolve(data)); + stream.on("error", err => reject(err)); + }); + +export const S3Controller = { + listObjects +}; diff --git a/apps/ove-core/src/server/socket-setup.ts b/apps/ove-core/src/server/socket-setup.ts index d31749fb..c640b9b1 100644 --- a/apps/ove-core/src/server/socket-setup.ts +++ b/apps/ove-core/src/server/socket-setup.ts @@ -1,7 +1,11 @@ import { type Namespace } from "socket.io"; import { prisma } from "./db"; +import { logger } from "../env"; -export const setupNamespace = (io: T, clients: Map) => { +export const setupNamespace = ( + io: T, + clients: Map +) => { io.use((socket, next) => { const { username, password } = socket.handshake.auth; prisma.user.findUnique({ @@ -18,12 +22,14 @@ export const setupNamespace = (io: T, clients: Map { - console.log(`Socket ID: ${socket.handshake.auth.username} connected via ${io.name}`); + logger.info(`Socket ID: ${socket.handshake.auth.username} + connected via ${io.name}`); clients.set(socket.handshake.auth.username, socket.id); socket.on("disconnect", reason => { - console.log(`${socket.handshake.auth.username} disconnected with reason: ${reason}`); + logger.info(`${socket.handshake.auth.username} + disconnected with reason: ${reason}`); clients.delete(socket.handshake.auth.username); }); }); -} +}; diff --git a/apps/ove-core/src/server/sockets.ts b/apps/ove-core/src/server/sockets.ts index f65befc7..424ecbdc 100644 --- a/apps/ove-core/src/server/sockets.ts +++ b/apps/ove-core/src/server/sockets.ts @@ -1,3 +1,5 @@ +/* global process */ + import { Server, type ServerOptions } from "socket.io"; import { type THardwareServerToClientEvents, @@ -5,7 +7,7 @@ import { } from "@ove/ove-types"; import { server } from "./app"; import { instrument } from "@socket.io/admin-ui"; -import { env } from "../env"; +import { env, logger } from "../env"; import { Parser } from "@ove/ove-utils"; export const io: Server = new Server< @@ -13,15 +15,26 @@ export const io: Server = new Server< THardwareServerToClientEvents >( server, - { cors: { origin: "*", methods: ["GET", "POST", "DELETE"] }, credentials: false, parser: Parser } as Partial + { + cors: { origin: "*", methods: ["GET", "POST", "DELETE"] }, + credentials: false, + parser: Parser + } as Partial ); -const auth = env.SOCKET_ADMIN === undefined ? false : {type: "basic" as const, username: env.SOCKET_ADMIN.USERNAME, password: env.SOCKET_ADMIN.PASSWORD} -instrument(io, {auth, mode: process.env.NODE_ENV === "production" ? "production" : "development"}); +const auth = env.SOCKET_ADMIN === undefined ? false : { + type: "basic" as const, + username: env.SOCKET_ADMIN.USERNAME, + password: env.SOCKET_ADMIN.PASSWORD +}; +instrument(io, { + auth, + mode: process.env.NODE_ENV === "production" ? "production" : "development" +}); io.on("connection", socket => { - console.log(`New client connected: ${socket.id}`); + logger.info(`New client connected: ${socket.id}`); socket.on("disconnect", reason => - console.log(`${socket.id} disconnecting with reason: ${reason}`)); + logger.info(`${socket.id} disconnecting with reason: ${reason}`)); }); diff --git a/apps/ove-core/src/server/state.ts b/apps/ove-core/src/server/state.ts index 1f45d30e..cdd2654c 100644 --- a/apps/ove-core/src/server/state.ts +++ b/apps/ove-core/src/server/state.ts @@ -1,4 +1,4 @@ export const state = { hardwareClients: new Map(), bridgeClients: new Map() -}; \ No newline at end of file +}; diff --git a/apps/ove-core/src/server/trpc.ts b/apps/ove-core/src/server/trpc.ts index bdd1d2a3..94bff090 100644 --- a/apps/ove-core/src/server/trpc.ts +++ b/apps/ove-core/src/server/trpc.ts @@ -1,11 +1,14 @@ import { env } from "../env"; -import superjson from "superjson" +import superjson from "superjson"; import * as jwt from "jsonwebtoken"; import { type Context } from "./context"; import { type OpenApiMeta } from "trpc-openapi"; import { initTRPC, TRPCError } from "@trpc/server"; -const trpc = initTRPC.meta().context().create({transformer: superjson}); +const trpc = initTRPC + .meta() + .context() + .create({ transformer: superjson }); export const router = trpc.router; export const procedure = trpc.procedure; @@ -14,19 +17,24 @@ export const mergeRouters = trpc.mergeRouters; export const middleware = trpc.middleware; -const isAuthed = middleware((opts) => { +const isAuthed = middleware(opts => { const { ctx } = opts; - if (env.DISABLE_AUTH) return opts.next({ - ctx: { user: env.TEST_USER ?? "" } - }); + if (env.DISABLE_AUTH) { + return opts.next({ + ctx: { user: env.TEST_USER ?? "" } + }); + } - if (ctx.user === null || env.ACCESS_TOKEN_SECRET === undefined) throw new TRPCError({ code: "UNAUTHORIZED" }); + if (ctx.user === null || env.ACCESS_TOKEN_SECRET === undefined) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } let user: { username: string }; try { - user = (jwt.verify(ctx.user, env.ACCESS_TOKEN_SECRET) as unknown as {username: string}); + user = (jwt.verify(ctx.user, env.ACCESS_TOKEN_SECRET) as + unknown as { username: string }); } catch (e) { throw new TRPCError({ code: "UNAUTHORIZED" }); } diff --git a/libs/mdc-control/src/lib/mdc-control.ts b/libs/mdc-control/src/lib/mdc-control.ts index a752637b..d2c152e9 100644 --- a/libs/mdc-control/src/lib/mdc-control.ts +++ b/libs/mdc-control/src/lib/mdc-control.ts @@ -1,5 +1,7 @@ +/* global setTimeout */ + import { io } from "socket.io-client"; -import { MDCSources, OVEException } from "@ove/ove-types"; +import { type MDCSources, type OVEException } from "@ove/ove-types"; import { raise } from "@ove/ove-utils"; const TIMEOUT = 5_000; @@ -57,22 +59,76 @@ const sendCommand = ( }); }; -export const getStatus = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x00)); +export const getStatus = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x00)); -export const setPower = (id: number, ip: string, port: number, state: "on" | "off"): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x11, state === "off" ? 0 : 1)); +export const setPower = ( + id: number, + ip: string, + port: number, + state: "on" | "off" +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x11, state === "off" ? 0 : 1)); -export const getPower = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x11)); +export const getPower = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x11)); -export const setVolume = (id: number, ip: string, port: number, volume: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x12, volume)); +export const setVolume = ( + id: number, + ip: string, + port: number, + volume: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x12, volume)); -export const getVolume = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x12)); +export const getVolume = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x12)); -export const setIsMute = (id: number, ip: string, port: number, state: boolean): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x13, +state)); +export const setIsMute = ( + id: number, + ip: string, + port: number, + state: boolean +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x13, +state)); -export const getIsMute = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x13)); +export const getIsMute = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x13)); -export const setSource = (id: number, ip: string, port: number, source: MDCSource) => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x14, source)); +export const setSource = ( + id: number, + ip: string, + port: number, + source: MDCSource +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x14, source)); -export const getSource = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x14)); +export const getSource = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x14)); -export const getModel = (id: number, ip: string, port: number): Promise => new Promise(resolve => sendCommand(resolve, id, ip, port, 0x10)); +export const getModel = ( + id: number, + ip: string, + port: number +): Promise => new Promise(resolve => + sendCommand(resolve, id, ip, port, 0x10)); diff --git a/libs/ove-logging/src/index.ts b/libs/ove-logging/src/index.ts index ee6791da..29ec8987 100644 --- a/libs/ove-logging/src/index.ts +++ b/libs/ove-logging/src/index.ts @@ -1 +1 @@ -export * from './lib/ove-logging'; +export * from "./lib/ove-logging"; diff --git a/libs/ove-logging/src/lib/constants.ts b/libs/ove-logging/src/lib/constants.ts index 11526f66..c6f1badb 100644 --- a/libs/ove-logging/src/lib/constants.ts +++ b/libs/ove-logging/src/lib/constants.ts @@ -1,4 +1,5 @@ -import { LogLevel } from "../../../ove-utils/src/lib/types"; +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries +import { type LogLevel } from "@ove/ove-utils"; export type ConstantsType = { UNKNOWN_APP_ID: string @@ -67,4 +68,4 @@ const Constants: ConstantsType = ({ DEFAULT_LOG_LEVEL: -1 }); -export default Constants; \ No newline at end of file +export default Constants; diff --git a/libs/ove-logging/src/lib/ove-logging.ts b/libs/ove-logging/src/lib/ove-logging.ts index 0b9ec9a8..7d10291f 100644 --- a/libs/ove-logging/src/lib/ove-logging.ts +++ b/libs/ove-logging/src/lib/ove-logging.ts @@ -1,6 +1,4 @@ -// global fetch - -declare const fetch: any; +/* global console, fetch */ import chalk from "chalk"; import format from "date-fns/format"; @@ -16,7 +14,11 @@ export type LogLevel = { } } -export const Logger = (name?: string, logLevel?: number, loggingServerURL?: string) => { +export const Logger = ( + name?: string, + logLevel?: number, + loggingServerURL?: string +) => { const logLevel_ = logLevel ?? Constants.DEFAULT_LOG_LEVEL; const name_: string = name ?? Constants.UNKNOWN_APP_ID; @@ -25,6 +27,7 @@ export const Logger = (name?: string, logLevel?: number, loggingServerURL?: stri .hex(logLevel.label.color) .bold; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const buildLogMessage = (logLevel: LogLevel, ...args: any[]): string[] => { const whitespace = logLevel.name.length === 4 ? " " : ""; const logLabel = getLogLabel(logLevel)(`[${logLevel.name}]`); @@ -34,6 +37,7 @@ export const Logger = (name?: string, logLevel?: number, loggingServerURL?: stri .concat(Object.values(args)); }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const log = (level: LogLevel, ...args: any[]): void => { if (logLevel_ > level.level) return; @@ -44,7 +48,8 @@ export const Logger = (name?: string, logLevel?: number, loggingServerURL?: stri fetch(loggingServerURL, { method: "POST", body: message.join(" ") - }).catch(() => {}); + }).catch(() => { + }); } switch (level.consoleLogger) { @@ -67,13 +72,19 @@ export const Logger = (name?: string, logLevel?: number, loggingServerURL?: stri }; return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any fatal: (...args: any[]) => log(Constants.LogLevels["fatal"], ...args), + // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (...args: any[]) => log(Constants.LogLevels["error"], ...args), + // eslint-disable-next-line @typescript-eslint/no-explicit-any warn: (...args: any[]) => log(Constants.LogLevels["warn"], ...args), + // eslint-disable-next-line @typescript-eslint/no-explicit-any info: (...args: any[]) => log(Constants.LogLevels["info"], ...args), + // eslint-disable-next-line @typescript-eslint/no-explicit-any debug: (...args: any[]) => log(Constants.LogLevels["debug"], ...args), + // eslint-disable-next-line @typescript-eslint/no-explicit-any trace: (...args: any[]) => log(Constants.LogLevels["trace"], ...args) }; }; -export type TLogger = ReturnType \ No newline at end of file +export type TLogger = ReturnType diff --git a/libs/ove-server-utils/src/index.ts b/libs/ove-server-utils/src/index.ts index 26f68588..9c00e8a0 100644 --- a/libs/ove-server-utils/src/index.ts +++ b/libs/ove-server-utils/src/index.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import * as all from "@ove/ove-utils"; import { safeFetch } from "./lib/ove-fetch"; import * as fileUtils from "./lib/ove-file-utils"; diff --git a/libs/ove-server-utils/src/lib/ove-env-utils.ts b/libs/ove-server-utils/src/lib/ove-env-utils.ts index 56b7cae4..2e376916 100644 --- a/libs/ove-server-utils/src/lib/ove-env-utils.ts +++ b/libs/ove-server-utils/src/lib/ove-env-utils.ts @@ -1,22 +1,29 @@ +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { DeepProxy, Json } from "@ove/ove-utils"; import { readFile, safeWriteFile } from "./ove-file-utils"; import { z } from "zod"; -const updateConfig = (configPath: string, defaultConfig: object, expectedKeys: string[]): { +const updateConfig = ( + configPath: string, + defaultConfig: object, + expectedKeys: string[] +): { rawConfig: object, isUpdate: boolean } | null => { let isUpdate = false; - const rawConfig = readFile>(configPath, Json.stringify(defaultConfig, undefined, 2)); + const rawConfig = readFile>( + configPath, Json.stringify(defaultConfig, undefined, 2)); if (rawConfig === null) return null; - for (let key of Object.keys(defaultConfig)) { + for (const key of Object.keys(defaultConfig)) { if (key in rawConfig) continue; isUpdate = true; - rawConfig[key as keyof typeof defaultConfig] = defaultConfig[key as keyof typeof defaultConfig]; + rawConfig[key as keyof typeof defaultConfig] = + defaultConfig[key as keyof typeof defaultConfig]; } - for (let key of Object.keys(rawConfig)) { + for (const key of Object.keys(rawConfig)) { if (expectedKeys.includes(key)) continue; isUpdate = true; delete rawConfig[key]; @@ -28,7 +35,11 @@ const updateConfig = (configPath: string, defaultConfig: object, expectedKeys: s }; }; -const saveConfig = >(configPath: string, updatedEnv: T, excludeKeys: string[]) => { +const saveConfig = >( + configPath: string, + updatedEnv: T, + excludeKeys: string[] +) => { const excludedEnv = {} as T; (Object.keys(updatedEnv) as Array).forEach(k => { if (excludeKeys.includes(k as string)) return; @@ -38,28 +49,47 @@ const saveConfig = >(configPath: string, updat safeWriteFile(configPath, Json.stringify(excludedEnv), true); }; -export const setupConfigWithRefinement = (configPath: string, defaultConfig: T, schema: z.ZodObject | z.ZodEffects>, staticConfig: V, keys: string[]) => { - const config = updateConfig( - configPath, - defaultConfig, - keys - ); +export const setupConfigWithRefinement = + ( + configPath: string, + defaultConfig: T, + schema: z.ZodObject | z.ZodEffects>, + staticConfig: V, + keys: string[] + ) => { + const config = updateConfig( + configPath, + defaultConfig, + keys + ); - if (config === null) throw new Error("Error retrieving configuration"); - const { rawConfig, isUpdate } = config; + if (config === null) throw new Error("Error retrieving configuration"); + const { rawConfig, isUpdate } = config; - type Environment = z.infer & V + type Environment = z.infer & V - const env: Environment = DeepProxy({ - ...schema.parse(rawConfig), - ...staticConfig - }, () => saveConfig(configPath, Json.copy(env), Object.keys(staticConfig))); + const env: Environment = DeepProxy({ + ...schema.parse(rawConfig), + ...staticConfig + }, () => saveConfig(configPath, Json.copy(env), Object.keys(staticConfig))); - if (isUpdate) { - saveConfig(configPath, env, Object.keys(staticConfig)); - } + if (isUpdate) { + saveConfig(configPath, env, Object.keys(staticConfig)); + } - return env; -} + return env; + }; -export const setupConfig = (configPath: string, defaultConfig: T, schema: z.ZodObject, staticConfig: V) => setupConfigWithRefinement(configPath, defaultConfig, schema, staticConfig, Object.keys(schema.shape)); \ No newline at end of file +export const setupConfig = + ( + configPath: string, + defaultConfig: T, + schema: z.ZodObject, + staticConfig: V + ) => setupConfigWithRefinement( + configPath, + defaultConfig, + schema, + staticConfig, + Object.keys(schema.shape) + ); diff --git a/libs/ove-server-utils/src/lib/ove-fetch.ts b/libs/ove-server-utils/src/lib/ove-fetch.ts index ba498a9a..787d2306 100644 --- a/libs/ove-server-utils/src/lib/ove-fetch.ts +++ b/libs/ove-server-utils/src/lib/ove-fetch.ts @@ -1,7 +1,17 @@ -import {type RequestInfo, type RequestInit, default as nodeFetch} from "node-fetch"; -import {z} from "zod"; +/* global URL */ -export const safeFetch = async (url: RequestInfo | URL, schema: T, args?: RequestInit): Promise | null> => { +import { + type RequestInfo, + type RequestInit, + default as nodeFetch +} from "node-fetch"; +import { z } from "zod"; + +export const safeFetch = async ( + url: RequestInfo | URL, + schema: T, + args?: RequestInit +): Promise | null> => { try { const raw = await nodeFetch(url, args); const res = await raw.json(); @@ -11,4 +21,4 @@ export const safeFetch = async (url: RequestInfo | URL, sche } catch (_e) { return null; } -}; \ No newline at end of file +}; diff --git a/libs/ove-server-utils/src/lib/ove-file-utils.ts b/libs/ove-server-utils/src/lib/ove-file-utils.ts index caa7e453..43d9a817 100644 --- a/libs/ove-server-utils/src/lib/ove-file-utils.ts +++ b/libs/ove-server-utils/src/lib/ove-file-utils.ts @@ -1,9 +1,13 @@ import * as fs from "fs"; import * as path from "path"; +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { Json } from "@ove/ove-utils"; import { readFileSync, writeFileSync } from "atomically"; -export const readFile = >(filePath: string, defaultAsset: string | null = null): T | null => { +export const readFile = >( + filePath: string, + defaultAsset: string | null = null +): T | null => { try { if (!exists(filePath) && defaultAsset !== null) { writeFileSync(filePath, defaultAsset); @@ -15,7 +19,11 @@ export const readFile = >(filePath: string, defaultAs } }; -export const safeWriteFile = (path: string, data: string, overwrite: boolean): boolean | null => { +export const safeWriteFile = ( + path: string, + data: string, + overwrite: boolean +): boolean | null => { if (overwrite) { try { writeFileSync(path, data); @@ -60,5 +68,9 @@ export const exists = (path: string) => { }; export const saveSwagger = (filename: string, swagger: object) => { - safeWriteFile(path.join("docs", "api", filename), Json.stringify(swagger, undefined, 2), true); + safeWriteFile( + path.join("docs", "api", filename), + Json.stringify(swagger, undefined, 2), + true + ); }; diff --git a/libs/ove-types/.eslintrc.json b/libs/ove-types/.eslintrc.json index 9d9c0db5..d6920bef 100644 --- a/libs/ove-types/.eslintrc.json +++ b/libs/ove-types/.eslintrc.json @@ -4,7 +4,16 @@ "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "rules": { + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + } }, { "files": ["*.ts", "*.tsx"], diff --git a/libs/ove-types/helpers.d.ts b/libs/ove-types/helpers.d.ts new file mode 100644 index 00000000..da245ca5 --- /dev/null +++ b/libs/ove-types/helpers.d.ts @@ -0,0 +1,36 @@ +/* global jest */ + +declare type Benchmark = { + instantiations: number + time: number +}; + +declare type LogFn = ReturnType; + +declare function init(): void; + +declare function formatOutput(log: LogFn): Benchmark; + +declare function readFile(): BenchmarkEntries; + +declare type BenchmarkEntries = { + "ove-types"?: { + "ove-types"?: Record + "hardware/"?: { + bridge?: Record + "bridge-transform"?: Record + client?: Record + "client-transform"?: Record + core?: Record + "core-transform"?: Record + service?: Record + }, + hardware?: Record + "bridge/"?: { + service?: Record + } + "projects/"?: { + projects?: Record + } + } +} diff --git a/libs/ove-types/helpers.js b/libs/ove-types/helpers.js new file mode 100644 index 00000000..377c6977 --- /dev/null +++ b/libs/ove-types/helpers.js @@ -0,0 +1,44 @@ +/** @type {(log: ReturnType) => + * {time: number, instantiations: number}} + */ +import {readFileSync, writeFileSync} from 'atomically'; +import fs from 'fs'; + +global.formatOutput = log => { + const results = log + .mock + .calls + .map(x => x[0]) + .filter(o => o.includes('Result: ')) + .map(o => o.match(/Result: ([\d|.]+)/)[1]); + return { + time: parseFloat(results[0]), + instantiations: parseInt(results[1]) + }; +}; + +global.init = () => { + console.log = jest.fn(); + console.group = jest.fn(); + console.error = jest.fn(); +}; + +const exists = (path) => { + try { + fs.statSync(path); + return true; + } catch (e) { + return false; + } +}; + +global.readFile = () => { + const filePath = './benchmarks.json'; + const defaultAsset = JSON.stringify({}); + + if (!exists(filePath) && defaultAsset !== null) { + writeFileSync(filePath, defaultAsset); + } + + return JSON.parse(readFileSync(filePath).toString()); +}; \ No newline at end of file diff --git a/libs/ove-types/jest.config.ts b/libs/ove-types/jest.config.ts index e3b680dc..c01c21df 100644 --- a/libs/ove-types/jest.config.ts +++ b/libs/ove-types/jest.config.ts @@ -3,6 +3,7 @@ export default { displayName: 'ove-types', preset: '../../jest.preset.js', globals: {}, + setupFiles: ["./helpers.js"], transform: { '^.+\\.[tj]sx?$': [ 'ts-jest', @@ -19,4 +20,5 @@ export default { ], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/ove-types', + workerIdleMemoryLimit: "512MB" }; diff --git a/libs/ove-types/src/index.ts b/libs/ove-types/src/index.ts index 4a116970..30b3cedb 100644 --- a/libs/ove-types/src/index.ts +++ b/libs/ove-types/src/index.ts @@ -4,4 +4,7 @@ export * from "./lib/hardware/client"; export * from "./lib/hardware/bridge"; export * from "./lib/hardware/core"; export * from "./lib/bridge/service"; -export {type OpenAPIMethod, type APIExposureLevel} from "./lib/hardware/service"; +export * from "./lib/projects/projects"; +export { + type OpenAPIMethod, type APIExposureLevel +} from "./lib/hardware/service"; diff --git a/libs/ove-types/src/lib/bridge/service.spec.ts b/libs/ove-types/src/lib/bridge/service.spec.ts index bb15b30f..3bc7e577 100644 --- a/libs/ove-types/src/lib/bridge/service.spec.ts +++ b/libs/ove-types/src/lib/bridge/service.spec.ts @@ -1,38 +1,29 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; -import { type TAPIRoutes } from "@ove/ove-types"; - -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; +import { type TAPIRoutes } from "./service"; describe("service types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("getGeometry", () => { init(); - bench("getGeometry", () => ({}) as TAPIRoutes["getGeometry"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getGeometry"] = formatOutput(); + bench("getGeometry", () => ({}) as TAPIRoutes["getGeometry"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getGeometry"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("bridge/" in existing["ove-types"])) existing["ove-types"]["bridge/"] = {}; + if (!("bridge/" in existing["ove-types"]!)) { + existing["ove-types"]!["bridge/"] = {}; + } - existing["ove-types"]["bridge/"]["service"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["bridge/"]!["service"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/bridge/service.ts b/libs/ove-types/src/lib/bridge/service.ts index 21d9bc74..01ec4166 100644 --- a/libs/ove-types/src/lib/bridge/service.ts +++ b/libs/ove-types/src/lib/bridge/service.ts @@ -21,7 +21,9 @@ import { /* Utility Types */ export type InboundAPI = { - [Key in keyof TAPIRoutes]: (args: Omit, "bridgeId">) => Promise["response"]>> + [Key in keyof TAPIRoutes]: ( + args: Omit, "bridgeId"> + ) => Promise["response"]>> } export type APIController = Omit @@ -29,25 +31,39 @@ export type APIController = Omit export const excludeKeys: readonly (keyof TAPIRoutes)[] = ["getPublicKey"]; export type TBridgeService = { - [Key in keyof TAPIRoutes]: (args: Omit, "bridgeId">) => z.infer["response"] + [Key in keyof TAPIRoutes]: ( + args: Omit, "bridgeId"> + ) => z.infer["response"] } -export type TParameters = Parameters[0] -export type TCallback = (response: TBridgeResponse>>) => void +export type TParameters = + Parameters[0] +export type TCallback = ( + response: TBridgeResponse>> +) => void export type TBridgeController = { - [Key in keyof APIController]: (args: TParameters) => Promise>>> + [Key in keyof APIController]: (args: TParameters) => + Promise>>> } export type TSocketOutEvents = { - [Key in keyof APIController]: (args: TParameters, callback: TCallback) => void + [Key in keyof APIController]: ( + args: TParameters, + callback: TCallback + ) => void } -export type TSocketInEvents = {} +export type TSocketInEvents = Record type TGet = "GET" -export type TIsGet = TAPIRoutes[Key]["meta"]["openapi"]["method"] extends TGet ? T : U +export type TIsGet = + TAPIRoutes[Key]["meta"]["openapi"]["method"] extends TGet ? T : U -const EnvSchema = z.strictObject({ bridgeName: z.string().optional(), coreURL: z.string().optional(), calendarURL: z.string().optional() }); +const EnvSchema = z.strictObject({ + bridgeName: z.string().optional(), + coreURL: z.string().optional(), + calendarURL: z.string().optional() +}); export const APIRoutes = { getDevice: { @@ -70,7 +86,8 @@ export const APIRoutes = { } }, input: z.strictObject({ tag: z.string().optional(), bridgeId: z.string() }), - output: getBridgeResponseSchema(getDeviceResponseSchema(z.array(DeviceSchema))) + output: getBridgeResponseSchema( + getDeviceResponseSchema(z.array(DeviceSchema))) }, addDevice: { meta: { @@ -125,7 +142,8 @@ export const APIRoutes = { } }, input: z.strictObject({ bridgeId: z.string() }), - output: getBridgeResponseSchema(getDeviceResponseSchema(z.array(z.string()).optional())) + output: getBridgeResponseSchema( + getDeviceResponseSchema(z.array(z.string()).optional())) }, getCalendar: { meta: { @@ -136,7 +154,8 @@ export const APIRoutes = { } }, input: z.strictObject({ bridgeId: z.string() }), - output: getBridgeResponseSchema(getDeviceResponseSchema(CalendarSchema.optional())) + output: getBridgeResponseSchema( + getDeviceResponseSchema(CalendarSchema.optional())) }, getSocketStatus: { meta: { @@ -179,7 +198,10 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({ bridgeId: z.string(), ecoSchedule: z.array(CalendarEventSchema) }), + input: z.strictObject({ + bridgeId: z.string(), + ecoSchedule: z.array(CalendarEventSchema) + }), output: getBridgeResponseSchema(getDeviceResponseSchema(z.void())) }, setAutoSchedule: { @@ -190,7 +212,10 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({ bridgeId: z.string(), autoSchedule: AutoScheduleSchema.optional() }), + input: z.strictObject({ + bridgeId: z.string(), + autoSchedule: AutoScheduleSchema.optional() + }), output: getBridgeResponseSchema(getDeviceResponseSchema(z.void())) }, getEnv: { @@ -212,7 +237,7 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}).merge(EnvSchema), + input: z.strictObject({ bridgeId: z.string() }).merge(EnvSchema), output: getBridgeResponseSchema(getDeviceResponseSchema(z.void())) }, registerAuth: { @@ -223,7 +248,11 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string(), id: z.string(), pin: z.string()}), + input: z.strictObject({ + bridgeId: z.string(), + id: z.string(), + pin: z.string() + }), output: getBridgeResponseSchema(getDeviceResponseSchema(z.void().promise())) }, getDevicesToAuth: { @@ -234,8 +263,9 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}), - output: getBridgeResponseSchema(getDeviceResponseSchema(z.array(DeviceSchema))) + input: z.strictObject({ bridgeId: z.string() }), + output: getBridgeResponseSchema( + getDeviceResponseSchema(z.array(DeviceSchema))) }, getAppVersion: { meta: { @@ -245,7 +275,7 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}), + input: z.strictObject({ bridgeId: z.string() }), output: getBridgeResponseSchema(getDeviceResponseSchema(z.string())) }, getPublicKey: { @@ -256,7 +286,7 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}), + input: z.strictObject({ bridgeId: z.string() }), output: getBridgeResponseSchema(getDeviceResponseSchema(z.string())) }, getAutoSchedule: { @@ -267,8 +297,9 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}), - output: getBridgeResponseSchema(getDeviceResponseSchema(AutoScheduleSchema.optional())) + input: z.strictObject({ bridgeId: z.string() }), + output: getBridgeResponseSchema( + getDeviceResponseSchema(AutoScheduleSchema.optional())) }, getGeometry: { meta: { @@ -278,8 +309,9 @@ export const APIRoutes = { protect: true } }, - input: z.strictObject({bridgeId: z.string()}), - output: getBridgeResponseSchema(getDeviceResponseSchema(BoundsSchema.optional())) + input: z.strictObject({ bridgeId: z.string() }), + output: getBridgeResponseSchema( + getDeviceResponseSchema(BoundsSchema.optional())) } }; diff --git a/libs/ove-types/src/lib/hardware.spec.ts b/libs/ove-types/src/lib/hardware.spec.ts index 8f37f269..161289b8 100644 --- a/libs/ove-types/src/lib/hardware.spec.ts +++ b/libs/ove-types/src/lib/hardware.spec.ts @@ -1,37 +1,26 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; -import { type Optional } from "@ove/ove-types"; - -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; +import { type Optional } from "./hardware"; describe("hardware types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("Optional", () => { init(); - bench("Optional", () => ({}) as Optional<{}>).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["Optional"] = formatOutput(); + bench("Optional", () => ({}) as Optional>) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["Optional"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - existing["ove-types"]["hardware"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware.ts b/libs/ove-types/src/lib/hardware.ts index 1af53277..14ddf20b 100644 --- a/libs/ove-types/src/lib/hardware.ts +++ b/libs/ove-types/src/lib/hardware.ts @@ -69,7 +69,10 @@ export const DeviceSchema = z.object({ type: ServiceTypeSchema, mac: z.string(), tags: z.array(z.string()), - auth: z.union([z.object({username: z.string(), password: z.string()}), z.boolean()]).nullable() + auth: z.union([z.object({ + username: z.string(), + password: z.string() + }), z.boolean()]).nullable() }); export type Device = z.infer; diff --git a/libs/ove-types/src/lib/hardware/bridge-transform.spec.ts b/libs/ove-types/src/lib/hardware/bridge-transform.spec.ts index 828438e4..7e86020f 100644 --- a/libs/ove-types/src/lib/hardware/bridge-transform.spec.ts +++ b/libs/ove-types/src/lib/hardware/bridge-transform.spec.ts @@ -1,38 +1,31 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { getMultiDeviceResponseSchema } from "./bridge-transform"; -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; - describe("bridge-transform types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("getMultiDeviceResponseSchema", () => { init(); - bench("getMultiDeviceResponseSchema", () => ({}) as typeof getMultiDeviceResponseSchema).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getMultiDeviceReponseSchema"] = formatOutput(); + bench("getMultiDeviceResponseSchema", + () => ({}) as typeof getMultiDeviceResponseSchema) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getMultiDeviceReponseSchema"] = + formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["bridge-transform"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["bridge-transform"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/bridge-transform.ts b/libs/ove-types/src/lib/hardware/bridge-transform.ts index 44be62fa..c8ab0fd9 100644 --- a/libs/ove-types/src/lib/hardware/bridge-transform.ts +++ b/libs/ove-types/src/lib/hardware/bridge-transform.ts @@ -26,7 +26,8 @@ export const BridgeMetadataSchema = z.object({ bridge: z.string() }); /** * Generates multi-device response schema for wrapping with errors - * @param schema + * @param {z.ZodTypeAny} schema + * @return {TMultiDeviceResponseSchema} */ export const getMultiDeviceResponseSchema = (schema: T): TMultiDeviceResponseSchema => @@ -37,13 +38,15 @@ export const getMultiDeviceResponseSchema = /** * Generates response schema wrapped with metadata - * @param schema + * @param {z.ZodTypeAny} schema + * @return {TBridgeResponseSchema} */ export const getBridgeResponseSchema = - (schema: T): TBridgeResponseSchema => z.object({ - meta: BridgeMetadataSchema, - response: schema - }); + (schema: T): TBridgeResponseSchema => + z.object({ + meta: BridgeMetadataSchema, + response: schema + }); /* Utility Types */ @@ -83,7 +86,10 @@ export type TBridgeRouteSchema< { bridge: TBridgeResponseSchema["client"]> } - & { [Key in keyof TClientRouteSchema]: TClientRouteSchema[Key] }; + & { + [Key in keyof TClientRouteSchema]: + TClientRouteSchema[Key] +}; /** * Schema for each multi-device route as a type for mapping @@ -97,7 +103,10 @@ export type TBridgeMultiRouteSchema< bridge: TBridgeResponseSchema< TMultiDeviceResponseSchema["returns"]>> } - & { [Key in keyof TClientRouteSchema]: TClientRouteSchema[Key] }; + & { + [Key in keyof TClientRouteSchema]: + TClientRouteSchema[Key] +}; /* API Types */ @@ -107,7 +116,8 @@ export type TBridgeMultiRouteSchema< export type TBridgeSingleRoutesSchema = { [Key in keyof TClientRoutesSchema]: TBridgeRouteSchema, { deviceId: z.ZodString } - >, TClientRouteOutputTransformSchema, OpenAPIMethod, APIExposureLevel> + >, TClientRouteOutputTransformSchema, + OpenAPIMethod, APIExposureLevel> } /** @@ -118,7 +128,8 @@ export type TBridgeMultiRoutesSchema = { z.extendShape< TClientRouteInputTransformSchema, { tag: z.ZodOptional } - >, TClientRouteOutputTransformSchema, OpenAPIMethod, APIExposureLevel> + >, TClientRouteOutputTransformSchema, + OpenAPIMethod, APIExposureLevel> } /** @@ -133,38 +144,41 @@ export type TBridgeRoutesSchema = /** * Instantiation of the schema above. */ -export const BridgeAPITransformSchema: TBridgeRoutesSchema = Object.entries(ClientAPITransformSchema).reduce((acc, [k, route]) => { - acc[k] = { - meta: route.meta, - returns: route.returns, - args: route.args.extend({ - deviceId: DeviceIDSchema - }), - client: route.client, - bridge: getBridgeResponseSchema(route.client) - }; - acc[`${k}All`] = { - meta: route.meta, - returns: route.returns, - args: route.args.extend({ - tag: z.string().optional() - }), - client: route.client, - bridge: getBridgeResponseSchema( - getMultiDeviceResponseSchema(route.returns)) - }; - return acc; -}, <{ [key: string]: unknown }>{}) as TBridgeRoutesSchema; +export const BridgeAPITransformSchema: TBridgeRoutesSchema = + Object.entries(ClientAPITransformSchema) + .reduce((acc, [k, route]) => { + acc[k] = { + meta: route.meta, + returns: route.returns, + args: route.args.extend({ + deviceId: DeviceIDSchema + }), + client: route.client, + bridge: getBridgeResponseSchema(route.client) + }; + acc[`${k}All`] = { + meta: route.meta, + returns: route.returns, + args: route.args.extend({ + tag: z.string().optional() + }), + client: route.client, + bridge: getBridgeResponseSchema( + getMultiDeviceResponseSchema(route.returns)) + }; + return acc; + }, <{ [key: string]: unknown }>{}) as TBridgeRoutesSchema; /* API Utility Types */ /** * Input schema for a route */ -export type BridgeRouteInputTransformSchema = +export type BridgeRouteInputTransformSchema< + Key extends keyof TBridgeRoutesSchema> = TBridgeRoutesSchema[Key]["args"]["shape"]; /** * Output schema for a route */ -export type BridgeRouteOutputTransformSchema = - TBridgeRoutesSchema[Key]["returns"]; +export type BridgeRouteOutputTransformSchema< + Key extends keyof TBridgeRoutesSchema> = TBridgeRoutesSchema[Key]["returns"]; diff --git a/libs/ove-types/src/lib/hardware/bridge.spec.ts b/libs/ove-types/src/lib/hardware/bridge.spec.ts index 70688e12..90903ff0 100644 --- a/libs/ove-types/src/lib/hardware/bridge.spec.ts +++ b/libs/ove-types/src/lib/hardware/bridge.spec.ts @@ -1,38 +1,30 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; -import { type TBridgeHardwareService } from "@ove/ove-types"; - -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; +import { type TBridgeHardwareService } from "./bridge"; describe("bridge types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("hardware service - getStatus", () => { init(); - bench("hardware service - getStatus", () => ({}) as TBridgeHardwareService["getStatus"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getStatus"] = formatOutput(); + bench("hardware service - getStatus", + () => ({}) as TBridgeHardwareService["getStatus"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getStatus"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["bridge"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["bridge"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/bridge.ts b/libs/ove-types/src/lib/hardware/bridge.ts index 2ca43999..427140ae 100644 --- a/libs/ove-types/src/lib/hardware/bridge.ts +++ b/libs/ove-types/src/lib/hardware/bridge.ts @@ -1,20 +1,24 @@ import { z } from "zod"; import { type Device, type Optional } from "../hardware"; +import { type TBridgeRoutesSchema } from "./bridge-transform"; import { - type TBridgeRoutesSchema, -} from "./bridge-transform"; -import { ClientAPITransformSchema, type TClientRoutesSchema } from "./client-transform"; + ClientAPITransformSchema, + type TClientRoutesSchema +} from "./client-transform"; /* API Keys */ /** * Keys of all routes */ -export const BridgeServiceKeys: readonly (keyof TClientRoutesSchema)[] = Object.keys(ClientAPITransformSchema) as Array; +export const BridgeServiceKeys: readonly (keyof TClientRoutesSchema)[] = + Object.keys(ClientAPITransformSchema) as Array; /* API Types */ -export { type TBridgeRoutesSchema, type TBridgeResponseSchema } from "./bridge-transform"; +export { + type TBridgeRoutesSchema, type TBridgeResponseSchema +} from "./bridge-transform"; /** * Bridge service type diff --git a/libs/ove-types/src/lib/hardware/client-transform.spec.ts b/libs/ove-types/src/lib/hardware/client-transform.spec.ts index 6b6c0691..70e7637c 100644 --- a/libs/ove-types/src/lib/hardware/client-transform.spec.ts +++ b/libs/ove-types/src/lib/hardware/client-transform.spec.ts @@ -1,38 +1,29 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { type TDeviceResponse } from "./client-transform"; -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; - describe("client-transform types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("TDeviceResponse", () => { init(); - bench("TDeviceResponse", () => ({}) as TDeviceResponse).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["TDeviceResponse"] = formatOutput(); + bench("TDeviceResponse", () => ({}) as TDeviceResponse) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["TDeviceResponse"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["client-transform"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["client-transform"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/client-transform.ts b/libs/ove-types/src/lib/hardware/client-transform.ts index b6870d52..c5c0ab61 100644 --- a/libs/ove-types/src/lib/hardware/client-transform.ts +++ b/libs/ove-types/src/lib/hardware/client-transform.ts @@ -26,11 +26,12 @@ export type TDeviceResponse = T | OVEException /** * Generate schema for wrapped response - * @param schema + * @param {z.ZodTypeAny} schema + * @return {TDeviceResponseSchema} */ -export const getDeviceResponseSchema = < - T extends z.ZodTypeAny ->(schema: T): TDeviceResponseSchema => z.union([schema, OVEExceptionSchema]); +export const getDeviceResponseSchema = ( + schema: T +): TDeviceResponseSchema => z.union([schema, OVEExceptionSchema]); /* API Route Types */ @@ -43,7 +44,10 @@ export type TClientRouteSchema< M extends RouteMethod, E extends ExposureLevel > = { client: TDeviceResponseSchema; } - & { [Key in keyof TServiceRouteSchema]: TServiceRouteSchema[Key] }; + & { + [Key in keyof TServiceRouteSchema]: + TServiceRouteSchema[Key] +}; /* API Type */ @@ -52,7 +56,8 @@ export type TClientRouteSchema< */ export type TClientRoutesSchema = { [Key in keyof TServiceRoutesSchema]: TClientRouteSchema< - ServiceRouteInputSchema, ServiceRouteOutputSchema, OpenAPIMethod, APIExposureLevel> + ServiceRouteInputSchema, ServiceRouteOutputSchema, + OpenAPIMethod, APIExposureLevel> }; /* API */ @@ -60,26 +65,29 @@ export type TClientRoutesSchema = { /** * Instantiation of the schema type above. */ -export const ClientAPITransformSchema: TClientRoutesSchema = Object.entries(ServiceAPISchema).reduce((acc, [k, route]) => { - acc[k] = { - meta: route.meta, - returns: route.returns, - args: route.args, - exposed: route.exposed, - client: getDeviceResponseSchema(route.returns) - }; - return acc; -}, <{ [key: string]: unknown }>{}) as TClientRoutesSchema; +export const ClientAPITransformSchema: TClientRoutesSchema = + Object.entries(ServiceAPISchema) + .reduce((acc, [k, route]) => { + acc[k] = { + meta: route.meta, + returns: route.returns, + args: route.args, + exposed: route.exposed, + client: getDeviceResponseSchema(route.returns) + }; + return acc; + }, <{ [key: string]: unknown }>{}) as TClientRoutesSchema; /* API Utility Types */ /** * Input schema for a route */ -export type TClientRouteInputTransformSchema = +export type TClientRouteInputTransformSchema< + Key extends keyof TClientRoutesSchema> = TClientRoutesSchema[Key]["args"]["shape"]; /** * Output schema for a route */ -export type TClientRouteOutputTransformSchema = - TClientRoutesSchema[Key]["returns"]; +export type TClientRouteOutputTransformSchema< + Key extends keyof TClientRoutesSchema> = TClientRoutesSchema[Key]["returns"]; diff --git a/libs/ove-types/src/lib/hardware/client.spec.ts b/libs/ove-types/src/lib/hardware/client.spec.ts index 5359523b..3e6d94a2 100644 --- a/libs/ove-types/src/lib/hardware/client.spec.ts +++ b/libs/ove-types/src/lib/hardware/client.spec.ts @@ -1,38 +1,30 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { type TDeviceResponse } from "./client"; -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; - describe("client types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("TDeviceResponse", () => { init(); - bench("TDeviceResponse", () => ({}) as TDeviceResponse).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["TDeviceResponse"] = formatOutput(); + bench("TDeviceResponse", + () => ({}) as TDeviceResponse) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["TDeviceResponse"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["client"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["client"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/client.ts b/libs/ove-types/src/lib/hardware/client.ts index e8a3f79a..9f4e011d 100644 --- a/libs/ove-types/src/lib/hardware/client.ts +++ b/libs/ove-types/src/lib/hardware/client.ts @@ -11,10 +11,11 @@ import { type TServiceRoutesSchema } from "./service"; -export {TDeviceResponse, TClientRouteInputSchema, TClientRouteOutputSchema} +export { TDeviceResponse, TClientRouteInputSchema, TClientRouteOutputSchema }; export type TClientExposedRoutes = { - [Key in keyof TServiceRoutesSchema]: APIExposureLevel extends "client" ? Key : never + [Key in keyof TServiceRoutesSchema]: + APIExposureLevel extends "client" ? Key : never }[keyof TServiceRoutesSchema] /* Service Types */ @@ -36,8 +37,9 @@ export type TClientAPI = { /** * Client API schema, only those that are exposed on the client */ -export const ClientAPISchema: TClientAPI = Object.fromEntries(Object.entries(ClientAPITransformSchema) - .filter(([_k, route]) => route.exposed === "client")) as TClientAPI; +export const ClientAPISchema: TClientAPI = + Object.fromEntries(Object.entries(ClientAPITransformSchema) + .filter(([_k, route]) => route.exposed === "client")) as TClientAPI; /* Service Utility Types */ @@ -47,4 +49,5 @@ export const ClientAPISchema: TClientAPI = Object.fromEntries(Object.entries(Cli export type TClientServiceArgs = z.infer; -export type TClientAPIReturns = Promise> +export type TClientAPIReturns = + Promise> diff --git a/libs/ove-types/src/lib/hardware/core-transform.spec.ts b/libs/ove-types/src/lib/hardware/core-transform.spec.ts index 65e91c74..bd6ad466 100644 --- a/libs/ove-types/src/lib/hardware/core-transform.spec.ts +++ b/libs/ove-types/src/lib/hardware/core-transform.spec.ts @@ -1,38 +1,30 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { type ToSingleRoute } from "./core-transform"; -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; - describe("core-transform types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("ToSingleRoute", () => { init(); - bench("ToSingleRoute", () => "" as ToSingleRoute<"getStatusAll">).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["ToSingleRoute"] = formatOutput(); + bench("ToSingleRoute", () => "" as ToSingleRoute<"getStatusAll">) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["ToSingleRoute"] = + formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["core-transform"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["core-transform"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/core-transform.ts b/libs/ove-types/src/lib/hardware/core-transform.ts index 585ed42c..1a995ea5 100644 --- a/libs/ove-types/src/lib/hardware/core-transform.ts +++ b/libs/ove-types/src/lib/hardware/core-transform.ts @@ -33,7 +33,8 @@ type TCoreRouteSchema< M extends RouteMethod, E extends ExposureLevel > = { - [Key in keyof TBridgeRouteSchema]: TBridgeRouteSchema[Key] + [Key in keyof TBridgeRouteSchema]: + TBridgeRouteSchema[Key] }; /** @@ -45,7 +46,8 @@ type TCoreMultiRouteSchema< M extends RouteMethod, E extends ExposureLevel > = { - [Key in keyof TBridgeMultiRouteSchema]: TBridgeMultiRouteSchema[Key] + [Key in keyof TBridgeMultiRouteSchema]: + TBridgeMultiRouteSchema[Key] }; /* API Type */ @@ -54,17 +56,20 @@ type TCoreMultiRouteSchema< * All possible routes as schema types. */ export type TCoreRoutesSchema = { - [Key in keyof TBridgeSingleRoutesSchema]:TCoreRouteSchema< - z.extendShape, { - bridgeId: z.ZodString - }>, BridgeRouteOutputTransformSchema, OpenAPIMethod, APIExposureLevel> + [Key in keyof TBridgeSingleRoutesSchema]: TCoreRouteSchema< + z.extendShape, { + bridgeId: z.ZodString + }>, BridgeRouteOutputTransformSchema, + OpenAPIMethod, APIExposureLevel> } & { [Key in keyof TBridgeMultiRoutesSchema]: TCoreMultiRouteSchema< - z.extendShape, { bridgeId: z.ZodString }>, - BridgeRouteOutputTransformSchema, - OpenAPIMethod>, - APIExposureLevel> - > + z.extendShape, { + bridgeId: z.ZodString + }>, + BridgeRouteOutputTransformSchema, + OpenAPIMethod>, + APIExposureLevel> + > } /* API */ @@ -72,21 +77,22 @@ export type TCoreRoutesSchema = { /** * Instantiation of the schema type above. */ -export const CoreAPITransformSchema: TCoreRoutesSchema = Object.entries(BridgeAPITransformSchema).reduce((acc, [k, route]) => { - const optionalPath = `/{bridgeId}${k.includes("All") ? "" : "/{deviceId}"}`; - const path = route.meta.openapi.path; - acc[k] = { - meta: { - openapi: { - method: route.meta.openapi.method, - path: `/hardware${optionalPath}${path}` - } - }, - returns: route.returns, - args: route.args.extend({ bridgeId: z.string() }), - client: route.client, - bridge: route.bridge, - exposed: route.exposed - }; - return acc; -}, <{[key: string]: unknown}>{}) as TCoreRoutesSchema; +export const CoreAPITransformSchema: TCoreRoutesSchema = + Object.entries(BridgeAPITransformSchema).reduce((acc, [k, route]) => { + const optionalPath = `/{bridgeId}${k.includes("All") ? "" : "/{deviceId}"}`; + const path = route.meta.openapi.path; + acc[k] = { + meta: { + openapi: { + method: route.meta.openapi.method, + path: `/hardware${optionalPath}${path}` + } + }, + returns: route.returns, + args: route.args.extend({ bridgeId: z.string() }), + client: route.client, + bridge: route.bridge, + exposed: route.exposed + }; + return acc; + }, <{ [key: string]: unknown }>{}) as TCoreRoutesSchema; diff --git a/libs/ove-types/src/lib/hardware/core.spec.ts b/libs/ove-types/src/lib/hardware/core.spec.ts index 9a8507fb..c239e7f1 100644 --- a/libs/ove-types/src/lib/hardware/core.spec.ts +++ b/libs/ove-types/src/lib/hardware/core.spec.ts @@ -1,56 +1,50 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { type TCoreAPI } from "./core"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; - -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; describe("core types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("getStatus", async () => { init(); - bench("getStatus", () => ({}) as TCoreAPI["getStatus"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getStatus"] = formatOutput(); + bench("getStatus", () => ({}) as TCoreAPI["getStatus"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getStatus"] = formatOutput(console.log as LogFn); }); it("getStatusAll", async () => { init(); - bench("getStatusAll", () => ({}) as TCoreAPI["getStatusAll"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getStatusAll"] = formatOutput(); + bench("getStatusAll", () => ({}) as TCoreAPI["getStatusAll"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getStatusAll"] = formatOutput(console.log as LogFn); }); it("getInfo", async () => { init(); - bench("getInfo", () => ({}) as TCoreAPI["getInfo"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getInfo"] = formatOutput(); + bench("getInfo", () => ({}) as TCoreAPI["getInfo"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getInfo"] = formatOutput(console.log as LogFn); }); it("getInfoAll", async () => { init(); - bench("getInfoAll", () => ({}) as TCoreAPI["getInfoAll"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getInfoAll"] = formatOutput(); + bench("getInfoAll", () => ({}) as TCoreAPI["getInfoAll"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getInfoAll"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["core"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["core"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/hardware/core.ts b/libs/ove-types/src/lib/hardware/core.ts index aeea65e3..ea27a12d 100644 --- a/libs/ove-types/src/lib/hardware/core.ts +++ b/libs/ove-types/src/lib/hardware/core.ts @@ -1,12 +1,15 @@ import { CoreAPITransformSchema, - type TCoreRoutesSchema, + type TCoreRoutesSchema } from "./core-transform"; import { z } from "zod"; /* API */ -export { CoreAPITransformSchema as CoreAPI, type TCoreRoutesSchema as TCoreAPI }; +export { + CoreAPITransformSchema as CoreAPI, + type TCoreRoutesSchema as TCoreAPI +}; /* API Utility Types*/ diff --git a/libs/ove-types/src/lib/hardware/service.spec.ts b/libs/ove-types/src/lib/hardware/service.spec.ts index 3c394055..9e34a261 100644 --- a/libs/ove-types/src/lib/hardware/service.spec.ts +++ b/libs/ove-types/src/lib/hardware/service.spec.ts @@ -1,44 +1,37 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { type RouteMethod, type TServiceRoutesSchema } from "./service"; -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; - describe("service types", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("RouteMethod", async () => { init(); - bench("RouteMethod", () => "" as RouteMethod).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["RouteMethod"] = formatOutput(); + bench("RouteMethod", () => "" as RouteMethod) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["RouteMethod"] = formatOutput(console.log as LogFn); }); it("getStatus", async () => { init(); - bench("getStatus", () => ({}) as TServiceRoutesSchema["getStatus"]).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["getStatus"] = formatOutput(); + bench("getStatus", + () => ({}) as TServiceRoutesSchema["getStatus"]) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["getStatus"] = formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - if (!("hardware/" in existing["ove-types"])) existing["ove-types"]["hardware/"] = {}; + if (!("hardware/" in existing["ove-types"]!)) { + existing["ove-types"]!["hardware/"] = {}; + } - existing["ove-types"]["hardware/"]["service"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["hardware/"]!["service"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); -}); \ No newline at end of file +}); diff --git a/libs/ove-types/src/lib/ove-types.spec.ts b/libs/ove-types/src/lib/ove-types.spec.ts index 14bae142..d042c45d 100644 --- a/libs/ove-types/src/lib/ove-types.spec.ts +++ b/libs/ove-types/src/lib/ove-types.spec.ts @@ -1,25 +1,12 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + import { writeFileSync } from "fs"; import * as Types from "./ove-types"; -import { bench } from "@arktype/attest"; -import Utils from "@ove/ove-server-utils"; import { type OVEException } from "./ove-types"; - -const formatOutput = () => { - const results = (console.log as ReturnType).mock.calls.map(x => x[0] as string).filter(o => o.includes("Result: ")).map(o => o.match(/Result: ([\d|.]+)/)![1]); - return { - time: parseFloat(results[0]), - instantiations: parseInt(results[1]) - }; -}; - -const init = () => { - console.log = jest.fn(); - console.group = jest.fn(); - console.error = jest.fn(); -}; +import { bench } from "@arktype/attest"; describe("oveTypes", () => { - let benchmarks: Record = {}; + const benchmarks: Record = {}; it("should work", () => { expect(Object.keys(Types).length).toBeGreaterThan(0); @@ -27,16 +14,20 @@ describe("oveTypes", () => { it("OVEException", () => { init(); - bench("OVEException", () => ({}) as OVEException).mean([0, "ns"]).types([0, "instantiations"]); - benchmarks["OVEException"] = formatOutput(); + bench("OVEException", () => ({}) as OVEException) + .mean([0, "ns"]) + .types([0, "instantiations"]); + benchmarks["OVEException"] = + formatOutput(console.log as LogFn); }); afterAll(() => { - const existing = Utils.readFile>("./benchmarks.json", "{}")!; + const existing = readFile(); if (!("ove-types" in existing)) existing["ove-types"] = {}; - existing["ove-types"]["ove-types"] = benchmarks; - writeFileSync("./benchmarks.json", JSON.stringify(existing, undefined, 2)); + existing["ove-types"]!["ove-types"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); }); }); diff --git a/libs/ove-types/src/lib/ove-types.ts b/libs/ove-types/src/lib/ove-types.ts index e7d0b79f..40aa5602 100644 --- a/libs/ove-types/src/lib/ove-types.ts +++ b/libs/ove-types/src/lib/ove-types.ts @@ -15,7 +15,8 @@ export const is = ( export const isError = ( obj: unknown -): obj is OVEException => obj !== undefined && obj !== null && typeof obj === "object" && "oveError" in obj; +): obj is OVEException => obj !== undefined && + obj !== null && typeof obj === "object" && "oveError" in obj; export const isAll = ( schema: T, @@ -27,7 +28,11 @@ export type Tokens = { refresh: string } -export const PowerModeSchema = z.union([z.literal("manual"), z.literal("auto"), z.literal("eco")]); +export const PowerModeSchema = z.union([ + z.literal("manual"), + z.literal("auto"), + z.literal("eco") +]); export type PowerMode = z.infer export const CalendarEventSchema = z.strictObject({ @@ -39,7 +44,11 @@ export const CalendarEventSchema = z.strictObject({ export type CalendarEvent = z.infer export const CalendarSchema = z.strictObject({ - value: z.array(z.strictObject({ title: z.string(), start: z.string(), end: z.string() })), + value: z.array(z.strictObject({ + title: z.string(), + start: z.string(), + end: z.string() + })), lastUpdated: z.string().nullable() }); diff --git a/libs/ove-types/src/lib/projects/projects.spec.ts b/libs/ove-types/src/lib/projects/projects.spec.ts new file mode 100644 index 00000000..2243a1b8 --- /dev/null +++ b/libs/ove-types/src/lib/projects/projects.spec.ts @@ -0,0 +1,29 @@ +/* global readFile, LogFn, console, init, Benchmark, formatOutput */ + +import { writeFileSync } from "fs"; +import { bench } from "@arktype/attest"; +import { type File } from "./projects"; + +describe("projects types", () => { + const benchmarks: Record = {}; + + it("File", () => { + init(); + bench("File", () => ({}) as File) + .mean([0, "ns"]).types([0, "instantiations"]); + benchmarks["File"] = formatOutput(console.log as LogFn); + }); + + afterAll(() => { + const existing = readFile(); + + if (!("ove-types" in existing)) existing["ove-types"] = {}; + if (!("projects/" in existing["ove-types"]!)) { + existing["ove-types"]!["projects/"] = {}; + } + + existing["ove-types"]!["projects/"]!["projects"] = benchmarks; + writeFileSync("./benchmarks.json", + JSON.stringify(existing, undefined, 2)); + }); +}); diff --git a/libs/ove-types/src/lib/projects/projects.ts b/libs/ove-types/src/lib/projects/projects.ts new file mode 100644 index 00000000..9a6db859 --- /dev/null +++ b/libs/ove-types/src/lib/projects/projects.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const FileSchema = z.strictObject({ + name: z.string(), + assetId: z.string(), + version: z.number(), + isGlobal: z.boolean() +}); + +export type File = z.infer diff --git a/libs/ove-types/tsconfig.spec.json b/libs/ove-types/tsconfig.spec.json index 0c443e75..dd16cc54 100644 --- a/libs/ove-types/tsconfig.spec.json +++ b/libs/ove-types/tsconfig.spec.json @@ -7,6 +7,7 @@ "types": ["jest", "node"], "allowJs": true }, + "files": ["./helpers.d.ts"], "include": [ "jest.config.ts", "src/**/*.test.ts", diff --git a/libs/ove-utils/src/index.ts b/libs/ove-utils/src/index.ts index 4c6a85a1..f30c7217 100644 --- a/libs/ove-utils/src/index.ts +++ b/libs/ove-utils/src/index.ts @@ -2,3 +2,4 @@ export * from "./lib/ove-utils"; export { Json } from "./lib/ove-json"; export { safeFetch } from "./lib/ove-fetch"; export { default as Parser } from "./lib/ove-sockets"; +export * from "./lib/types"; diff --git a/libs/ove-utils/src/lib/ove-fetch.ts b/libs/ove-utils/src/lib/ove-fetch.ts index 451b90a4..bdf62aed 100644 --- a/libs/ove-utils/src/lib/ove-fetch.ts +++ b/libs/ove-utils/src/lib/ove-fetch.ts @@ -1,15 +1,25 @@ -// global console, window, fetch +/* global console, window, fetch, URL, RequestInfo, RequestInit */ import { z } from "zod"; -// @ts-ignore -export const safeFetch = async (url: URL | RequestInfo, schema: T, args?: RequestInit): Promise | null> => { +export const safeFetch = async ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + url: URL | RequestInfo, + schema: T, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + args?: RequestInit +): Promise | null> => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (window === undefined) { - console.error("Attempting to use browser version of safeFetch in a server environment"); + console.error("Attempting to use browser version of safeFetch" + + " in a server environment"); return null; } try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const raw = await fetch(url, args); const res = await raw.json(); @@ -19,4 +29,4 @@ export const safeFetch = async (url: URL | RequestInfo, sche } catch (e) { return null; } -}; \ No newline at end of file +}; diff --git a/libs/ove-utils/src/lib/ove-json.ts b/libs/ove-utils/src/lib/ove-json.ts index 7414efa4..3519c16f 100644 --- a/libs/ove-utils/src/lib/ove-json.ts +++ b/libs/ove-utils/src/lib/ove-json.ts @@ -21,7 +21,13 @@ export const Json = { return JSON.stringify(x) === JSON.stringify(y); }, parse: (obj: string) => JSON.parse(obj) as T, - stringify: (obj: T, replacer?: (this:any, key: string, value: any) => any, space?: string | number) => JSON.stringify(obj, replacer, space ?? 2), + stringify: ( + obj: T, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + replacer?: (this: any, key: string, value: any) => any, + space?: string | number + ) => JSON.stringify(obj, replacer, space ?? 2), getDescendent: getDescendent, - copy: (obj: T): T => obj === undefined ? obj : JSON.parse(JSON.stringify(obj)) as T -}; \ No newline at end of file + copy: (obj: T): T => obj === undefined ? + obj : JSON.parse(JSON.stringify(obj)) as T +}; diff --git a/libs/ove-utils/src/lib/ove-sockets.ts b/libs/ove-utils/src/lib/ove-sockets.ts index 76ac18b5..ccc6872a 100644 --- a/libs/ove-utils/src/lib/ove-sockets.ts +++ b/libs/ove-utils/src/lib/ove-sockets.ts @@ -1,27 +1,58 @@ -import Emitter from "component-emitter"; // polyfill of Node.js EventEmitter in the browser +/* global console */ + +// polyfill of Node.js EventEmitter in the browser +import Emitter from "component-emitter"; import superjson from "superjson"; + +type ValidPacketArgs = { + type: number, + data: object | unknown[] | undefined, + nsp: string, + id: undefined | number +} + +/** + * Packet encoder + */ class Encoder { /** * Encode a packet into a list of strings/buffers + * @param {unknown} packet - packet to encode. + * @return {string[]} */ - encode(packet: unknown) { + encode(packet: unknown): string[] { return [superjson.stringify(packet)]; } } +/** + * Packet decoder + */ class Decoder extends Emitter { /** - * Receive a chunk (string or buffer) and optionally emit a "decoded" event with the reconstructed packet + * Receive a chunk (string or buffer) and optionally emit a + * “decoded” event with the reconstructed packet. + * @param {string} chunk - chunk to decode. */ add(chunk: string) { - const packet = superjson.parse(chunk); + const packet = superjson.parse(chunk); if (this.isPacketValid(packet)) { this.emit("decoded", packet); } else { throw new Error("invalid format"); } } - isPacketValid({ type, data, nsp, id }: {type: any, data: any, nsp: any, id: any}) { + + /** + * Whether the received packet is valid + * @param {ValidPacketArgs} packetArgs + * @param {number} packetArgs.type - packet type + * @param {object | unknown[] | undefined} packetArgs.data - packet data + * @param {string} packetArgs.nsp - packet nsp + * @param {number | undefined} packetArgs.id - packet id + * @return {boolean} + */ + isPacketValid({ type, data, nsp, id }: ValidPacketArgs): boolean { const isNamespaceValid = typeof nsp === "string"; const isAckIdValid = id === undefined || Number.isInteger(id); if (!isNamespaceValid || !isAckIdValid) { @@ -42,10 +73,13 @@ class Decoder extends Emitter { return false; } } + /** * Clean up internal buffers */ - destroy() {} + destroy() { + console.log("Destroying Decoder"); + } } -export default { Encoder, Decoder }; \ No newline at end of file +export default { Encoder, Decoder }; diff --git a/libs/ove-utils/src/lib/ove-utils.ts b/libs/ove-utils/src/lib/ove-utils.ts index 26637638..f375fbdf 100644 --- a/libs/ove-utils/src/lib/ove-utils.ts +++ b/libs/ove-utils/src/lib/ove-utils.ts @@ -1,4 +1,8 @@ +/* global Proxy */ + +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type OVEException } from "@ove/ove-types"; +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { type TLogger } from "@ove/ove-logging"; export const replaceAll = (s: string, xs: string[]): string => { @@ -17,32 +21,40 @@ export const assert = (x: T | undefined | null) => { return x; }; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -export const DeepProxy = (target: T, onChange: () => void) => { - let proxyCache = new WeakMap(); - return new Proxy(target, { - get(target, property) { - // @ts-ignore - const item = target[property]; - if (item && typeof item === "object") { - if (proxyCache.has(item)) return proxyCache.get(item); +export const DeepProxy = + (target: T, onChange: () => void) => { + const proxyCache = new WeakMap(); + return new Proxy(target, { + get(target, property) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const item = target[property]; + if (item && typeof item === "object") { + if (proxyCache.has(item)) return proxyCache.get(item); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const proxy = DeepProxy(item, onChange); + proxyCache.set(item, proxy); + return proxy; + } + return item; + }, + set(target, property, newValue) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const proxy = DeepProxy(item, onChange); - proxyCache.set(item, proxy); - return proxy; + target[property] = newValue; + onChange(); + return true; } - return item as any; - }, - set(target, property, newValue) { - // @ts-ignore - target[property] = newValue; - onChange(); - return true; - } - }); -}; + }); + }; -export const safe = async (logger: TLogger, handler: () => T): Promise | OVEException> => { +export const safe = async ( + logger: TLogger, + handler: () => Promise +): Promise | OVEException> => { try { return await handler(); } catch (e) { diff --git a/libs/ui-base-components/.eslintrc.json b/libs/ui-base-components/.eslintrc.json index 734ddace..1e382421 100644 --- a/libs/ui-base-components/.eslintrc.json +++ b/libs/ui-base-components/.eslintrc.json @@ -1,17 +1,48 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "plugin:@nrwl/nx/react", + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ] + } }, { - "files": ["*.ts", "*.tsx"], + "files": [ + "*.ts", + "*.tsx" + ], "rules": {} }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/libs/ui-base-components/postcss.config.js b/libs/ui-base-components/postcss.config.js index c72626d6..f314ecc6 100644 --- a/libs/ui-base-components/postcss.config.js +++ b/libs/ui-base-components/postcss.config.js @@ -1,14 +1,15 @@ -const { join } = require('path'); +const { join } = require("path"); -// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build -// option from your application's configuration (i.e. project.json). +// Note: If you use library-specific PostCSS/Tailwind +// configuration then you should remove the `postcssConfig` build +// option from your application"s configuration (i.e. project.json). // // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries module.exports = { plugins: { tailwindcss: { - config: join(__dirname, 'tailwind.config.js'), + config: join(__dirname, "tailwind.config.js"), }, autoprefixer: {}, }, diff --git a/libs/ui-base-components/src/lib/button.tsx b/libs/ui-base-components/src/lib/button.tsx index 6d5eb38f..a1fdb237 100644 --- a/libs/ui-base-components/src/lib/button.tsx +++ b/libs/ui-base-components/src/lib/button.tsx @@ -1,11 +1,15 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "./utils" +import { cn } from "./utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center whitespace-nowrap " + + "rounded-md text-sm font-medium ring-offset-background transition-colors " + + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " + + "focus-visible:ring-offset-2 disabled:pointer-events-none " + + "disabled:opacity-50", { variants: { variant: { @@ -13,44 +17,45 @@ const buttonVariants = cva( destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + "border border-input bg-background hover:bg-accent " + + "hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, + icon: "h-10 w-10" + } }, defaultVariants: { variant: "default", - size: "default", - }, + size: "default" + } } -) +); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) + ); } -) -Button.displayName = "Button" +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/libs/ui-base-components/src/lib/card.tsx b/libs/ui-base-components/src/lib/card.tsx index b73a1d12..da4171db 100644 --- a/libs/ui-base-components/src/lib/card.tsx +++ b/libs/ui-base-components/src/lib/card.tsx @@ -1,10 +1,11 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "./utils" +import { cn } from "./utils"; const Card = React.forwardRef< HTMLDivElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => (
    -)) -Card.displayName = "Card" +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef< HTMLDivElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => (
    -)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< HTMLParagraphElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => ( + // eslint-disable-next-line jsx-a11y/heading-has-content

    -)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< HTMLParagraphElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => (

    -)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< HTMLDivElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => (

    -)) -CardContent.displayName = "CardContent" +)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< HTMLDivElement, - React.HTMLAttributes + React.HTMLAttributes & + {className?: string | undefined} >(({ className, ...props }, ref) => (
    -)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent +}; diff --git a/libs/ui-base-components/src/lib/navigation-menu.tsx b/libs/ui-base-components/src/lib/navigation-menu.tsx index b5c27676..3fe3f5fd 100644 --- a/libs/ui-base-components/src/lib/navigation-menu.tsx +++ b/libs/ui-base-components/src/lib/navigation-menu.tsx @@ -1,13 +1,14 @@ -import * as React from "react" -import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" -import { cva } from "class-variance-authority" -import { ChevronDown } from "react-bootstrap-icons" +import * as React from "react"; +import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; +import { cva } from "class-variance-authority"; +import { ChevronDown } from "react-bootstrap-icons"; -import { cn } from "./utils" +import { cn } from "./utils"; const NavigationMenu = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, children, ...props }, ref) => ( -)) -NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName +)); +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; const NavigationMenuList = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, ...props }, ref) => ( -)) -NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName +)); +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; const NavigationMenuItem = NavigationMenuPrimitive.Item; const navigationMenuTriggerStyle = cva( - "inline-flex items-center justify-center rounded-md text-m font-medium transition-colors focus:outline-none focus:bg-accent text-white focus:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none bg-primary hover:bg-primary hover:text-white data-[state=open]:bg-accent/50 data-[active]:bg-accent/50 h-10 py-2 px-4 group w-max" -) + "inline-flex items-center justify-center rounded-md text-m " + + "font-medium transition-colors focus:outline-none focus:bg-accent " + + "text-white focus:text-accent-foreground disabled:opacity-50 " + + "disabled:pointer-events-none bg-primary hover:bg-primary " + + "hover:text-white data-[state=open]:bg-accent/50 " + + "data-[active]:bg-accent/50 h-10 py-2 px-4 group w-max" +); const NavigationMenuTrigger = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, children, ...props }, ref) => ( {children}{" "} -)) -NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName +)); +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; const NavigationMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, ...props }, ref) => ( -)) -NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName +)); +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; -const NavigationMenuLink = NavigationMenuPrimitive.Link +const NavigationMenuLink = NavigationMenuPrimitive.Link; const NavigationMenuViewport = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, ...props }, ref) => (
    -)) +)); NavigationMenuViewport.displayName = - NavigationMenuPrimitive.Viewport.displayName + NavigationMenuPrimitive.Viewport.displayName; const NavigationMenuIndicator = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + {className?: string | undefined} >(({ className, ...props }, ref) => ( -
    +
    -)) +)); NavigationMenuIndicator.displayName = - NavigationMenuPrimitive.Indicator.displayName + NavigationMenuPrimitive.Indicator.displayName; const NavigationMenuListItem = React.forwardRef< React.ElementRef<"a">, - React.ComponentPropsWithoutRef<"a"> + React.ComponentPropsWithoutRef<"a"> & + {className?: string | undefined, title?: string} >(({ className, title, children, ...props }, ref) => { return (
  • @@ -125,7 +155,10 @@ const NavigationMenuListItem = React.forwardRef< React.ReactElement | null = PanelResizeHandle as (props: PanelResizeHandleProps) => React.ReactElement | null; +const PanelResizeHandleElement: (props: PanelResizeHandleProps) => + React.ReactElement> | null = + PanelResizeHandle as (props: PanelResizeHandleProps) => + React.ReactElement> | null; const ResizablePanelGroup = ({ className, @@ -31,14 +40,27 @@ const ResizableHandle = ({ }) => ( div]:rotate-90", + "relative flex w-px items-center justify-center bg-border " + + "after:absolute after:inset-y-0 after:left-1/2 after:w-1 " + + "after:-translate-x-1/2 focus-visible:outline-none " + + "focus-visible:ring-1 " + + "focus-visible:ring-ring focus-visible:ring-offset-1 " + + "data-[panel-group-direction=vertical]:h-px " + + "data-[panel-group-direction=vertical]:w-full " + + "data-[panel-group-direction=vertical]:after:left-0 " + + "data-[panel-group-direction=vertical]:after:h-1 " + + "data-[panel-group-direction=vertical]:after:w-full " + + "data-[panel-group-direction=vertical]:after:-translate-y-1/2 " + + "data-[panel-group-direction=vertical]:after:translate-x-0 " + + "[&[data-panel-group-direction=vertical]>div]:rotate-90", className )} {...props} > {withHandle && (
    + className="z-10 flex h-4 w-3 items-center + justify-center rounded-sm border bg-border">
    )} diff --git a/libs/ui-base-components/src/lib/select.tsx b/libs/ui-base-components/src/lib/select.tsx index ef7b710d..b399c63a 100644 --- a/libs/ui-base-components/src/lib/select.tsx +++ b/libs/ui-base-components/src/lib/select.tsx @@ -1,24 +1,29 @@ -"use client" +"use client"; -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" -import { Check, ChevronDown } from "lucide-react" -import { cn } from "@ove/ui-base-components"; +import * as React from "react"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown } from "lucide-react"; +import { cn } from "./utils"; -const Select = SelectPrimitive.Root +const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group +const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value +const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + { className?: string | undefined } >(({ className, children, ...props }, ref) => ( -)) -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + { className?: string | undefined, position: "item-aligned" | "popper" } >(({ className, children, position = "popper", ...props }, ref) => ( {children} -)) -SelectContent.displayName = SelectPrimitive.Content.displayName +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + { className?: string | undefined } >(({ className, ...props }, ref) => ( -)) -SelectLabel.displayName = SelectPrimitive.Label.displayName +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; const SelectItem = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + { className?: string | undefined } >(({ className, children, ...props }, ref) => ( - + @@ -93,20 +115,21 @@ const SelectItem = React.forwardRef< {children} -)) -SelectItem.displayName = SelectPrimitive.Item.displayName +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; const SelectSeparator = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef + React.ComponentPropsWithoutRef & + { className?: string | undefined } >(({ className, ...props }, ref) => ( -)) -SelectSeparator.displayName = SelectPrimitive.Separator.displayName +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; export { Select, @@ -116,5 +139,5 @@ export { SelectContent, SelectLabel, SelectItem, - SelectSeparator, -} + SelectSeparator +}; diff --git a/libs/ui-base-components/src/lib/sonner.tsx b/libs/ui-base-components/src/lib/sonner.tsx index 1128edfc..2945511a 100644 --- a/libs/ui-base-components/src/lib/sonner.tsx +++ b/libs/ui-base-components/src/lib/sonner.tsx @@ -1,10 +1,11 @@ -import { useTheme } from "next-themes" -import { Toaster as Sonner } from "sonner" +import React from "react"; +import { useTheme } from "next-themes"; +import { Toaster as Sonner } from "sonner"; type ToasterProps = React.ComponentProps const Toaster = ({ ...props }: ToasterProps) => { - const { theme = "system" } = useTheme() + const { theme = "system" } = useTheme(); return ( { toastOptions={{ classNames: { toast: - "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg", + "group toast group-[.toaster]:bg-background " + + "group-[.toaster]:text-foreground " + + "group-[.toaster]:border-border group-[.toaster]:shadow-lg", description: "group-[.toast]:text-muted-foreground", actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground", cancelButton: - "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground", - }, + "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground" + } }} {...props} /> - ) -} + ); +}; -export { Toaster } +export { Toaster }; diff --git a/libs/ui-base-components/src/lib/utils.ts b/libs/ui-base-components/src/lib/utils.ts index c20489c8..a500a738 100644 --- a/libs/ui-base-components/src/lib/utils.ts +++ b/libs/ui-base-components/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; -export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); \ No newline at end of file +export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); diff --git a/libs/ui-base-components/tailwind.config.js b/libs/ui-base-components/tailwind.config.js index 7cb7e37a..c8e4dad4 100644 --- a/libs/ui-base-components/tailwind.config.js +++ b/libs/ui-base-components/tailwind.config.js @@ -1,11 +1,11 @@ -/** @type {import('tailwindcss').Config} */ +/** @type {import("tailwindcss").Config} */ module.exports = { darkMode: ["class"], content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", ], prefix: "", theme: { @@ -74,4 +74,4 @@ module.exports = { }, }, plugins: [require("tailwindcss-animate")], -} \ No newline at end of file +}; diff --git a/libs/ui-components/.eslintrc.json b/libs/ui-components/.eslintrc.json index 734ddace..1e382421 100644 --- a/libs/ui-components/.eslintrc.json +++ b/libs/ui-components/.eslintrc.json @@ -1,17 +1,48 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "plugin:@nrwl/nx/react", + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": { + "react/jsx-indent-props": [ + "error", + "first" + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "ignoredNodes": [ + "JSXAttribute" + ] + } + ] + } }, { - "files": ["*.ts", "*.tsx"], + "files": [ + "*.ts", + "*.tsx" + ], "rules": {} }, { - "files": ["*.js", "*.jsx"], + "files": [ + "*.js", + "*.jsx" + ], "rules": {} } ] diff --git a/libs/ui-components/postcss.config.js b/libs/ui-components/postcss.config.js index c72626d6..6d8b356d 100644 --- a/libs/ui-components/postcss.config.js +++ b/libs/ui-components/postcss.config.js @@ -1,6 +1,8 @@ -const { join } = require('path'); +const { join } = require("path"); -// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build +// Note: If you use library-specific +// PostCSS/Tailwind configuration then you should remove +// the `postcssConfig` build // option from your application's configuration (i.e. project.json). // // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries @@ -8,7 +10,7 @@ const { join } = require('path'); module.exports = { plugins: { tailwindcss: { - config: join(__dirname, 'tailwind.config.js'), + config: join(__dirname, "tailwind.config.js"), }, autoprefixer: {}, }, diff --git a/libs/ui-components/src/index.ts b/libs/ui-components/src/index.ts index a577bc17..d3a4752d 100644 --- a/libs/ui-components/src/index.ts +++ b/libs/ui-components/src/index.ts @@ -8,4 +8,4 @@ export { default as PowerMode } from "./lib/power-mode/power-mode"; export { default as LastUpdated } from "./lib/calendar/last-updated"; export { default as Calendar } from "./lib/calendar/calendar"; export { useCalendar } from "./lib/calendar/hooks"; -export { useMode } from "./lib/power-mode/hooks"; \ No newline at end of file +export { useMode } from "./lib/power-mode/hooks"; diff --git a/libs/ui-components/src/lib/calendar/calendar.tsx b/libs/ui-components/src/lib/calendar/calendar.tsx index 391964da..4279f958 100644 --- a/libs/ui-components/src/lib/calendar/calendar.tsx +++ b/libs/ui-components/src/lib/calendar/calendar.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { dateFnsLocalizer, Calendar as CalendarDisplay diff --git a/libs/ui-components/src/lib/calendar/hooks.ts b/libs/ui-components/src/lib/calendar/hooks.ts index 9f5655c3..d0f58117 100644 --- a/libs/ui-components/src/lib/calendar/hooks.ts +++ b/libs/ui-components/src/lib/calendar/hooks.ts @@ -25,7 +25,8 @@ export const useCalendar = (response: Calendar | OVEException | undefined) => { const [lastUpdated, setLastUpdated] = useState(null); const fetchCalendar = useCallback(() => { - if (response === undefined || "oveError" in response || response.lastUpdated === null) return; + if (response === undefined || "oveError" in response || + response.lastUpdated === null) return; setLastUpdated(formatLastUpdated(new Date(response.lastUpdated))); setCalendar(response["value"].map(event => ({ title: event["title"], @@ -39,4 +40,4 @@ export const useCalendar = (response: Calendar | OVEException | undefined) => { }, [fetchCalendar]); return { calendar, lastUpdated, refresh: fetchCalendar }; -}; \ No newline at end of file +}; diff --git a/libs/ui-components/src/lib/calendar/last-updated.tsx b/libs/ui-components/src/lib/calendar/last-updated.tsx index 142cf4d9..09add675 100644 --- a/libs/ui-components/src/lib/calendar/last-updated.tsx +++ b/libs/ui-components/src/lib/calendar/last-updated.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { RefreshCcw } from "lucide-react"; import styles from "./calendar.module.scss"; diff --git a/libs/ui-components/src/lib/dialog/dialog.tsx b/libs/ui-components/src/lib/dialog/dialog.tsx index 1c201b5d..bf88b231 100644 --- a/libs/ui-components/src/lib/dialog/dialog.tsx +++ b/libs/ui-components/src/lib/dialog/dialog.tsx @@ -1,4 +1,4 @@ -import { type CSSProperties, forwardRef, type ReactNode } from "react"; +import React, { type CSSProperties, forwardRef, type ReactNode } from "react"; import styles from "./dialog.module.scss"; @@ -17,12 +17,15 @@ const Dialog = forwardRef(({ children, style }, ref) => + // noinspection typescript:S6847 – onClick -
    e.stopPropagation()} className={styles.hidden} - style={hiddenStyle}> +
    e.stopPropagation()} + className={styles.hidden} style={hiddenStyle}> {children}
    ); +Dialog.displayName = "Dialog"; + export default Dialog; diff --git a/libs/ui-components/src/lib/dialog/hook.ts b/libs/ui-components/src/lib/dialog/hook.ts index e4df14e9..ef174404 100644 --- a/libs/ui-components/src/lib/dialog/hook.ts +++ b/libs/ui-components/src/lib/dialog/hook.ts @@ -21,4 +21,4 @@ export const useDialog = () => { closeDialog: () => setDialogOpen(false), openDialog: () => setDialogOpen(true) }; -}; \ No newline at end of file +}; diff --git a/libs/ui-components/src/lib/nav.tsx b/libs/ui-components/src/lib/nav.tsx index adcc055b..389c477b 100644 --- a/libs/ui-components/src/lib/nav.tsx +++ b/libs/ui-components/src/lib/nav.tsx @@ -1,3 +1,5 @@ +/* global JSX */ + import * as React from "react"; import { NavigationMenu, @@ -25,7 +27,11 @@ const Nav = ({ icon: { asset, alt }, content }: NavProps) => { const location = useLocation(); const isLogin = location.pathname === "/login"; - const getNavigationMenuItem = (card: JSX.Element | null, title: string, item: JSX.Element | null) => { + const getNavigationMenuItem = ( + card: JSX.Element | null, + title: string, + item: JSX.Element | null + ) => { return {card !== null ? <> {title} @@ -35,30 +41,27 @@ const Nav = ({ icon: { asset, alt }, content }: NavProps) => { ; }; - return ( - <> - -

    - - - {alt} - - - {isLogin ? null : - - {content.map(({ - title, - card, - item, - location - }) => location !== null ? {getNavigationMenuItem(card, title, item)} : getNavigationMenuItem(card, title, item))} - } -
    - - ); + return + + + {alt} + + + {isLogin ? null : + + {content.map(({ + title, + card, + item, + location + }) => { + const menuItem = getNavigationMenuItem(card, title, item); + return location !== null ? {menuItem} : menuItem; + })} + } + ; }; export default Nav; diff --git a/libs/ui-components/src/lib/power-mode/hooks.ts b/libs/ui-components/src/lib/power-mode/hooks.ts index c7c3391d..6b96eaec 100644 --- a/libs/ui-components/src/lib/power-mode/hooks.ts +++ b/libs/ui-components/src/lib/power-mode/hooks.ts @@ -3,12 +3,20 @@ import { type InboundAPI, type PowerMode } from "@ove/ove-types"; -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; -export type ModeController = Pick +export type ModeController = Pick< + InboundAPI, + "setManualSchedule" | "setAutoSchedule" | "setEcoSchedule" +> -export const useMode = (calendar: CalendarEvent[], mode: PowerMode | null, setMode: (mode: PowerMode | null) => void, controller: ModeController) => { - const refreshMode = (mode_: PowerMode | null) => { +export const useMode = ( + calendar: CalendarEvent[], + mode: PowerMode | null, + setMode: (mode: PowerMode | null) => void, + controller: ModeController +) => { + const refreshMode = useCallback((mode_: PowerMode | null) => { if (mode_ === null) return; switch (mode_) { case "manual": @@ -18,18 +26,19 @@ export const useMode = (calendar: CalendarEvent[], mode: PowerMode | null, setMo controller.setAutoSchedule({}).catch(console.error); break; case "eco": - controller.setEcoSchedule({ ecoSchedule: calendar }).catch(console.error); + controller.setEcoSchedule({ ecoSchedule: calendar }) + .catch(console.error); break; } - }; + }, [calendar, controller]); useEffect(() => { refreshMode(mode); - }, [calendar, mode]); + }, [calendar, mode, refreshMode]); return { setManual: () => setMode("manual"), setAuto: () => setMode("auto"), setEco: () => setMode("eco") }; -}; \ No newline at end of file +}; diff --git a/libs/ui-components/src/lib/power-mode/power-mode.tsx b/libs/ui-components/src/lib/power-mode/power-mode.tsx index 8287aef4..db65fc5f 100644 --- a/libs/ui-components/src/lib/power-mode/power-mode.tsx +++ b/libs/ui-components/src/lib/power-mode/power-mode.tsx @@ -1,4 +1,8 @@ -import { type CalendarEvent, type PowerMode as TPowerMode } from "@ove/ove-types"; +import React from "react"; +import { + type CalendarEvent, + type PowerMode as TPowerMode +} from "@ove/ove-types"; import { type ModeController, useMode } from "./hooks"; import styles from "./power-mode.module.scss"; @@ -10,22 +14,26 @@ type PowerModeProps = { setMode: (mode: TPowerMode | null) => void } -const PowerMode = ({calendar, controller, mode, setMode}: PowerModeProps) => { - const { setManual, setAuto, setEco } = useMode(calendar, mode, setMode, controller); - return
    - - -
    ; }; -export default PowerMode; \ No newline at end of file +export default PowerMode; diff --git a/libs/ui-components/src/lib/snackbar/hook.ts b/libs/ui-components/src/lib/snackbar/hook.ts index 1d331b47..b02b8ca1 100644 --- a/libs/ui-components/src/lib/snackbar/hook.ts +++ b/libs/ui-components/src/lib/snackbar/hook.ts @@ -15,4 +15,4 @@ export const useSnackbar = () => { show: showNotification, isVisible: notification !== null }; -}; \ No newline at end of file +}; diff --git a/libs/ui-components/src/lib/snackbar/snackbar.tsx b/libs/ui-components/src/lib/snackbar/snackbar.tsx index 24166266..ec020d9c 100644 --- a/libs/ui-components/src/lib/snackbar/snackbar.tsx +++ b/libs/ui-components/src/lib/snackbar/snackbar.tsx @@ -1,3 +1,4 @@ +import React from "react"; import styles from "./snackbar.module.scss"; type SnackbarProps = { @@ -5,7 +6,7 @@ type SnackbarProps = { show: boolean } -const Snackbar = ({ text, show }: SnackbarProps) => <>{show ? -
    {text}
    : null} +const Snackbar = ({ text, show }: SnackbarProps) => show ? +
    {text}
    : null; -export default Snackbar; \ No newline at end of file +export default Snackbar; diff --git a/libs/ui-components/src/lib/video-streams/video-streams.tsx b/libs/ui-components/src/lib/video-streams/video-streams.tsx index d9e684f1..ecf67314 100644 --- a/libs/ui-components/src/lib/video-streams/video-streams.tsx +++ b/libs/ui-components/src/lib/video-streams/video-streams.tsx @@ -1,3 +1,4 @@ +import React from "react"; import styles from "./video-streams.module.scss"; const VideoStreams = ({ streams }: { streams: string[] | undefined }) => diff --git a/libs/ui-components/tailwind.config.js b/libs/ui-components/tailwind.config.js index d00839f6..c6836fa1 100644 --- a/libs/ui-components/tailwind.config.js +++ b/libs/ui-components/tailwind.config.js @@ -1,12 +1,12 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind'); -const { join } = require('path'); +const { createGlobPatternsForDependencies } = require("@nrwl/react/tailwind"); +const { join } = require("path"); -/** @type {import('tailwindcss').Config} */ +/** @type {import("tailwindcss").Config} */ module.exports = { content: [ join( __dirname, - '{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}' + "{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}" ), ...createGlobPatternsForDependencies(__dirname), ], diff --git a/libs/ui-reorderable-list/src/index.ts b/libs/ui-reorderable-list/src/index.ts index af7bbf3f..50db4517 100644 --- a/libs/ui-reorderable-list/src/index.ts +++ b/libs/ui-reorderable-list/src/index.ts @@ -1,8 +1,13 @@ import { type ComponentType, CSSProperties, type ReactNode } from "react"; // @ts-expect-error - JSX -import ReOrderableItem from "./lib/reorderable-list/components/reorderable-item.jsx"; +import Item from "./lib/reorderable-list/components/reorderable-item.jsx"; // @ts-expect-error - JSX -import ReOrderableList from "./lib/reorderable-list/components/reorderable-list.jsx"; +import List from "./lib/reorderable-list/components/reorderable-list.jsx"; -export const ReorderableItem: ComponentType<{ children: ReactNode }> = ReOrderableItem; -export const ReorderableList: ComponentType<{ children: ReactNode, list: any[], onListUpdate: (newList: any[]) => void, style: CSSProperties }> = ReOrderableList; \ No newline at end of file +export const ReorderableItem: ComponentType<{ children: ReactNode }> = Item; +export const ReorderableList: ComponentType<{ + children: ReactNode, + list: object[], + onListUpdate: (newList: object[]) => void, + style: CSSProperties +}> = List; diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx index 44a6a6a1..fb0e6872 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-item.jsx @@ -1,18 +1,24 @@ -import React, { Component, createRef } from 'react' +/* global ReactElement */ + +import React, { Component, createRef } from "react"; +import PropTypes from "prop-types"; import { appendClassIfNotExists, getIntersectingElementOnList, isTouchDevice, - JSXToDOMElement, + jsxToDOMElement, removeClassIfExists -} from '../lib/dom' -import ListItem from '../models/list-item' -import styles from '../css/reorderable-item.css?inline' -import { distance } from '../lib/math' +} from "../lib/dom"; +import ListItem from "../models/list-item"; +import styles from "../css/reorderable-item.css?inline"; +import { distance } from "../lib/math"; +// eslint-disable-next-line valid-jsdoc /** * A wrapper for reorderable items. Handles drag and drop logic. * + * @typedef {import("react").ReactElement} ReactElement + * * @class ReOrderableItem * @author Ezequiel Sam Ceracas * @version 1.1.0 @@ -26,19 +32,37 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ static defaultProps = { - drag: (item) => { - const itemCopy = item.clonedItemElement - itemCopy.style.width = `${item.rect.width}px` - itemCopy.style.height = `${item.rect.height}px` - itemCopy.style.zIndex = '9000' - return itemCopy + drag: item => { + const itemCopy = item.clonedItemElement; + itemCopy.style.width = `${item.rect.width}px`; + itemCopy.style.height = `${item.rect.height}px`; + itemCopy.style.zIndex = "9000"; + return itemCopy; }, enabled: true, - component: `div`, + component: "div", componentProps: null, itemIndex: 0, itemData: null - } + }; + + static propTypes = { + children: PropTypes.instanceOf(ReactElement), + component: PropTypes.instanceOf(ReactElement), + list: PropTypes.array, + onItemDrag: PropTypes.func, + onItemDragEnd: PropTypes.func, + onItemDragStart: PropTypes.func, + itemIndex: PropTypes.number, + itemData: PropTypes.object, + enabled: PropTypes.bool, + componentProps: PropTypes.object, + drag: PropTypes.func + }; + + context; + refs; + state; /** * Creates an instance of ReOrderableItem. @@ -47,53 +71,53 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ constructor(props) { - super(props) + super(props); - this._beforeDragRect = null - this._halfWidth = null - this._halfHeight = null + this._beforeDragRect = null; + this._halfWidth = null; + this._halfHeight = null; this._offset = { x: 0, y: 0 - } + }; this._dragTrigger = { mouseDown: false, prevX: 0, prevY: 0 - } + }; this._inputEvents = { touch: { - down: 'touchstart', - up: 'touchend', - move: 'touchmove' + down: "touchstart", + up: "touchend", + move: "touchmove" }, click: { - down: 'mousedown', - up: 'mouseup', - move: 'mousemove' + down: "mousedown", + up: "mouseup", + move: "mousemove" } - } + }; - this._itemRef = null + this._itemRef = null; - this._isDragging = false - this._draggedElement = null - this._overlappingList = null - this._clonedItem = null + this._isDragging = false; + this._draggedElement = null; + this._overlappingList = null; + this._clonedItem = null; - this._model = new ListItem(props) + this._model = new ListItem(props); - this._onItemMouseMove = this._onItemMouseMove.bind(this) - this._onItemMouseUp = this._onItemMouseUp.bind(this) - this._onItemMouseDown = this._onItemMouseDown.bind(this) + this._onItemMouseMove = this._onItemMouseMove.bind(this); + this._onItemMouseUp = this._onItemMouseUp.bind(this); + this._onItemMouseDown = this._onItemMouseDown.bind(this); - this._handleDrag = this._handleDrag.bind(this) - this._handleDragStart = this._handleDragStart.bind(this) - this._handleDragEnd = this._handleDragEnd.bind(this) - this._itemRef = createRef() + this._handleDrag = this._handleDrag.bind(this); + this._handleDragStart = this._handleDragStart.bind(this); + this._handleDragEnd = this._handleDragEnd.bind(this); + this._itemRef = createRef(); } /** @@ -104,7 +128,7 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ get instanceID() { - return this._model.instanceID + return this._model.instanceID; } /** @@ -115,11 +139,12 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ get model() { - return this._model + return this._model; } /** - * Returns the HTMLElement of the dragged item. This becomes ```null``` when no drag and drop action is happening. + * Returns the HTMLElement of the dragged item. + * This becomes ```null``` when no drag and drop action is happening. * * @type {HTMLElement|null} * @readonly @@ -127,12 +152,11 @@ export default class ReOrderableItem extends Component { */ get draggedElement() { if (!this._draggedElement) { - const element = this.props.drag?.(this) - this._draggedElement = React.isValidElement(element) - ? JSXToDOMElement(element) - : element + const element = this.props.drag?.(this); + this._draggedElement = React.isValidElement(element) ? + jsxToDOMElement(element) : element; } - return this._draggedElement + return this._draggedElement; } /** @@ -143,22 +167,24 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ get listComponent() { - return this.props.list + return this.props.list; } /** - * Returns the overlapping list element. This becomes ```null``` when there are no overlapping lists. + * Returns the overlapping list element. + * This becomes ```null``` when there are no overlapping lists. * * @type {HTMLElement|null} * @readonly * @memberof ReOrderableItem */ get overlappingListElement() { - return this._overlappingList + return this._overlappingList; } /** - * Clones and returns a copy of the item element. Will not reclone once there's an instantiated instance of the clone. + * Clones and returns a copy of the item element. + * Will not re-clone once there"s an instantiated instance of the clone. * * @type {Node} * @readonly @@ -166,9 +192,9 @@ export default class ReOrderableItem extends Component { */ get clonedItemElement() { if (!this._clonedItem) { - this._clonedItem = this._itemRef.cloneNode(true) + this._clonedItem = this._itemRef.cloneNode(true); } - return this._clonedItem + return this._clonedItem; } /** @@ -179,7 +205,7 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ get rect() { - return this._beforeDragRect + return this._beforeDragRect; } /** @@ -188,16 +214,16 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ componentDidMount() { - const inputControl = isTouchDevice() ? 'touch' : 'click' - const inputEvents = this._inputEvents[inputControl] + const inputControl = isTouchDevice() ? "touch" : "click"; + const inputEvents = this._inputEvents[inputControl]; document.addEventListener(inputEvents.move, this._handleDrag, { passive: false - }) - document.addEventListener(inputEvents.up, this._handleDragEnd) - this._itemRef.addEventListener(inputEvents.down, this._onItemMouseDown) - this._itemRef.addEventListener(inputEvents.move, this._onItemMouseMove) - this._itemRef.addEventListener(inputEvents.up, this._onItemMouseUp) + }); + document.addEventListener(inputEvents.up, this._handleDragEnd); + this._itemRef.addEventListener(inputEvents.down, this._onItemMouseDown); + this._itemRef.addEventListener(inputEvents.move, this._onItemMouseMove); + this._itemRef.addEventListener(inputEvents.up, this._onItemMouseUp); } /** @@ -206,15 +232,15 @@ export default class ReOrderableItem extends Component { * @memberof ReOrderableItem */ componentWillUnmount() { - const inputControl = isTouchDevice() ? 'touch' : 'click' - const inputEvents = this._inputEvents[inputControl] + const inputControl = isTouchDevice() ? "touch" : "click"; + const inputEvents = this._inputEvents[inputControl]; document.removeEventListener(inputEvents.move, this._handleDrag, { passive: false - }) - document.removeEventListener(inputEvents.up, this._handleDragEnd) - this._itemRef.removeEventListener(inputEvents.down, this._onItemMouseDown) - this._itemRef.removeEventListener(inputEvents.move, this._onItemMouseMove) - this._itemRef.removeEventListener(inputEvents.up, this._onItemMouseUp) + }); + document.removeEventListener(inputEvents.up, this._handleDragEnd); + this._itemRef.removeEventListener(inputEvents.down, this._onItemMouseDown); + this._itemRef.removeEventListener(inputEvents.move, this._onItemMouseMove); + this._itemRef.removeEventListener(inputEvents.up, this._onItemMouseUp); } /** @@ -234,37 +260,37 @@ export default class ReOrderableItem extends Component { cancelable: true, detail }) - ) + ); } /** * Checks for overlapping list containers. Will dispatch the following events: * * dragexit - Dispatched when exiting a list container. * * dragenter - Dispatched when entering a list container. - * * dragover - Dispatches when the dragged element is moved within the list container. + * * dragover - Dispatches when the dragged element + * is moved within the list container. * * @private - * @returns {void} * @memberof ReOrderableItem */ _checkOverlappingElements() { - const list = Array.prototype.slice.call(this._groupListElements) - const previousList = this._overlappingList + const list = Array.prototype.slice.call(this._groupListElements); + const previousList = this._overlappingList; this._overlappingList = getIntersectingElementOnList( this.draggedElement, list - ) + ); if (previousList && !this._overlappingList) { - this._dispatchCustomEvent(previousList, 'dragexit', { + this._dispatchCustomEvent(previousList, "dragexit", { item: this - }) + }); } if (!this._overlappingList) { - appendClassIfNotExists(document.body, styles.noDrop) - return + appendClassIfNotExists(document.body, styles.noDrop); + return; } if ( @@ -272,33 +298,38 @@ export default class ReOrderableItem extends Component { (previousList && previousList !== this._overlappingList) ) { if (previousList && previousList !== this._overlappingList) { - this._dispatchCustomEvent(previousList, 'dragexit', { + this._dispatchCustomEvent(previousList, "dragexit", { item: this - }) + }); } - this._dispatchCustomEvent(this._overlappingList, 'dragenter', { + this._dispatchCustomEvent(this._overlappingList, "dragenter", { item: this - }) - removeClassIfExists(document.body, styles.noDrop) + }); + removeClassIfExists(document.body, styles.noDrop); } - this._dispatchCustomEvent(this._overlappingList, 'dragover', { + this._dispatchCustomEvent(this._overlappingList, "dragover", { item: this - }) + }); } + /** + * Drag handler helper + * @param {object} event - drag event to handle + * @private + */ _handleDrag(event) { - if (!this._isDragging) return - if (isTouchDevice()) event.preventDefault() - const input = isTouchDevice() ? event.changedTouches[0] : event - const dragX = input.pageX - this._halfWidth - this._offset.x - const dragY = input.pageY - this._halfHeight - this._offset.y + if (!this._isDragging) return; + if (isTouchDevice()) event.preventDefault(); + const input = isTouchDevice() ? event.changedTouches[0] : event; + const dragX = input.pageX - this._halfWidth - this._offset.x; + const dragY = input.pageY - this._halfHeight - this._offset.y; - this.draggedElement.style.left = `${dragX}px` - this.draggedElement.style.top = `${dragY}px` + this.draggedElement.style.left = `${dragX}px`; + this.draggedElement.style.top = `${dragY}px`; - this._checkOverlappingElements() + this._checkOverlappingElements(); this.props.onItemDrag?.({ item: this, @@ -308,19 +339,24 @@ export default class ReOrderableItem extends Component { pageY: input.pageY, clientX: input.clientX, clientY: input.clientY - }) + }); } + /** + * Drag event end handler helper. + * @param {object} event - drag event to handle the end of. + * @private + */ _handleDragEnd(event) { - if (!this._isDragging) return - const input = isTouchDevice() ? event.changedTouches[0] : event + if (!this._isDragging) return; + const input = isTouchDevice() ? event.changedTouches[0] : event; - removeClassIfExists(document.body, styles.noDrop) + removeClassIfExists(document.body, styles.noDrop); if (this._overlappingList) { this._overlappingList.dispatchEvent( // eslint-disable-next-line no-undef - new CustomEvent('drop', { + new CustomEvent("drop", { bubbles: true, cancelable: true, detail: { @@ -328,7 +364,7 @@ export default class ReOrderableItem extends Component { list: this.listComponent } }) - ) + ); } this.props.onItemDragEnd?.({ @@ -337,55 +373,60 @@ export default class ReOrderableItem extends Component { pageY: input.pageY, clientX: input.clientX, clientY: input.clientY - }) + }); - this._overlappingList = null - this._isDragging = false + this._overlappingList = null; + this._isDragging = false; if (this._itemRef) { - this._itemRef.style.display = '' - this._itemRef.hidden = false + this._itemRef.style.display = ""; + this._itemRef.hidden = false; } - this._clonedItem = null - this._draggedElement?.remove() - this._draggedElement = null + this._clonedItem = null; + this._draggedElement?.remove(); + this._draggedElement = null; } + /** + * Drag start event handler helper. + * @param {object} event - drag event to handle end of. + * @private + */ _handleDragStart(event) { - if (this._isDragging) return + if (this._isDragging) return; - document.getSelection().empty() + document.getSelection().empty(); - const input = isTouchDevice() ? event.changedTouches[0] : event - this._clonedItem = null - this._beforeDragRect = this._itemRef.getBoundingClientRect() + const input = isTouchDevice() ? event.changedTouches[0] : event; + this._clonedItem = null; + this._beforeDragRect = this._itemRef.getBoundingClientRect(); this._groupListElements = document.querySelectorAll( `.${this.listComponent.model.groupID}` - ) + ); if (!this._halfWidth && !this._halfHeight) { - this._halfWidth = this._beforeDragRect.width / 2 - this._halfHeight = this._beforeDragRect.height / 2 + this._halfWidth = this._beforeDragRect.width / 2; + this._halfHeight = this._beforeDragRect.height / 2; } this._offset = { x: input.clientX - this._beforeDragRect.left - this._halfWidth, y: input.clientY - this._beforeDragRect.top - this._halfHeight - } + }; - const dragX = input.pageX - this._halfWidth - this._offset.x - const dragY = input.pageY - this._halfHeight - this._offset.y + const dragX = input.pageX - this._halfWidth - this._offset.x; + const dragY = input.pageY - this._halfHeight - this._offset.y; - this._isDragging = true + this._isDragging = true; - this.draggedElement.style.position = 'absolute' - this.draggedElement.style.left = `${dragX}px` - this.draggedElement.style.top = `${dragY}px` + this.draggedElement.style.position = "absolute"; + this.draggedElement.style.left = `${dragX}px`; + this.draggedElement.style.top = `${dragY}px`; - document.body.appendChild(this.draggedElement) + document.body.appendChild(this.draggedElement); - this._itemRef.style.display = 'none' - this._itemRef.hidden = true + this._itemRef.style.display = "none"; + this._itemRef.hidden = true; this.props.onItemDragStart?.({ item: this, @@ -395,19 +436,20 @@ export default class ReOrderableItem extends Component { pageY: input.pageY, clientX: input.clientX, clientY: input.clientY - }) + }); - this._checkOverlappingElements() + this._checkOverlappingElements(); } /** * Mouse move event handler. * + * @param {object} event - mouse move event to handle * @private * @memberof ReOrderableItem */ - _onItemMouseMove = (event) => { - const input = isTouchDevice() ? event.changedTouches[0] : event + _onItemMouseMove = event => { + const input = isTouchDevice() ? event.changedTouches[0] : event; if (this._dragTrigger.mouseDown && !this._isDragging) { const dist = distance( @@ -419,67 +461,69 @@ export default class ReOrderableItem extends Component { x: this._dragTrigger.prevX, y: this._dragTrigger.prevY } - ) + ); if (dist >= 1) { - this._dragTrigger.mouseDown = false - this._handleDragStart(event) + this._dragTrigger.mouseDown = false; + this._handleDragStart(event); } } - } + }; /** * Mouse up event handler. * + * @param {object} _event - mouse up event to handle. * @private * @memberof ReOrderableItem */ - _onItemMouseUp = (event) => { + _onItemMouseUp = _event => { if (this._dragTrigger.mouseDown) { - this._dragTrigger.mouseDown = false + this._dragTrigger.mouseDown = false; } - } + }; /** * Mouse down event handler. * + * @param {object} event - mouse down event to handle. * @private * @memberof ReOrderableItem */ - _onItemMouseDown = (event) => { - const input = isTouchDevice() ? event.changedTouches[0] : event + _onItemMouseDown = event => { + const input = isTouchDevice() ? event.changedTouches[0] : event; const firstMatch = document .elementsFromPoint(input.clientX, input.clientY) - .find((e) => e.classList.contains('ui-reorderable-item')) - if (firstMatch !== this._itemRef || this._dragTrigger.mouseDown) return - this._dragTrigger.mouseDown = true - this._dragTrigger.prevX = input.pageX - this._dragTrigger.prevY = input.pageY - } + .find(e => e.classList.contains("ui-reorderable-item")); + if (firstMatch !== this._itemRef || this._dragTrigger.mouseDown) return; + this._dragTrigger.mouseDown = true; + this._dragTrigger.prevX = input.pageX; + this._dragTrigger.prevY = input.pageY; + }; /** * Renders the component. * - * @returns {import("react").ReactElement} + * @return {ReactElement} * @memberof ReOrderableItem */ render() { - const Component = this.props.component - this._model = new ListItem(this.props) + const Component = this.props.component; + this._model = new ListItem(this.props); return ( (this._itemRef = ref)} + ].join(" ")} + ref={ref => (this._itemRef = ref)} > {React.Children.only(this.props.children)} - ) + ); } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list-group.jsx b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list-group.jsx index ad719866..c4de62bb 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list-group.jsx +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list-group.jsx @@ -1,12 +1,17 @@ -/* eslint-disable no-unused-expressions */ -import React, { Component } from 'react' -import { get, set } from '../lib/object' -import isEmpty from 'is-empty' -import ReOrderableList from './reorderable-list' +/* global ReactElement*/ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { get, set } from "../lib/object"; +import isEmpty from "is-empty"; +import ReOrderableList from "./reorderable-list"; + +// eslint-disable-next-line valid-jsdoc /** * A wrapper for several interacting ```ReorderableList``` components. * + * @typedef {import("react").ReactElement} ReactElement + * * @class ReOrderableListGroup * @author Ezequiel Sam Ceracas * @version 1.0.0 @@ -14,63 +19,72 @@ import ReOrderableList from './reorderable-list' */ export default class ReOrderableListGroup extends Component { static defaultProps = { - name: 'list', - listGroup: [] - } + name: "list", + }; + + static propTypes = { + group: PropTypes.any, + name: PropTypes.string, + onListGroupUpdate: PropTypes.func, + children: PropTypes.instanceOf(ReactElement), + }; /** * List update handler. * + * @param {unknown[]} newList - new list to update + * @param {string} path - path of item to update * @memberof ReOrderableListGroup */ _onListUpdate = (newList, path) => { - let listCopy = [...this.props.group] + let listCopy = [...this.props.group]; if (path) { - set(path, listCopy, newList) + set(path, listCopy, newList); } else { - listCopy = newList + listCopy = newList; } - this.props.onListGroupUpdate?.(listCopy) - } + this.props.onListGroupUpdate?.(listCopy); + }; /** - * Initializes the children. Only detects ```ReorderableList``` components and passes special properties to them. + * Initializes the children. Only detects + * ```ReorderableList``` components and passes special properties to them. * * @private - * @returns {Array} + * @return {Array} * @memberof ReOrderableListGroup */ _initializeChildren() { - let index = 0 - const childrenWithProps = React.Children.map( + let index = 0; + return React.Children.map( this.props.children, - (child) => { - if (React.isValidElement(child) && child.type === ReOrderableList) { + child => { + if (React.isValidElement(/** @type {object} */child) && + child.type === ReOrderableList) { const props = { name: this.props.name, - list: isEmpty(child.props.list) - ? get(index, this.props.group) - : child.props.list, + list: isEmpty(child.props.list) ? + get(index.toString(), this.props.group) : + child.props.list, path: isEmpty(child.props.path) ? index : child.props.path, onListUpdate: this._onListUpdate, group: this.props.group - } - index++ - return React.cloneElement(child, props) + }; + index++; + return React.cloneElement(child, props); } - return child + return child; } - ) - return childrenWithProps + ); } /** * Renders the component. * - * @returns {import("react").ReactElement} + * @return {ReactElement} * @memberof ReOrderableListGroup */ render() { - return {this._initializeChildren()} + return {this._initializeChildren()}; } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list.jsx b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list.jsx index 27c23945..557d6c13 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list.jsx +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/components/reorderable-list.jsx @@ -1,14 +1,13 @@ -/* eslint-disable no-unused-expressions */ -import PropTypes from 'prop-types' -import React, { Component, ReactElement } from 'react' +import PropTypes from "prop-types"; +import React, { Component, ReactElement } from "react"; import { - JSXToDOMElement, getClosestElement, insertAfter, - insertBefore -} from '../lib/dom' -import ReOrderableItem from './reorderable-item.jsx' -import ListController from '../controllers/list-controller' + insertBefore, + jsxToDOMElement +} from "../lib/dom"; +import ReOrderableItem from "./reorderable-item.jsx"; +import ListController from "../controllers/list-controller"; const isEmpty = x => { if (x === undefined) return true; @@ -17,12 +16,15 @@ const isEmpty = x => { if (y === "[]") return true; if (y === "{}") return true; if (y === "0") return true; - if (y === "") return true; - return false; + return y === ""; }; +// eslint-disable-next-line valid-jsdoc /** - * A container for reorderable list items. This contains the main logic for reordering and updating items. + * A container for reorderable list items. + * This contains the main logic for reordering and updating items. + * + * @typedef {import("react").ReactElement} ReactElement * * @class ReOrderableList * @author Ezequiel Sam Ceracas @@ -46,13 +48,15 @@ export default class ReOrderableList extends Component { PropTypes.object ]).isRequired, componentProps: PropTypes.object, - orientation: PropTypes.oneOf(['vertical', 'horizontal']).isRequired, + orientation: PropTypes.oneOf(["vertical", "horizontal"]).isRequired, placeholder: PropTypes.oneOfType([ PropTypes.instanceOf(Object), PropTypes.instanceOf(ReactElement) ]).isRequired, - group: PropTypes.oneOfType([PropTypes.object, PropTypes.array]) - } + group: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + children: PropTypes.instanceOf(ReactElement), + onListUpdate: PropTypes.func + }; /** * Default properties for this component. @@ -62,24 +66,20 @@ export default class ReOrderableList extends Component { * @type {Object} */ static defaultProps = { - name: '', - component: `div`, + name: "", + component: "div", list: [], path: 0, group: null, - orientation: 'vertical', - placeholder: (item) => { - return ( -
    - ) - } - } + orientation: "vertical", + placeholder: item =>
    + }; /** * Contains the currently dragged item. @@ -89,7 +89,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList * @type {ReOrderableItem} */ - static _currentDraggedItem = null + static _currentDraggedItem = null; /** * Settings for list orientation. @@ -101,14 +101,14 @@ export default class ReOrderableList extends Component { */ static _orientationSettings = { vertical: { - axis: 'y', - size: 'height' + axis: "y", + size: "height" }, horizontal: { - axis: 'x', - size: 'width' + axis: "x", + size: "width" } - } + }; /** * Creates an instance of ReOrderableList. @@ -117,10 +117,10 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ constructor(props) { - super(props) + super(props); - this._itemPlaceholder = null - this._controller = new ListController(props) + this._itemPlaceholder = null; + this._controller = new ListController(props); } /** @@ -130,7 +130,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ get instanceID() { - return this.controller.model.instanceID + return this.controller.model.instanceID; } /** @@ -140,7 +140,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ get groupID() { - return this.controller.model.groupID + return this.controller.model.groupID; } /** @@ -151,7 +151,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ get controller() { - return this._controller + return this._controller; } /** @@ -162,7 +162,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ get model() { - return this._controller.model + return this._controller.model; } /** @@ -174,7 +174,7 @@ export default class ReOrderableList extends Component { * @type {HTMLElement} */ get element() { - return this._listRef + return this._listRef; } /** @@ -186,7 +186,7 @@ export default class ReOrderableList extends Component { * @type {ReOrderableItem} */ get currentDraggedItem() { - return ReOrderableList._currentDraggedItem + return ReOrderableList._currentDraggedItem; } /** @@ -198,21 +198,20 @@ export default class ReOrderableList extends Component { * @type {HTMLElement} */ get itemPlaceholder() { - if (!this.currentDraggedItem) return null + if (!this.currentDraggedItem) return null; if (!this._itemPlaceholder) { - const element = this.props.placeholder?.(this.currentDraggedItem) - this._itemPlaceholder = React.isValidElement(element) - ? JSXToDOMElement(element) - : element + const element = this.props.placeholder?.(this.currentDraggedItem); + this._itemPlaceholder = React.isValidElement(element) ? + jsxToDOMElement(element) : element; - if (!this._itemPlaceholder.classList.contains('ui-reorderable-item')) { - this._itemPlaceholder.classList.add('ui-reorderable-item') + if (!this._itemPlaceholder.classList.contains("ui-reorderable-item")) { + this._itemPlaceholder.classList.add("ui-reorderable-item"); } - if (!this._itemPlaceholder.classList.contains('item-placeholder')) { - this._itemPlaceholder.classList.add('item-placeholder') + if (!this._itemPlaceholder.classList.contains("item-placeholder")) { + this._itemPlaceholder.classList.add("item-placeholder"); } } - return this._itemPlaceholder + return this._itemPlaceholder; } /** @@ -224,52 +223,48 @@ export default class ReOrderableList extends Component { * @type {Object} */ get orientationSettings() { - return ReOrderableList._orientationSettings[this.props.orientation] + return ReOrderableList._orientationSettings[this.props.orientation]; } /** - * Initializes the children. Only looks for instances of ```ReorderableItem``` and passes props to each instance. + * Initializes the children. Only looks for + * instances of ```ReorderableItem``` and passes props to each instance. * * @private - * @returns + * @return {Array} * @memberof ReOrderableList - * @returns {Array} */ _initializeChildren() { - let index = 0 - const childrenWithProps = React.Children.map( + let index = 0; + return React.Children.map( this.props.children, - (child) => { + child => { if (React.isValidElement(child) && child.type === ReOrderableItem) { const props = { list: this, - onItemDragStart: (item) => { - this._onItemDragStart(item) + onItemDragStart: item => { + this._onItemDragStart(item); if (child.props.onItemDragStart) { - child.props.onItemDragStart(item) + child.props.onItemDragStart(item); } }, - onItemDragEnd: (item) => { - this._onItemDragEnd(item) + onItemDragEnd: item => { + this._onItemDragEnd(item); if (child.props.onItemDragEnd) { - child.props.onItemDragEnd(item) + child.props.onItemDragEnd(item); } }, - itemData: isEmpty(child.props.itemData) - ? this.model.listData[index] - : child.props.itemData, - itemIndex: isEmpty(child.props.itemIndex) - ? index - : child.props.itemIndex - } - index++ - return React.cloneElement(child, props) + itemData: isEmpty(child.props.itemData) ? + this.model.listData[index] : child.props.itemData, + itemIndex: isEmpty(child.props.itemIndex) ? + index : child.props.itemIndex + }; + index++; + return React.cloneElement(child, props); } - return child + return child; } - ) - - return childrenWithProps + ); } /** @@ -285,8 +280,8 @@ export default class ReOrderableList extends Component { item.model, item.listComponent.model, targetIndex - ) - this.props.onListUpdate?.apply(this, params) + ); + this.props.onListUpdate?.apply(this, params); } /** @@ -297,7 +292,7 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ _initDragData(item) { - ReOrderableList._currentDraggedItem = item + ReOrderableList._currentDraggedItem = item; } /** @@ -307,9 +302,9 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ _resetDragData() { - ReOrderableList._currentDraggedItem = null - this._removePlaceholders() - this._itemPlaceholder = null + ReOrderableList._currentDraggedItem = null; + this._removePlaceholders(); + this._itemPlaceholder = null; } /** @@ -320,9 +315,9 @@ export default class ReOrderableList extends Component { */ _removePlaceholders() { const placeholder = document.querySelector( - '.ui-reorderable-list .ui-reorderable-item.item-placeholder' - ) - placeholder?.remove() + ".ui-reorderable-list .ui-reorderable-item.item-placeholder" + ); + placeholder?.remove(); } /** @@ -332,8 +327,8 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ _onItemDragStart = ({ item }) => { - this._initDragData(item) - } + this._initDragData(item); + }; /** * Item drag end event handler. @@ -342,8 +337,8 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ _onItemDragEnd = () => { - this._resetDragData() - } + this._resetDragData(); + }; /** * Drag exit event handler. @@ -352,95 +347,98 @@ export default class ReOrderableList extends Component { * @memberof ReOrderableList */ _onDragExit = () => { - this._removePlaceholders() - } + this._removePlaceholders(); + }; /** * Drag over event handler. * * @private + * @param {object} event - drag over event to handle * @memberof ReOrderableList */ - _onDragOver = (event) => { - if (!this.itemPlaceholder) return - const { item } = event.detail - event.stopPropagation() - this.itemPlaceholder.remove() + _onDragOver = event => { + if (!this.itemPlaceholder) return; + const { item } = event.detail; + event.stopPropagation(); + this.itemPlaceholder.remove(); const filteredItems = Array.prototype.slice.call( this.element.querySelectorAll( `.ui-reorderable-item.${this.instanceID}:not([hidden])` ) - ) + ); if (filteredItems.length <= 0) { - this.element.appendChild(this.itemPlaceholder) - return + this.element.appendChild(this.itemPlaceholder); + return; } - const closestElement = getClosestElement(filteredItems, item.draggedElement) + const closestElement = + getClosestElement(filteredItems, item.draggedElement); - const closestElementRect = closestElement.getBoundingClientRect() - const draggedElementRect = item.draggedElement.getBoundingClientRect() + const closestElementRect = closestElement.getBoundingClientRect(); + const draggedElementRect = item.draggedElement.getBoundingClientRect(); const draggedElementAxis = Math.floor( draggedElementRect[this.orientationSettings.axis] - ) + ); const closestRectYAxis = Math.floor( closestElementRect[this.orientationSettings.axis] + closestElementRect[this.orientationSettings.size] / 2 - ) + ); const insertFunction = - closestRectYAxis > draggedElementAxis ? insertBefore : insertAfter - insertFunction(this.element, closestElement, this.itemPlaceholder) - } + closestRectYAxis > draggedElementAxis ? insertBefore : insertAfter; + insertFunction(this.element, closestElement, this.itemPlaceholder); + }; /** * Drop event handler. * * @private + * @param {object} event - drop event to handle * @memberof ReOrderableList */ - _onDrop = (event) => { - const { item } = event.detail - event.stopPropagation() + _onDrop = event => { + const { item } = event.detail; + event.stopPropagation(); const filteredItems = Array.prototype.slice.call( - this.element.querySelectorAll(`.ui-reorderable-item:not([hidden])`) - ) + this.element.querySelectorAll(".ui-reorderable-item:not([hidden])") + ); - const index = filteredItems.findIndex((item) => - item.classList.contains('item-placeholder') - ) + const index = filteredItems.findIndex(item => + item.classList.contains("item-placeholder") + ); - this._updateList(item, index) - } + this._updateList(item, index); + }; /** * Renders the component. * - * @returns {import("react").ReactElement} + * @return {ReactElement} * @memberof ReOrderableList */ render() { - const CustomComponent = this.props.component - this._controller = new ListController(this.props) + const CustomComponent = this.props.component; + this._controller = new ListController(this.props); return ( (this._listRef = ref)} + ref={ref => (this._listRef = ref)} className={[ - 'ui-reorderable-list', + "ui-reorderable-list", this.instanceID, this.groupID, this.props.componentProps?.className - ].join(' ')} + ].join(" ")} onDrop={this._onDrop} onDragOver={this._onDragOver} onDragExit={this._onDragExit} > {this._initializeChildren()} - ) + ); } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/controllers/list-controller.js b/libs/ui-reorderable-list/src/lib/reorderable-list/controllers/list-controller.js index c0b9acfd..78c8469b 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/controllers/list-controller.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/controllers/list-controller.js @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ -import {set} from '../lib/object'; -import List from '../models/list'; +import { set } from "../lib/object"; +import List from "../models/list"; const arrayMove = (arr, from, to) => { const without = arr.filter((_x, i) => i !== from); @@ -26,7 +26,7 @@ export default class ListController { * @memberof ListController */ constructor(props) { - this._model = new List(props) + this._model = new List(props); } /** @@ -36,7 +36,7 @@ export default class ListController { * @memberof ListController */ get model() { - return this._model + return this._model; } /** @@ -45,18 +45,19 @@ export default class ListController { * @param {*} sourceItem * @param {*} targetIndex * @memberof ListController + * @return {any} */ moveItem(sourceItem, targetIndex) { - let targetList = [...this.model.listData] - const itemIndex = sourceItem.index - targetList = arrayMove(targetList, itemIndex, targetIndex) - let params = [targetList, this.model.path] + let targetList = [...this.model.listData]; + const itemIndex = sourceItem.index; + targetList = arrayMove(targetList, itemIndex, targetIndex); + let params = [targetList, this.model.path]; if (this.model.group) { - const groupCopy = [...this.model.group] - set(this.model.path, groupCopy, targetList) - params = [groupCopy] + const groupCopy = [...this.model.group]; + set(this.model.path, groupCopy, targetList); + params = [groupCopy]; } - return params + return params; } /** @@ -66,44 +67,47 @@ export default class ListController { * @param {*} [sourceList] * @param {*} targetIndex * @memberof ListController + * @return {any} */ transferItem(sourceItem, sourceList, targetIndex) { - let targetList = [...this.model.listData] - const itemData = sourceItem.data - const itemIndex = sourceItem.index + let targetList = [...this.model.listData]; + const itemData = sourceItem.data; + const itemIndex = sourceItem.index; - const sourceListPath = sourceList.path + const sourceListPath = sourceList.path; // make a copy of the group - const groupCopy = [...this.model.group] + const groupCopy = [...this.model.group]; // remove the source item from its list const sourceListData = sourceList.listData.filter( (item, index) => index !== itemIndex - ) + ); // add the item to the new list - targetList.push(itemData) + targetList.push(itemData); // move the newly added item to the target index - targetList = arrayMove(targetList, targetList.length - 1, targetIndex) + targetList = arrayMove(targetList, targetList.length - 1, targetIndex); // set the new source and target list - set(sourceListPath, groupCopy, sourceListData) - set(this.model.path, groupCopy, targetList) + set(sourceListPath, groupCopy, sourceListData); + set(this.model.path, groupCopy, targetList); // return the group - return [groupCopy] + return [groupCopy]; } /** - * Transfers an item from one list to another or reorders the items if the ```sourceList``` is null. + * Transfers an item from one list to another or + * reorders the items if the ```sourceList``` is null. * * @param {ListItem} sourceItem - * @param {Number} targetIndex * @param {List} sourceList + * @param {Number} targetIndex * @memberof ListController + * @return {any} */ updateList(sourceItem, sourceList, targetIndex) { - sourceList = !sourceList ? this.model : sourceList + sourceList = !sourceList ? this.model : sourceList; if (this.model.instanceID === sourceList.instanceID) { - return this.moveItem(sourceItem, targetIndex) + return this.moveItem(sourceItem, targetIndex); } - return this.transferItem(sourceItem, sourceList, targetIndex) + return this.transferItem(sourceItem, sourceList, targetIndex); } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/dom/index.js b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/dom/index.js index 44f1d257..2d31e412 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/dom/index.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/dom/index.js @@ -1,5 +1,5 @@ -import ReactDOMServer from 'react-dom/server' -import { distance } from '../math' +import ReactDOMServer from "react-dom/server"; +import { distance } from "../math"; /** * Inserts an element before the reference element. @@ -10,7 +10,7 @@ import { distance } from '../math' * @param {HTMLElement} newElement */ export function insertBefore(container, referenceElement, newElement) { - container.insertBefore(newElement, referenceElement) + container.insertBefore(newElement, referenceElement); } /** @@ -22,32 +22,33 @@ export function insertBefore(container, referenceElement, newElement) { * @param {HTMLElement} newElement */ export function insertAfter(container, referenceElement, newElement) { - container.insertBefore(newElement, referenceElement.nextSibling) + container.insertBefore(newElement, referenceElement.nextSibling); } /** * Retrieves the closest element * @param {Array} nodeList * @param {HTMLElement} targetElement + * @return {any} */ export function getClosestElement(nodeList, targetElement) { - let closestElement = nodeList[0] + let closestElement = nodeList[0]; let dist = distance( closestElement.getBoundingClientRect(), targetElement.getBoundingClientRect() - ) + ); - nodeList.forEach((element) => { + nodeList.forEach(element => { const currentDist = distance( element.getBoundingClientRect(), targetElement.getBoundingClientRect() - ) + ); if (currentDist < dist) { - dist = currentDist - closestElement = element + dist = currentDist; + closestElement = element; } - }) - return closestElement + }); + return closestElement; } /** @@ -56,14 +57,14 @@ export function getClosestElement(nodeList, targetElement) { * @export * @source https://stackoverflow.com/a/494348 * @param {String} htmlString - * @returns {HTMLElement} + * @return {HTMLElement} */ export function createElementFromHTML(htmlString) { - var div = document.createElement('div') - div.innerHTML = htmlString.trim() + const div = document.createElement("div"); + div.innerHTML = htmlString.trim(); // Change this to div.childNodes to support multiple top-level nodes - return div.firstElementChild + return div.firstElementChild; } /** @@ -71,8 +72,8 @@ export function createElementFromHTML(htmlString) { * @param {JSX.Element} jsx * @return {HTMLElement} */ -export function JSXToDOMElement(jsx) { - return createElementFromHTML(ReactDOMServer.renderToStaticMarkup(jsx)) +export function jsxToDOMElement(jsx) { + return createElementFromHTML(ReactDOMServer.renderToStaticMarkup(jsx)); } /** @@ -80,7 +81,7 @@ export function JSXToDOMElement(jsx) { * * @param {*} r1 * @param {*} r2 - * @returns {Boolean} + * @return {Boolean} */ function intersectRect(r1, r2) { return !( @@ -88,7 +89,7 @@ function intersectRect(r1, r2) { r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top - ) + ); } /** @@ -98,15 +99,15 @@ function intersectRect(r1, r2) { * @return {HTMLElement|null} */ export function getIntersectingElementOnList(target, list) { - const targetRect = target.getBoundingClientRect() - list = list.filter((node) => { - const nodeRect = node.getBoundingClientRect() - return intersectRect(nodeRect, targetRect) - }) + const targetRect = target.getBoundingClientRect(); + list = list.filter(node => { + const nodeRect = node.getBoundingClientRect(); + return intersectRect(nodeRect, targetRect); + }); - if (list.length <= 0) return null + if (list.length <= 0) return null; - return list[0] + return list[0]; } /** @@ -114,33 +115,34 @@ export function getIntersectingElementOnList(target, list) { * * @source https://www.labnol.org/code/19616-detect-touch-screen-javascript * @export - * @returns {Boolean} + * @return {Boolean} */ export function isTouchDevice() { return ( - 'ontouchstart' in window || + "ontouchstart" in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 - ) + ); } /** - * Appends a class or an array of classes to the element if it doesn't exist. + * Appends a class or an array of classes to the element if it doesn"t exist. * * @export * @param {HTMLElement} element The element to append the class - * @param {String|Array} className The class name or a list of class names to append. + * @param {String|Array} className The class name or a + * list of class names to append. */ export function appendClassIfNotExists(element, className) { if (Array.isArray(className)) { - className.forEach((val) => { + className.forEach(val => { if (!element.classList.contains(val)) { - element.classList.add(val) + element.classList.add(val); } - }) + }); } else { if (!element.classList.contains(className)) { - element.classList.add(className) + element.classList.add(className); } } } @@ -150,18 +152,19 @@ export function appendClassIfNotExists(element, className) { * * @export * @param {HTMLElement} element The element to remove classes - * @param {String|Array} className The class name or a list of class names to remove. + * @param {String|Array} className The class name or + * a list of class names to remove. */ export function removeClassIfExists(element, className) { if (Array.isArray(className)) { - className.forEach((val) => { + className.forEach(val => { if (element.classList.contains(val)) { - element.classList.remove(val) + element.classList.remove(val); } - }) + }); } else { if (element.classList.contains(className)) { - element.classList.remove(className) + element.classList.remove(className); } } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/math/index.js b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/math/index.js index e08eca95..730c0850 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/math/index.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/math/index.js @@ -4,10 +4,10 @@ * @export * @param {Object} pointA * @param {Object} pointB - * @returns {Number} + * @return {Number} */ export function distance(pointA, pointB) { - const xDiff = pointA.x - pointB.x - const yDiff = pointA.y - pointB.y - return Math.sqrt(xDiff * xDiff + yDiff * yDiff) -} \ No newline at end of file + const xDiff = pointA.x - pointB.x; + const yDiff = pointA.y - pointB.y; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/object/index.js b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/object/index.js index ea46d28d..c296bf2d 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/lib/object/index.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/lib/object/index.js @@ -4,17 +4,17 @@ * @export * @param {String} path Path to the property you want to access. * @param {Object} object The source object. - * @returns {*} + * @return {*} */ export function get(path, object) { - path = path + '' - const tokens = path.split('.') - let currentProperty = object + path = path + ""; + const tokens = path.split("."); + let currentProperty = object; - for (let i = 0; i < tokens.length; i++) { - currentProperty = currentProperty[tokens[i]] + for (const token of tokens) { + currentProperty = currentProperty[token]; } - return currentProperty + return currentProperty; } /** @@ -25,10 +25,11 @@ export function get(path, object) { * @param {String} path Path to the property you want to access. * @param {Object} object The source object. * @param {*} newValue + * @return {Object} */ export function set(path, object, newValue) { - path = path + '' - path = path.split('.') - while (path.length > 1) object = object[path.shift()] - return (object[path.shift()] = newValue) -} \ No newline at end of file + path = path + ""; + path = path.split("."); + while (path.length > 1) object = object[path.shift()]; + return (object[path.shift()] = newValue); +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/models/list-item.js b/libs/ui-reorderable-list/src/lib/reorderable-list/models/list-item.js index c0c747fb..5678e823 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/models/list-item.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/models/list-item.js @@ -1,4 +1,4 @@ -import {nanoid} from 'nanoid'; +import { nanoid } from "nanoid"; /** * A model representing our list item. @@ -17,20 +17,17 @@ export default class ListItem { constructor(props) { this._itemIndex = props.itemIndex || 0; this._itemData = props.itemData || null; - // @todo this._isEnabled = props.enabled || true; this._removeByDragging = props.removeByDragging || false; this._instanceID = - 'item-instance-' + - (props._instanceID - ? props._instanceID - : nanoid(8)); + "item-instance-" + + (props._instanceID ? props._instanceID : nanoid(8)); } /** * Returns the instance id for this item. * - * @type {String} + * @return {String} * @readonly * @memberof ListItem */ @@ -41,7 +38,7 @@ export default class ListItem { /** * Returns the data for this item. * - * @type {Object} + * @return {Object} * @readonly * @memberof ListItem */ @@ -52,6 +49,7 @@ export default class ListItem { /** * Returns a flag which indicates if an item is enabled/disabled. * + * @return {boolean} * @memberof ListItem */ get enabled() { @@ -61,7 +59,7 @@ export default class ListItem { /** * Returns the index for this item on the list. * - * @type {Number} + * @return {Number} * @readonly * @memberof ListItem */ @@ -72,7 +70,7 @@ export default class ListItem { /** * Sets the data for this item. * - * @type {Object} + * @param {Object} value * @memberof ListItem */ set data(value) { @@ -82,7 +80,7 @@ export default class ListItem { /** * Sets the index for this item on the list. * - * @type {Number} + * @param {Number} value * @memberof ListItem */ set index(value) { @@ -92,9 +90,10 @@ export default class ListItem { /** * Enables/disables the item. * + * @param {boolean} value * @memberof ListItem */ set enabled(value) { this._isEnabled = value; } -} \ No newline at end of file +} diff --git a/libs/ui-reorderable-list/src/lib/reorderable-list/models/list.js b/libs/ui-reorderable-list/src/lib/reorderable-list/models/list.js index 90585745..3c53039c 100644 --- a/libs/ui-reorderable-list/src/lib/reorderable-list/models/list.js +++ b/libs/ui-reorderable-list/src/lib/reorderable-list/models/list.js @@ -1,6 +1,6 @@ -import {nanoid} from 'nanoid'; -import {get, set} from '../lib/object'; -import ListItem from './list-item'; +import { nanoid } from "nanoid"; +import { get, set } from "../lib/object"; +import ListItem from "./list-item"; /** * A model representing our list. @@ -11,27 +11,30 @@ import ListItem from './list-item'; * @version 1.1.0 */ export default class List { + /** + * List constructor + * @param {object} props + */ constructor(props) { - this._name = props.name || ''; + this._name = props.name || ""; this._list = props.list || []; this._path = props.path || 0; this._group = props.group || null; this._listItems = null; - this._groupID = 'list-group-' + props.name.replace(/ /g, '-'); + this._groupID = "list-group-" + props.name.replace(/ /g, "-"); this._instanceID = - 'list-instance-' + - (props._instanceID - ? props._instanceID - : nanoid(8)); + "list-instance-" + + (props._instanceID ? props._instanceID : nanoid(8)); } /** - * Returns the list group that this list is part of. For lists without a group, this will return ```null```. + * Returns the list group that this list is part of. + * For lists without a group, this will return ```null```. * * @public * @readonly * @memberof List - * @type {null|Array} + * @return {null|Array} */ get group() { return this._group; @@ -43,7 +46,7 @@ export default class List { * @public * @readonly * @memberof List - * @type {String} + * @return {String} */ get groupID() { return this._groupID; @@ -55,7 +58,7 @@ export default class List { * @public * @readonly * @memberof List - * @type {String} + * @return {String} */ get instanceID() { return this._instanceID; @@ -67,7 +70,7 @@ export default class List { * @public * @readonly * @memberof List - * @type {Array} + * @return {Array} */ get listData() { return this._group ? get(this._path, this._group) : this._list; @@ -78,6 +81,7 @@ export default class List { * * @readonly * @memberof List + * @return {Array} */ get listItems() { if (!this._listItems) { @@ -93,12 +97,13 @@ export default class List { } /** - * Returns the object path of this list from the list group. Defaults to ```0``` when this list is ungrouped. + * Returns the object path of this list from the list group. + * Defaults to ```0``` when this list is ungrouped. * * @public * @readonly * @memberof List - * @type {Number} + * @return {Number} */ get path() { return this._path; @@ -109,7 +114,7 @@ export default class List { * * @public * @memberof List - * @type {null|Array} + * @param {null|Array} value */ set group(value) { this._group = value; @@ -120,8 +125,8 @@ export default class List { * * @public * @readonly + * @param {Array} value * @memberof List - * @type {Array} */ set listData(value) { this._group ? set(this._path, this._group, value) : (this._list = value); @@ -139,9 +144,9 @@ export default class List { * * @public * @memberof List - * @type {Number} + * @param {number} value */ set path(value) { this._path = value; } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 5ae60aba..b924beed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "electron-updater": "^5.3.0", "express": "^4.18.1", "github-markdown-css": "^5.2.0", + "is-empty": "^1.2.0", "jsonwebtoken": "^9.0.0", "lucide-react": "^0.287.0", "minio": "^7.1.3", @@ -46,6 +47,7 @@ "next-themes": "^0.2.1", "node-fetch": "^2.6.9", "node-schedule": "^2.1.1", + "prop-types": "^15.8.1", "react": "18.2.0", "react-ace": "^10.1.0", "react-big-calendar": "^1.8.1", @@ -124,8 +126,8 @@ "@types/swagger-ui-express": "^4.1.3", "@types/ws": "^8.5.5", "@types/yamljs": "^0.2.31", - "@typescript-eslint/eslint-plugin": "^5.36.1", - "@typescript-eslint/parser": "^5.36.1", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "@vitejs/plugin-react": "^3.0.0", "@vitest/coverage-c8": "~0.25.8", "@vitest/ui": "^0.25.8", @@ -2745,6 +2747,30 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -9784,9 +9810,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -10013,9 +10039,9 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" }, "node_modules/@types/serve-index": { "version": "1.9.1", @@ -10143,31 +10169,33 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", - "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/type-utils": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -10175,10 +10203,179 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10191,74 +10388,418 @@ } }, "node_modules/@typescript-eslint/parser": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/scope-manager": { "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", - "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", + "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/typescript-estree": "5.44.0", - "debug": "^4.3.4" + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", - "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/visitor-keys": "5.44.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", - "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.44.0", - "@typescript-eslint/utils": "5.44.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "6.18.1", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, - "peerDependencies": { - "eslint": "*" + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@typescript-eslint/types": { @@ -17133,6 +17674,167 @@ } } }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-config-next/node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/eslint-config-next/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-config-next/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-config-prettier": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", @@ -17577,12 +18279,15 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/argparse": { @@ -17720,9 +18425,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -19311,6 +20016,12 @@ "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/gud": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", @@ -19848,9 +20559,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -20290,6 +21001,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-empty": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", + "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==" + }, "node_modules/is-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", @@ -23533,12 +24249,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", @@ -31362,6 +32072,18 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", diff --git a/package.json b/package.json index 067a2d29..c66c5aff 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,8 @@ "@types/swagger-ui-express": "^4.1.3", "@types/ws": "^8.5.5", "@types/yamljs": "^0.2.31", - "@typescript-eslint/eslint-plugin": "^5.36.1", - "@typescript-eslint/parser": "^5.36.1", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", "@vitejs/plugin-react": "^3.0.0", "@vitest/coverage-c8": "~0.25.8", "@vitest/ui": "^0.25.8", @@ -162,6 +162,7 @@ "electron-updater": "^5.3.0", "express": "^4.18.1", "github-markdown-css": "^5.2.0", + "is-empty": "^1.2.0", "jsonwebtoken": "^9.0.0", "lucide-react": "^0.287.0", "minio": "^7.1.3", @@ -170,6 +171,7 @@ "next-themes": "^0.2.1", "node-fetch": "^2.6.9", "node-schedule": "^2.1.1", + "prop-types": "^15.8.1", "react": "18.2.0", "react-ace": "^10.1.0", "react-big-calendar": "^1.8.1",