diff --git a/website/yarn.lock b/website/yarn.lock index 895abec..479853f 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -1804,9 +1804,9 @@ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== "@tsconfig/docusaurus@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.4.tgz#fc40f87a672568678d83533dd4031a09d75877ca" - integrity sha512-I6sziQAzLrrqj9r6S26c7aOAjfGVXIE7gWdNONPwnpDcHiMRMQut1s1YCi/APem3dOy23tAb2rvHfNtGCaWuUQ== + version "1.0.5" + resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.5.tgz#5298c5b0333c6263f06c3149b38ebccc9f169a4e" + integrity sha512-KM/TuJa9fugo67dTGx+ktIqf3fVc077J6jwHu845Hex4EQf7LABlNonP/mohDKT0cmncdtlYVHHF74xR/YpThg== "@types/body-parser@*": version "1.19.2" @@ -7254,9 +7254,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" typescript@^4.5.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" - integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== ua-parser-js@^0.7.30: version "0.7.31" diff --git a/webui/package.json b/webui/package.json index 2244e3f..5c41676 100644 --- a/webui/package.json +++ b/webui/package.json @@ -15,6 +15,7 @@ "@types/react-helmet": "^6.1.5", "@types/react-redux": "^7.1.22", "@types/react-relay": "^13.0.1", + "@types/react-table": "^7.7.10", "@types/relay-runtime": "^13.0.2", "axios": "^0.26.0", "hamburger-react": "^2.4.1", @@ -31,6 +32,7 @@ "react-relay": "^13.2.0", "react-router-dom": "6", "react-scripts": "5.0.0", + "react-table": "^7.7.0", "sass": "^1.49.9", "typescript": "~4.1.5" }, diff --git a/webui/src/App.tsx b/webui/src/App.tsx index 1ee113e..ebf309a 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -14,6 +14,8 @@ import { } from 'react-relay/hooks'; import Dashboard from "./components/panel/dashboard/Dashboard"; import DashboardQueryGraphql, {DashboardQuery} from "./components/panel/dashboard/__generated__/DashboardQuery.graphql"; +import Rooms from "./components/panel/rooms/Rooms"; +import RoomsQueryGraphql, {RoomsQuery} from "./components/panel/rooms/__generated__/RoomsQuery.graphql"; function App() { const dispatch = useAppDispatch() @@ -25,6 +27,10 @@ function App() { DashboardQueryGraphql ) + const [roomsInitialState, loadRoomsQuery, disposeRoomsQuery] = useQueryLoader( + RoomsQueryGraphql + ) + // This needs to be here to prevent a weird bug useTranslation() @@ -37,10 +43,12 @@ function App() { useEffect(() => { if(auth.jwt !== null) { loadQuery({}) + loadRoomsQuery({}) return } disposeQuery() + disposeRoomsQuery() environment.getStore().notify(undefined, true) }, [auth]) @@ -53,7 +61,7 @@ function App() { }> {dashboardInitialState && }} /> -

rooms

}> + {roomsInitialState && }}> room detail} />

lists

}> diff --git a/webui/src/_globals.scss b/webui/src/_globals.scss index e69de29..3536ad6 100644 --- a/webui/src/_globals.scss +++ b/webui/src/_globals.scss @@ -0,0 +1,21 @@ +@mixin badges { + .badge { + padding: 2px var(--veles-layout-padding-slim); + background-color: var(--veles-color-surface); + border-radius: var(--veles-layout-border-radius); + margin-left: 10px; + border: thin solid var(--veles-color-border); + + &.red { + border-color: var(--veles-color-red); + } + + &.blue { + border-color: var(--veles-color-blue); + } + + &.green { + border-color: var(--veles-color-green); + } + } +} \ No newline at end of file diff --git a/webui/src/components/panel/dashboard/DashMyRooms.module.scss b/webui/src/components/panel/dashboard/DashMyRooms.module.scss index dde51e5..7abac5e 100644 --- a/webui/src/components/panel/dashboard/DashMyRooms.module.scss +++ b/webui/src/components/panel/dashboard/DashMyRooms.module.scss @@ -31,25 +31,7 @@ font-size: 1.2em; } - .badge { - padding: 2px var(--veles-layout-padding-slim); - background-color: var(--veles-color-surface); - border-radius: var(--veles-layout-border-radius); - margin-left: 10px; - border: thin solid var(--veles-color-border); - - &.red { - border-color: var(--veles-color-red); - } - - &.blue { - border-color: var(--veles-color-blue); - } - - &.green { - border-color: var(--veles-color-green); - } - } + @include badges; } .id { diff --git a/webui/src/components/panel/dashboard/DashMyRooms.tsx b/webui/src/components/panel/dashboard/DashMyRooms.tsx index 09c859c..046e4f0 100644 --- a/webui/src/components/panel/dashboard/DashMyRooms.tsx +++ b/webui/src/components/panel/dashboard/DashMyRooms.tsx @@ -47,9 +47,9 @@ const DashMyRooms = (props: Props) => { return
{edge.node.name} - {edge.node.debug && Debug} - {!edge.node.active && Inactive} - {edge.node.active && Active} + {edge.node.debug && Debug} + {!edge.node.active && Inactive} + {edge.node.active && Active}
{edge.node.roomId} diff --git a/webui/src/components/panel/rooms/Rooms.module.scss b/webui/src/components/panel/rooms/Rooms.module.scss new file mode 100644 index 0000000..6b7da56 --- /dev/null +++ b/webui/src/components/panel/rooms/Rooms.module.scss @@ -0,0 +1,77 @@ +@import "../../../globals"; + +$slideOverBreakpoint: 1000px; + +.roomsContainer { + display: flex; + height: calc(100% + 2*var(--veles-layout-padding)); + margin: var(--veles-layout-padding-inverse); + width: calc(100% + 2*var(--veles-layout-padding)); + overflow: hidden; + + .roomsOverview { + flex-grow: 1; + flex-shrink: 1; + width: 100px; + padding: var(--veles-layout-padding); + transition: margin-right .25s; + + &.leaveSpace { + margin-right: 400px; + + @media(max-width: $slideOverBreakpoint) { + margin-right: 0; + } + } + } + + .slideOver { + position: absolute; + top: 0; + right: -400px; + height: 100%; + width: 400px; + border-left: thin solid var(--veles-color-border); + transition: right .25s, border-left .25s, width .25s; + + @media(max-width: $slideOverBreakpoint) { + width: 100%; + border-left: 0 solid var(--veles-color-border); + margin-right: 0; + right: -100%; + } + + background-color: var(--veles-color-background); + + &.active { + right: 0; + } + + .slideOverContent { + padding: var(--veles-layout-padding); + } + + .slideOverHeader { + display: flex; + border-bottom: thin solid var(--veles-color-border); + align-items: center; + + >* { + padding: var(--veles-layout-padding-slim) var(--veles-layout-padding); + } + + > span { + flex-grow: 1; + } + + > button { + margin: 0; + background: transparent; + font: inherit; + color: inherit; + border: none; + cursor: pointer; + } + } + } +} \ No newline at end of file diff --git a/webui/src/components/panel/rooms/Rooms.tsx b/webui/src/components/panel/rooms/Rooms.tsx new file mode 100644 index 0000000..1b21726 --- /dev/null +++ b/webui/src/components/panel/rooms/Rooms.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import {useNavigate, useOutlet} from "react-router-dom"; + +import styles from "./Rooms.module.scss"; +import {Trans} from "react-i18next"; +import RoomsTable from "./RoomsTable"; +import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks"; +import {graphql} from "babel-plugin-relay/macro"; +import {RoomsQuery} from "./__generated__/RoomsQuery.graphql"; +import {X} from "lucide-react"; + +type Props = { + initialQueryRef: PreloadedQuery, +} + +const Rooms = ({initialQueryRef}: Props) => { + const outlet = useOutlet() + const navigate = useNavigate() + + const data = usePreloadedQuery( + graphql` + query RoomsQuery($first: String, $count: Int) { + ...RoomsTableFragment + } + `, + initialQueryRef + ) + + + return
+
+

My Rooms

+ + +
+ +
+
+ Details + +
+
+ {outlet} +
+
+
+} + +export default Rooms \ No newline at end of file diff --git a/webui/src/components/panel/rooms/RoomsTable.module.scss b/webui/src/components/panel/rooms/RoomsTable.module.scss new file mode 100644 index 0000000..1458b6b --- /dev/null +++ b/webui/src/components/panel/rooms/RoomsTable.module.scss @@ -0,0 +1,35 @@ +@import "../../../globals"; + +.roomsTableWrapper { + width: 100%; + overflow-y: scroll; + + .roomsTable { + width: 100%; + + text-align: left; + + white-space: nowrap; + border-spacing: 0; + border-collapse: collapse; + + th, td { + padding: 10px 5px; + } + + thead tr { + border-bottom: thin solid var(--veles-color-border-highlight); + } + + tbody tr { + cursor:pointer; + border-bottom: thin solid var(--veles-color-border); + + @include badges; + + &:hover { + background: var(--veles-color-surface); + } + } + } +} diff --git a/webui/src/components/panel/rooms/RoomsTable.tsx b/webui/src/components/panel/rooms/RoomsTable.tsx new file mode 100644 index 0000000..17d3028 --- /dev/null +++ b/webui/src/components/panel/rooms/RoomsTable.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import {usePaginationFragment} from "react-relay/hooks"; +import {graphql} from "babel-plugin-relay/macro"; +import {RoomsTableFragment$key} from "./__generated__/RoomsTableFragment.graphql"; +import {useTable} from "react-table"; +import styles from "./RoomsTable.module.scss"; +import {useNavigate} from "react-router-dom"; +import {Trans} from "react-i18next"; + +type Props = { + initialQueryRef: RoomsTableFragment$key, +} + +const RoomsTable = ({initialQueryRef}: Props) => { + const {data, refetch, loadNext, hasNext, isLoadingNext} = usePaginationFragment(graphql` + fragment RoomsTableFragment on Query @refetchable(queryName: "RoomsTableFragment") { + rooms(after: $first, first: $count, filter: {canEdit: true}) @connection(key: "RoomsTableFragment_rooms") { + edges { + node { + id + name + active + debug + roomId + } + } + } + } + `, initialQueryRef) + + const navigate = useNavigate() + + return
+ + + + + + + + + + { + data.rooms?.edges.map((edge) => { + return {navigate("/rooms/"+edge.node.id)}}> + + + + ; + }) + } + + +
NameRoom ID
+ {edge.node.debug && Debug} + {!edge.node.active && Inactive} + {edge.node.active && Active} + {edge.node.name}{edge.node.roomId}
+
+} + +export default RoomsTable \ No newline at end of file diff --git a/webui/src/components/panel/rooms/__generated__/RoomsQuery.graphql.ts b/webui/src/components/panel/rooms/__generated__/RoomsQuery.graphql.ts new file mode 100644 index 0000000..299281d --- /dev/null +++ b/webui/src/components/panel/rooms/__generated__/RoomsQuery.graphql.ts @@ -0,0 +1,216 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type RoomsQuery$variables = { + first?: string | null; + count?: number | null; +}; +export type RoomsQuery$data = { + readonly " $fragmentSpreads": FragmentRefs<"RoomsTableFragment">; +}; +export type RoomsQuery = { + variables: RoomsQuery$variables; + response: RoomsQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "count" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "first" +}, +v2 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "first" + }, + { + "kind": "Literal", + "name": "filter", + "value": { + "canEdit": true + } + }, + { + "kind": "Variable", + "name": "first", + "variableName": "count" + } +]; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "RoomsQuery", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RoomsTableFragment" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "RoomsQuery", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "RoomConnection", + "kind": "LinkedField", + "name": "rooms", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "RoomEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Room", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "active", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "debug", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "roomId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v2/*: any*/), + "filters": [ + "filter" + ], + "handle": "connection", + "key": "RoomsTableFragment_rooms", + "kind": "LinkedHandle", + "name": "rooms" + } + ] + }, + "params": { + "cacheID": "c933de605f2929607b671c06737a6f53", + "id": null, + "metadata": {}, + "name": "RoomsQuery", + "operationKind": "query", + "text": "query RoomsQuery(\n $first: String\n $count: Int\n) {\n ...RoomsTableFragment\n}\n\nfragment RoomsTableFragment on Query {\n rooms(after: $first, first: $count, filter: {canEdit: true}) {\n edges {\n node {\n id\n name\n active\n debug\n roomId\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "c86e1dca69190955b66fc4f94477a8fc"; + +export default node; diff --git a/webui/src/components/panel/rooms/__generated__/RoomsTableFragment.graphql.ts b/webui/src/components/panel/rooms/__generated__/RoomsTableFragment.graphql.ts new file mode 100644 index 0000000..f4e65a5 --- /dev/null +++ b/webui/src/components/panel/rooms/__generated__/RoomsTableFragment.graphql.ts @@ -0,0 +1,195 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment, RefetchableFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type RoomsTableFragment$data = { + readonly rooms: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly active: boolean; + readonly debug: boolean; + readonly roomId: string; + }; + }>; + } | null; + readonly " $fragmentType": "RoomsTableFragment"; +}; +export type RoomsTableFragment$key = { + readonly " $data"?: RoomsTableFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"RoomsTableFragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = [ + "rooms" +]; +return { + "argumentDefinitions": [ + { + "kind": "RootArgument", + "name": "count" + }, + { + "kind": "RootArgument", + "name": "first" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": "count", + "cursor": "first", + "direction": "forward", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "count", + "cursor": "first" + }, + "backward": null, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [], + "operation": require('./RoomsTableFragment.graphql') + } + }, + "name": "RoomsTableFragment", + "selections": [ + { + "alias": "rooms", + "args": [ + { + "kind": "Literal", + "name": "filter", + "value": { + "canEdit": true + } + } + ], + "concreteType": "RoomConnection", + "kind": "LinkedField", + "name": "__RoomsTableFragment_rooms_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "RoomEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Room", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "active", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "debug", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "roomId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "__RoomsTableFragment_rooms_connection(filter:{\"canEdit\":true})" + } + ], + "type": "Query", + "abstractKey": null +}; +})(); + +(node as any).hash = "6e31abc6b1af82d05defa80dd7242e44"; + +export default node; diff --git a/webui/src/index.scss b/webui/src/index.scss index 9ad603e..4dcf883 100644 --- a/webui/src/index.scss +++ b/webui/src/index.scss @@ -22,6 +22,7 @@ html, body, #root { --veles-color-background: #0d0d0d; --veles-color-foreground: #fff; --veles-color-border: #1c1c1c; + --veles-color-border-highlight: #464646; --veles-color-surface: #ffffff08; --veles-color-accent: #007300; --veles-color-error: #e3373e; @@ -42,6 +43,7 @@ html, body, #root { --veles-color-background: #f2f2f2; --veles-color-foreground: #0d0d0d; --veles-color-border: #cccccc; + --veles-color-border-highlight: #9b9b9b; --veles-color-surface: #00000008; --veles-color-accent: #007300; } diff --git a/webui/src/layouts/PanelLayout.module.scss b/webui/src/layouts/PanelLayout.module.scss index f9d453c..c6f72a9 100644 --- a/webui/src/layouts/PanelLayout.module.scss +++ b/webui/src/layouts/PanelLayout.module.scss @@ -152,6 +152,7 @@ $navBreakpoint: 650px; >* { flex-shrink: 0; + flex-basis: 0; } .dropdown { @@ -200,8 +201,10 @@ $navBreakpoint: 650px; > main { padding: var(--veles-layout-padding); height: calc(100vh - 66px); - overflow: auto; + overflow-y: auto; + overflow-x: hidden; flex-grow: 1; + position: relative; } } } \ No newline at end of file diff --git a/webui/yarn.lock b/webui/yarn.lock index 9bf437a..7fabe52 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -1973,6 +1973,13 @@ "@types/react" "*" "@types/relay-runtime" "*" +"@types/react-table@^7.7.10": + version "7.7.10" + resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.10.tgz#ca8bb5420bfeae964ff61682f31f1cadfcfee726" + integrity sha512-yt7FHv/2cFsucStSWLBOB3OmsRZF08DvVHzz8Zg41B4tzRL6pQ+5VYvmhaR1dKS//tDG4UOJ1RQJPEINHYoRtg== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.43" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" @@ -8051,6 +8058,11 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== +react-table@^7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912" + integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA== + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"