From 03e6ec2b1a6a7530da9fce179965cdf6b430b6cc Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Sat, 16 Mar 2024 18:00:49 +0000 Subject: [PATCH 1/8] Progress on moving things into router --- package-lock.json | 172 +++++++++++++++++++++++++--- package.json | 6 +- src/App.test.tsx | 2 +- src/App.tsx | 87 +++++--------- src/AppOld.tsx | 115 +++++++++++++++++++ src/feature/home/HomePage.tsx | 71 +++++++----- src/feature/session/SessionPage.tsx | 6 +- src/index.tsx | 2 +- 8 files changed, 355 insertions(+), 106 deletions(-) create mode 100644 src/AppOld.tsx diff --git a/package-lock.json b/package-lock.json index b7abb13..f71865e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "0.1.0", "dependencies": { "@ionic/react": "^7.7.2", + "@ionic/react-router": "7.8.0", "@yudiel/react-qr-scanner": "^1.2.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-qr-code": "^2.0.12", "react-responsive-carousel": "^3.2.23", - "typescript": "^4" + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", + "typescript": "^4.9.5" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", @@ -23,6 +26,7 @@ "@types/react": "^18.2.56", "@types/react-dom": "^18.2.19", "@types/react-qr-reader": "^2.1.7", + "@types/react-router-dom": "^5.3.3", "eslint-utils": "^3.0.0", "react-scripts": "^5.0.1" } @@ -2238,7 +2242,6 @@ "version": "7.23.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2734,9 +2737,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.7.2.tgz", - "integrity": "sha512-cH92OSqJBTaW8AAqh+M6NjzltVoAZCXqsHAOQMmZgrY4KgXNU+Wh+fs2La/UrFxTob9pZf30EpRddUG5rQYIFw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.8.0.tgz", + "integrity": "sha512-rogQw6lWH367E5XQnovbAIB4pT1YmuTz7OvyQm0cp4pO2/64faKyTGteSxc99stG01CoARW+pjJN1K09hfKFPw==", "dependencies": { "@stencil/core": "^4.12.2", "ionicons": "^7.2.2", @@ -2744,11 +2747,11 @@ } }, "node_modules/@ionic/react": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.7.2.tgz", - "integrity": "sha512-oUR49V7XpfZf3kLu7lAZLFiK5B5HMiL5UigWYalGqSCxrK4Jj7Tt4OZEUoAoDnbdDx67S490w/PcIUIw8eE45Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.8.0.tgz", + "integrity": "sha512-OpP3WWnbD0cdP98afJk/Z6DmXk4t1ZBOLzZvBaNlRUMbnkTZOjt/PufCK+qvaPVGc2OKfKhv+XRckbx9uX5Few==", "dependencies": { - "@ionic/core": "7.7.2", + "@ionic/core": "7.8.0", "ionicons": "^7.0.0", "tslib": "*" }, @@ -2757,6 +2760,21 @@ "react-dom": ">=16.8.6" } }, + "node_modules/@ionic/react-router": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-7.8.0.tgz", + "integrity": "sha512-k0KdikXwZAxJF4PZXNlVFPCQqBGj35Tz20E/g/KsML6G3I3pB1HIhyioQfRecZrDWRxCYUh3RPAObY+vWxpC1w==", + "dependencies": { + "@ionic/react": "7.8.0", + "tslib": "*" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6", + "react-router": "^5.0.1", + "react-router-dom": "^5.0.1" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3743,9 +3761,9 @@ } }, "node_modules/@stencil/core": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.2.tgz", - "integrity": "sha512-WEMpoqwMV4hY/ab2z9NxRhSeZwuKEugjyn6Vd+qA9xqZh6VNUL27QbP8vCa7IeqD4Zql4JBtKu3lVuBHutWE6w==", + "version": "4.12.6", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.12.6.tgz", + "integrity": "sha512-15JO2TdaxGVKNdLZb/2TtDa+juj3XGD/V0y/disgdzYYSnajgSh06nwODfdHz9eTUh1Hisz+KIo857I1rCZrfg==", "bin": { "stencil": "bin/stencil" }, @@ -4143,6 +4161,12 @@ "@types/node": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4367,6 +4391,27 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -9416,6 +9461,32 @@ "he": "bin/he" } }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -15334,6 +15405,60 @@ "react-easy-swipe": "^0.0.21" } }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/react-router/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/react-router/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15496,8 +15621,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -15663,6 +15787,11 @@ "node": ">=4" } }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "node_modules/resolve-url-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", @@ -17198,6 +17327,16 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -17703,6 +17842,11 @@ "node": ">=10.12.0" } }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 15c5f58..c82b0f0 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "react-dom": "^18.2.0", "react-qr-code": "^2.0.12", "react-responsive-carousel": "^3.2.23", - "typescript": "^4" + "typescript": "^4.9.5", + "@ionic/react-router": "7.8.0", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4" }, "devDependencies": { "@types/jest": "^29.5.12", @@ -17,6 +20,7 @@ "@types/react": "^18.2.56", "@types/react-dom": "^18.2.19", "@types/react-qr-reader": "^2.1.7", + "@types/react-router-dom": "^5.3.3", "eslint-utils": "^3.0.0", "react-scripts": "^5.0.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11" diff --git a/src/App.test.tsx b/src/App.test.tsx index a754b20..2eb40d1 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import { App } from './App'; it('renders without crashing', () => { const div = document.createElement('div'); diff --git a/src/App.tsx b/src/App.tsx index cbe7ff8..907bfc5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,42 +1,22 @@ -import React from 'react'; +import React, { useState } from 'react'; import './App.css'; -import { ServerTypes } from './ServerTypes'; -import { Header } from './feature/header/Header'; -import { SessionPage } from './feature/session/SessionPage'; +import { + IonApp, + IonRouterOutlet, +} from '@ionic/react'; +import { IonReactRouter } from '@ionic/react-router'; +import { Redirect, Route } from 'react-router'; import { HomePage } from './feature/home/HomePage'; import { WSClient } from './WSClient'; -import { IonApp } from '@ionic/react'; -interface AppState { - result: string; - goToURL: string; - clientMap: {[key: string]: ServerTypes.Client}; - scanModalOpen: boolean; - ourClientId: string | null; - sessionId: string | null; - sessionOwnerId: string | null; -} +export const App: React.FC = () => { -class App extends React.Component<{}, AppState> { - wsClient: WSClient; + let wsUrl = "wss://qrsync.org/api/v1/ws"; + const [wsClient] = useState(new WSClient(wsUrl)); - constructor(props: {}) { - super(props); - this.state = { - result: "", - goToURL: "", - clientMap: {}, - scanModalOpen: false, - ourClientId: null, - sessionId: null, - sessionOwnerId: null - }; - let wsUrl = "wss://qrsync.org/api/v1/ws"; - this.wsClient = new WSClient(wsUrl); - this.wsClient.addMessageHandler("main", this.onReceiveWebsocketMsg); - } + wsClient.addMessageHandler("main", onReceiveWebsocketMsg); - onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { + const onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { if (msg.type) { switch (msg.type) { case "ClientConnect": return this.onClientConnectMsg(msg); @@ -48,7 +28,7 @@ class App extends React.Component<{}, AppState> { } } - onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { + const onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { if (msg.clientId === this.state.ourClientId) { this.setState({ sessionId: msg.sessionId, @@ -58,11 +38,11 @@ class App extends React.Component<{}, AppState> { } } - onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { + const onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { this.setState({ ourClientId: msg.client.id }); } - onScanClient = (clientId: string | null) => { + const onScanClient = (clientId: string | null) => { this.setState({result: "" + clientId}); if (clientId) { console.log("Client id scanned ", clientId); @@ -81,7 +61,7 @@ class App extends React.Component<{}, AppState> { } } - onBroadcastFromSessionMsg = (msg: ServerTypes.BroadcastFromSessionMsg) => { + const onBroadcastFromSessionMsg = (msg: ServerTypes.BroadcastFromSessionMsg) => { } @@ -90,26 +70,17 @@ class App extends React.Component<{}, AppState> { this.setState({ sessionId: null }); } - render() { - return
- -
- {this.state.sessionId ? - : + return ( + + + - } - -
- } -} - -export default App; \ No newline at end of file + ourClientId={wsClient.getId()} + onScanClient={onScanClient} + > + + + + + ); +}; \ No newline at end of file diff --git a/src/AppOld.tsx b/src/AppOld.tsx new file mode 100644 index 0000000..cbe7ff8 --- /dev/null +++ b/src/AppOld.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import './App.css'; +import { ServerTypes } from './ServerTypes'; +import { Header } from './feature/header/Header'; +import { SessionPage } from './feature/session/SessionPage'; +import { HomePage } from './feature/home/HomePage'; +import { WSClient } from './WSClient'; +import { IonApp } from '@ionic/react'; + +interface AppState { + result: string; + goToURL: string; + clientMap: {[key: string]: ServerTypes.Client}; + scanModalOpen: boolean; + ourClientId: string | null; + sessionId: string | null; + sessionOwnerId: string | null; +} + +class App extends React.Component<{}, AppState> { + wsClient: WSClient; + + constructor(props: {}) { + super(props); + this.state = { + result: "", + goToURL: "", + clientMap: {}, + scanModalOpen: false, + ourClientId: null, + sessionId: null, + sessionOwnerId: null + }; + let wsUrl = "wss://qrsync.org/api/v1/ws"; + this.wsClient = new WSClient(wsUrl); + this.wsClient.addMessageHandler("main", this.onReceiveWebsocketMsg); + } + + onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { + if (msg.type) { + switch (msg.type) { + case "ClientConnect": return this.onClientConnectMsg(msg); + case "ClientJoinedSession": return this.onClientJoinedSessionMsg(msg); + case "BroadcastFromSession": return this.onBroadcastFromSessionMsg(msg); + } + } else { + console.warn("No message type ", msg); + } + } + + onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { + if (msg.clientId === this.state.ourClientId) { + this.setState({ + sessionId: msg.sessionId, + sessionOwnerId: msg.sessionOwnerId, + clientMap: msg.clientMap + }); + } + } + + onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { + this.setState({ ourClientId: msg.client.id }); + } + + onScanClient = (clientId: string | null) => { + this.setState({result: "" + clientId}); + if (clientId) { + console.log("Client id scanned ", clientId); + if (!this.state.sessionId) { + this.wsClient.sendMessage({ + type: "CreateSession", + addClientId: clientId + }); + } else { + this.wsClient.sendMessage({ + type: "AddSessionClient", + addClientId: clientId, + sessionId: this.state.sessionId + }); + } + } + } + + onBroadcastFromSessionMsg = (msg: ServerTypes.BroadcastFromSessionMsg) => { + + } + + onLeaveSession = () => { + this.wsClient.leaveSession(); + this.setState({ sessionId: null }); + } + + render() { + return
+ +
+ {this.state.sessionId ? + : + + } +
+
+ } +} + +export default App; \ No newline at end of file diff --git a/src/feature/home/HomePage.tsx b/src/feature/home/HomePage.tsx index d805248..e892d7d 100644 --- a/src/feature/home/HomePage.tsx +++ b/src/feature/home/HomePage.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import QRCode from "react-qr-code"; -import { IonButton, IonModal } from "@ionic/react"; +import { IonButton, IonContent, IonHeader, IonModal, IonPage, IonTitle, IonToolbar } from "@ionic/react"; import { ScanClientModal } from "../scan-client/ScanClientModal"; import { IntroSlides } from "../intro-slides/IntroSlides"; @@ -28,32 +28,47 @@ export const HomePage: React.FC = ({ }; return ( -
-
- {ourClientId && ( - <> - -

Client id {ourClientId}

- - )} - setScanModalOpen(true)}>Open Scanner -
- - - - -
+ + + +

+ QR Sync +

+
+
+ +
+
+ {ourClientId && ( + <> + +

Client id {ourClientId}

+ + )} + setScanModalOpen(true)}> + Open Scanner + +
+ + + + +
+
+
); }; diff --git a/src/feature/session/SessionPage.tsx b/src/feature/session/SessionPage.tsx index 9be5e59..fff56a9 100644 --- a/src/feature/session/SessionPage.tsx +++ b/src/feature/session/SessionPage.tsx @@ -1,6 +1,6 @@ import React from "react"; import { ServerTypes } from "../../ServerTypes"; -import { IonButton } from "@ionic/react"; +import { IonButton, IonPage } from "@ionic/react"; import { WSClient } from "../../WSClient"; import { SessionMessage } from "./SessionMessage"; import { SessionActionsList } from "../session-actions/SessionActionsList"; @@ -31,7 +31,7 @@ export class SessionPage extends React.Component + return Leave Session

Session Page

{this.props.sessionId}

@@ -44,7 +44,7 @@ export class SessionPage extends React.Component - +
} onWebsocketMessage = (msg: ServerTypes.Msg) => { diff --git a/src/index.tsx b/src/index.tsx index d4ec660..71eb9cc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; -import App from './App'; +import { App } from './App'; import * as serviceWorker from './serviceWorker'; import '@ionic/react/css/core.css'; import { setupIonicReact } from '@ionic/react'; From 204ee7ae393bd17fe506800c0a84d4cfe84d07f6 Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Sat, 16 Mar 2024 23:57:01 +0000 Subject: [PATCH 2/8] Migrate App to IonReactRouter and functional component --- src/App.tsx | 64 ++++++++++------ src/AppOld.tsx | 115 ---------------------------- src/feature/session/SessionPage.tsx | 4 +- 3 files changed, 41 insertions(+), 142 deletions(-) delete mode 100644 src/AppOld.tsx diff --git a/src/App.tsx b/src/App.tsx index 907bfc5..29a206a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,54 +8,59 @@ import { IonReactRouter } from '@ionic/react-router'; import { Redirect, Route } from 'react-router'; import { HomePage } from './feature/home/HomePage'; import { WSClient } from './WSClient'; +import { ServerTypes } from './ServerTypes'; +import { SessionPage } from './feature/session/SessionPage'; export const App: React.FC = () => { let wsUrl = "wss://qrsync.org/api/v1/ws"; const [wsClient] = useState(new WSClient(wsUrl)); + const [ourClientId, setOurClientId] = useState(); + const [sessionOwnerId, setSessionOwnerId] = useState(); + const [sessionId, setSessionId] = useState(); + const [clientMap, setClientMap] = useState>({}); + const [result, setResult] = useState(); - wsClient.addMessageHandler("main", onReceiveWebsocketMsg); + const onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { + if (msg.clientId === ourClientId) { + setSessionId(msg.sessionId); + setSessionOwnerId(msg.sessionOwnerId); + setClientMap(msg.clientMap); + } + } + + const onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { + setOurClientId(msg.client.id); + } const onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { if (msg.type) { switch (msg.type) { - case "ClientConnect": return this.onClientConnectMsg(msg); - case "ClientJoinedSession": return this.onClientJoinedSessionMsg(msg); - case "BroadcastFromSession": return this.onBroadcastFromSessionMsg(msg); + case "ClientConnect": return onClientConnectMsg(msg); + case "ClientJoinedSession": return onClientJoinedSessionMsg(msg); + case "BroadcastFromSession": return onBroadcastFromSessionMsg(msg); } } else { console.warn("No message type ", msg); } } - const onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { - if (msg.clientId === this.state.ourClientId) { - this.setState({ - sessionId: msg.sessionId, - sessionOwnerId: msg.sessionOwnerId, - clientMap: msg.clientMap - }); - } - } - - const onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { - this.setState({ ourClientId: msg.client.id }); - } + wsClient.addMessageHandler("main", onReceiveWebsocketMsg); const onScanClient = (clientId: string | null) => { - this.setState({result: "" + clientId}); + setResult("" + clientId); if (clientId) { console.log("Client id scanned ", clientId); - if (!this.state.sessionId) { - this.wsClient.sendMessage({ + if (!sessionId) { + wsClient.sendMessage({ type: "CreateSession", addClientId: clientId }); } else { - this.wsClient.sendMessage({ + wsClient.sendMessage({ type: "AddSessionClient", addClientId: clientId, - sessionId: this.state.sessionId + sessionId: sessionId }); } } @@ -65,9 +70,9 @@ export const App: React.FC = () => { } - onLeaveSession = () => { - this.wsClient.leaveSession(); - this.setState({ sessionId: null }); + const onLeaveSession = () => { + wsClient.leaveSession(); + setSessionId(undefined); } return ( @@ -79,6 +84,15 @@ export const App: React.FC = () => { onScanClient={onScanClient} > + + + diff --git a/src/AppOld.tsx b/src/AppOld.tsx deleted file mode 100644 index cbe7ff8..0000000 --- a/src/AppOld.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react'; -import './App.css'; -import { ServerTypes } from './ServerTypes'; -import { Header } from './feature/header/Header'; -import { SessionPage } from './feature/session/SessionPage'; -import { HomePage } from './feature/home/HomePage'; -import { WSClient } from './WSClient'; -import { IonApp } from '@ionic/react'; - -interface AppState { - result: string; - goToURL: string; - clientMap: {[key: string]: ServerTypes.Client}; - scanModalOpen: boolean; - ourClientId: string | null; - sessionId: string | null; - sessionOwnerId: string | null; -} - -class App extends React.Component<{}, AppState> { - wsClient: WSClient; - - constructor(props: {}) { - super(props); - this.state = { - result: "", - goToURL: "", - clientMap: {}, - scanModalOpen: false, - ourClientId: null, - sessionId: null, - sessionOwnerId: null - }; - let wsUrl = "wss://qrsync.org/api/v1/ws"; - this.wsClient = new WSClient(wsUrl); - this.wsClient.addMessageHandler("main", this.onReceiveWebsocketMsg); - } - - onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { - if (msg.type) { - switch (msg.type) { - case "ClientConnect": return this.onClientConnectMsg(msg); - case "ClientJoinedSession": return this.onClientJoinedSessionMsg(msg); - case "BroadcastFromSession": return this.onBroadcastFromSessionMsg(msg); - } - } else { - console.warn("No message type ", msg); - } - } - - onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { - if (msg.clientId === this.state.ourClientId) { - this.setState({ - sessionId: msg.sessionId, - sessionOwnerId: msg.sessionOwnerId, - clientMap: msg.clientMap - }); - } - } - - onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { - this.setState({ ourClientId: msg.client.id }); - } - - onScanClient = (clientId: string | null) => { - this.setState({result: "" + clientId}); - if (clientId) { - console.log("Client id scanned ", clientId); - if (!this.state.sessionId) { - this.wsClient.sendMessage({ - type: "CreateSession", - addClientId: clientId - }); - } else { - this.wsClient.sendMessage({ - type: "AddSessionClient", - addClientId: clientId, - sessionId: this.state.sessionId - }); - } - } - } - - onBroadcastFromSessionMsg = (msg: ServerTypes.BroadcastFromSessionMsg) => { - - } - - onLeaveSession = () => { - this.wsClient.leaveSession(); - this.setState({ sessionId: null }); - } - - render() { - return
- -
- {this.state.sessionId ? - : - - } -
-
- } -} - -export default App; \ No newline at end of file diff --git a/src/feature/session/SessionPage.tsx b/src/feature/session/SessionPage.tsx index fff56a9..1d0dc8a 100644 --- a/src/feature/session/SessionPage.tsx +++ b/src/feature/session/SessionPage.tsx @@ -6,8 +6,8 @@ import { SessionMessage } from "./SessionMessage"; import { SessionActionsList } from "../session-actions/SessionActionsList"; export interface SessionPageProps { - sessionId: string | null; - sessionOwnerId: string | null; + sessionId: string | undefined; + sessionOwnerId: string | undefined; clientMap: Record; wsClient: WSClient; onLeaveSession: () => any; From aac02ff467e9aa4a558ef12bbeea222dd8bacece Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Sun, 17 Mar 2024 00:00:43 +0000 Subject: [PATCH 3/8] Address linter suggestions --- src/App.tsx | 2 -- src/feature/home/HomePage.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 29a206a..2b22ce4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,6 @@ export const App: React.FC = () => { const [sessionOwnerId, setSessionOwnerId] = useState(); const [sessionId, setSessionId] = useState(); const [clientMap, setClientMap] = useState>({}); - const [result, setResult] = useState(); const onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { if (msg.clientId === ourClientId) { @@ -48,7 +47,6 @@ export const App: React.FC = () => { wsClient.addMessageHandler("main", onReceiveWebsocketMsg); const onScanClient = (clientId: string | null) => { - setResult("" + clientId); if (clientId) { console.log("Client id scanned ", clientId); if (!sessionId) { diff --git a/src/feature/home/HomePage.tsx b/src/feature/home/HomePage.tsx index e892d7d..cb9e0f6 100644 --- a/src/feature/home/HomePage.tsx +++ b/src/feature/home/HomePage.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import QRCode from "react-qr-code"; -import { IonButton, IonContent, IonHeader, IonModal, IonPage, IonTitle, IonToolbar } from "@ionic/react"; +import { IonButton, IonContent, IonHeader, IonModal, IonPage, IonToolbar } from "@ionic/react"; import { ScanClientModal } from "../scan-client/ScanClientModal"; import { IntroSlides } from "../intro-slides/IntroSlides"; From 069ee6515650d596bafc000763f13e324cd8c4e9 Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Sun, 17 Mar 2024 14:33:51 +0000 Subject: [PATCH 4/8] Format everything using prettier --- README.md | 1 + package-lock.json | 2 +- package.json | 2 +- public/manifest.json | 4 +- src/App.css | 4 +- src/App.test.tsx | 10 +- src/App.tsx | 91 ++++++------ src/ServerTypes.ts | 4 +- src/WSClient.ts | 2 +- src/feature/header/Header.tsx | 27 ++-- src/feature/home/HomePage.tsx | 13 +- src/feature/intro-slides/slides.css | 10 +- .../session-actions/OpenWebsiteModal.tsx | 67 +++++---- .../session-actions/SessionActionsList.tsx | 139 +++++++++++------- src/feature/session/SessionPage.tsx | 111 +++++++------- src/index.css | 6 +- src/index.tsx | 17 +-- src/serviceWorker.ts | 16 +- tsconfig.json | 10 +- 19 files changed, 289 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index 145f248..a9b286b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## QR Sync Admin Web + This is the new web interface for the QR Sync Admin app. The goal of this admin app is for controlling multiple computers using a quick and easy QR scanning sync process. diff --git a/package-lock.json b/package-lock.json index f71865e..5b6876d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18877,4 +18877,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index c82b0f0..11b2e1b 100644 --- a/package.json +++ b/package.json @@ -46,4 +46,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index 747221a..11efc9a 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -16,9 +16,9 @@ "src": "logo512.png", "type": "image/png", "sizes": "512x512" - } + } ], "start_url": ".", "display": "standalone", "theme_color": "#107C76" -} +} \ No newline at end of file diff --git a/src/App.css b/src/App.css index ee53c3f..b9b9c1f 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,5 @@ body { - --ion-color-primary: #107C76; + --ion-color-primary: #107c76; --ion-background-color: rgb(39, 39, 39); --ion-text-color: white; --ion-text-color-rgb: 255, 255, 255; @@ -80,4 +80,4 @@ body { to { transform: rotate(360deg); } -} \ No newline at end of file +} diff --git a/src/App.test.tsx b/src/App.test.tsx index 2eb40d1..1729d67 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { App } from './App'; +import React from "react"; +import ReactDOM from "react-dom"; +import { App } from "./App"; -it('renders without crashing', () => { - const div = document.createElement('div'); +it("renders without crashing", () => { + const div = document.createElement("div"); ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); diff --git a/src/App.tsx b/src/App.tsx index 2b22ce4..156d533 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,48 +1,51 @@ -import React, { useState } from 'react'; -import './App.css'; -import { - IonApp, - IonRouterOutlet, -} from '@ionic/react'; -import { IonReactRouter } from '@ionic/react-router'; -import { Redirect, Route } from 'react-router'; -import { HomePage } from './feature/home/HomePage'; -import { WSClient } from './WSClient'; -import { ServerTypes } from './ServerTypes'; -import { SessionPage } from './feature/session/SessionPage'; +import React, { useState } from "react"; +import "./App.css"; +import { IonApp, IonRouterOutlet } from "@ionic/react"; +import { IonReactRouter } from "@ionic/react-router"; +import { Redirect, Route } from "react-router"; +import { HomePage } from "./feature/home/HomePage"; +import { WSClient } from "./WSClient"; +import { ServerTypes } from "./ServerTypes"; +import { SessionPage } from "./feature/session/SessionPage"; export const App: React.FC = () => { - let wsUrl = "wss://qrsync.org/api/v1/ws"; const [wsClient] = useState(new WSClient(wsUrl)); const [ourClientId, setOurClientId] = useState(); const [sessionOwnerId, setSessionOwnerId] = useState(); const [sessionId, setSessionId] = useState(); - const [clientMap, setClientMap] = useState>({}); + const [clientMap, setClientMap] = useState< + Record + >({}); - const onClientJoinedSessionMsg = (msg: ServerTypes.ClientJoinedSessionMsg) => { + const onClientJoinedSessionMsg = ( + msg: ServerTypes.ClientJoinedSessionMsg + ) => { if (msg.clientId === ourClientId) { setSessionId(msg.sessionId); setSessionOwnerId(msg.sessionOwnerId); setClientMap(msg.clientMap); } - } + }; const onClientConnectMsg = (msg: ServerTypes.ClientConnectMsg) => { setOurClientId(msg.client.id); - } + }; const onReceiveWebsocketMsg = (msg: ServerTypes.Msg) => { if (msg.type) { switch (msg.type) { - case "ClientConnect": return onClientConnectMsg(msg); - case "ClientJoinedSession": return onClientJoinedSessionMsg(msg); - case "BroadcastFromSession": return onBroadcastFromSessionMsg(msg); + case "ClientConnect": + return onClientConnectMsg(msg); + case "ClientJoinedSession": + return onClientJoinedSessionMsg(msg); + case "BroadcastFromSession": + return onBroadcastFromSessionMsg(msg); } } else { console.warn("No message type ", msg); } - } + }; wsClient.addMessageHandler("main", onReceiveWebsocketMsg); @@ -52,47 +55,49 @@ export const App: React.FC = () => { if (!sessionId) { wsClient.sendMessage({ type: "CreateSession", - addClientId: clientId + addClientId: clientId, }); } else { wsClient.sendMessage({ type: "AddSessionClient", addClientId: clientId, - sessionId: sessionId + sessionId: sessionId, }); } } - } - - const onBroadcastFromSessionMsg = (msg: ServerTypes.BroadcastFromSessionMsg) => { + }; - } + const onBroadcastFromSessionMsg = ( + msg: ServerTypes.BroadcastFromSessionMsg + ) => {}; const onLeaveSession = () => { wsClient.leaveSession(); setSessionId(undefined); - } + }; - return ( - - - - + + + + - - - + + - - - - - ); -}; \ No newline at end of file + > + + + + +
+ ); +}; diff --git a/src/ServerTypes.ts b/src/ServerTypes.ts index 0c2543d..f4f51d1 100644 --- a/src/ServerTypes.ts +++ b/src/ServerTypes.ts @@ -32,14 +32,14 @@ export namespace ServerTypes { clientId: string; sessionId: string; sessionOwnerId: string; - clientMap: {[key: string]: Client}; + clientMap: { [key: string]: Client }; } export interface ClientLeftSessionMsg { type: "ClientLeftSession"; clientId: string; sessionId: string; sessionOwnerId: string; - clientMap: {[key: string]: Client}; + clientMap: { [key: string]: Client }; } export interface BroadcastToSessionMsg { type: "BroadcastToSession"; diff --git a/src/WSClient.ts b/src/WSClient.ts index b28ebd8..9d8c3f2 100644 --- a/src/WSClient.ts +++ b/src/WSClient.ts @@ -4,7 +4,7 @@ export type WSMessageHandler = (msg: ServerTypes.Msg) => any; export class WSClient { ws: WebSocket; - messageHandlerMap: {[id: string]: WSMessageHandler} = {}; + messageHandlerMap: { [id: string]: WSMessageHandler } = {}; allMessages: ServerTypes.Msg[] = []; public clientId: string | null = null; clientName: string = ""; diff --git a/src/feature/header/Header.tsx b/src/feature/header/Header.tsx index eb2fb6c..b36109b 100644 --- a/src/feature/header/Header.tsx +++ b/src/feature/header/Header.tsx @@ -1,18 +1,17 @@ -import logo_fg from './logo_fg1.svg'; -import logo_bg from './sync_arrows1.svg'; -import React from 'react'; +import logo_fg from "./logo_fg1.svg"; +import logo_bg from "./sync_arrows1.svg"; +import React from "react"; export class Header extends React.Component { - - render = () => (
-
-
- logo - logo-bg -
-

QR Sync

+ render = () => ( +
+
+
+ logo + logo-bg
-
) - +

QR Sync

+
+
+ ); } - diff --git a/src/feature/home/HomePage.tsx b/src/feature/home/HomePage.tsx index cb9e0f6..91b7953 100644 --- a/src/feature/home/HomePage.tsx +++ b/src/feature/home/HomePage.tsx @@ -1,6 +1,13 @@ import React, { useState } from "react"; import QRCode from "react-qr-code"; -import { IonButton, IonContent, IonHeader, IonModal, IonPage, IonToolbar } from "@ionic/react"; +import { + IonButton, + IonContent, + IonHeader, + IonModal, + IonPage, + IonToolbar, +} from "@ionic/react"; import { ScanClientModal } from "../scan-client/ScanClientModal"; import { IntroSlides } from "../intro-slides/IntroSlides"; @@ -31,9 +38,7 @@ export const HomePage: React.FC = ({ -

- QR Sync -

+

QR Sync

diff --git a/src/feature/intro-slides/slides.css b/src/feature/intro-slides/slides.css index 9008e4b..d53a293 100644 --- a/src/feature/intro-slides/slides.css +++ b/src/feature/intro-slides/slides.css @@ -1,12 +1,12 @@ .introSlider { - margin-bottom: 20px; + margin-bottom: 20px; } .slideDescription { - width: 100%; - margin-bottom: 40px; + width: 100%; + margin-bottom: 40px; } .introSlide img { - padding: 40px; -} \ No newline at end of file + padding: 40px; +} diff --git a/src/feature/session-actions/OpenWebsiteModal.tsx b/src/feature/session-actions/OpenWebsiteModal.tsx index 2f357b4..cc27ccc 100644 --- a/src/feature/session-actions/OpenWebsiteModal.tsx +++ b/src/feature/session-actions/OpenWebsiteModal.tsx @@ -3,36 +3,43 @@ import React from "react"; import { SessionMessage } from "../session/SessionMessage"; import { SessionActionModal } from "./SessionActionModal"; -export class OpenWebsiteModal extends SessionActionModal<{}, { urlInput: string }> { - - state = { - urlInput: "" - }; +export class OpenWebsiteModal extends SessionActionModal< + {}, + { urlInput: string } +> { + state = { + urlInput: "", + }; - onOpenWebsiteClick = () => { - let id = this.props.wsClient.getId(); - if (this.props.wsClient && id != null) { - let sessionMsg: SessionMessage = { - type: "OPEN_WEBSITE", - text: this.state.urlInput, - senderId: id, - senderName: this.props.wsClient.getName() - }; - this.props.wsClient.sendMessage({ - type: "BroadcastToSession", - payload: sessionMsg - }); - } - this.props.closeModal(); + onOpenWebsiteClick = () => { + let id = this.props.wsClient.getId(); + if (this.props.wsClient && id != null) { + let sessionMsg: SessionMessage = { + type: "OPEN_WEBSITE", + text: this.state.urlInput, + senderId: id, + senderName: this.props.wsClient.getName(), + }; + this.props.wsClient.sendMessage({ + type: "BroadcastToSession", + payload: sessionMsg, + }); } + this.props.closeModal(); + }; - render() { - return <>

Open Website

-

Open a website on devices controlled by this session

- this.setState({ urlInput: e.detail.value! })}> - Open Website - - } - -} \ No newline at end of file + render() { + return ( + <> +

Open Website

+

Open a website on devices controlled by this session

+ this.setState({ urlInput: e.detail.value! })} + > + Open Website + + ); + } +} diff --git a/src/feature/session-actions/SessionActionsList.tsx b/src/feature/session-actions/SessionActionsList.tsx index 8debc96..7ce4683 100644 --- a/src/feature/session-actions/SessionActionsList.tsx +++ b/src/feature/session-actions/SessionActionsList.tsx @@ -1,82 +1,107 @@ import React from "react"; -import { globeOutline } from 'ionicons/icons'; +import { globeOutline } from "ionicons/icons"; import { IonIcon, IonModal } from "@ionic/react"; import { OpenWebsiteModal } from "./OpenWebsiteModal"; import { WSClient } from "../../WSClient"; import { SessionActionModal } from "./SessionActionModal"; export interface SessionActionsListProps { - wsClient: WSClient; - userIsSessionOwner: boolean; + wsClient: WSClient; + userIsSessionOwner: boolean; } export interface SessionActionsListState { - actionModalOpen: boolean; - actionModalComponent?: typeof SessionActionModal; + actionModalOpen: boolean; + actionModalComponent?: typeof SessionActionModal; } export interface SessionActionSummary { - name: string; - ionicon: string; - ownerOnly: boolean; - modalComponent?: typeof SessionActionModal; + name: string; + ionicon: string; + ownerOnly: boolean; + modalComponent?: typeof SessionActionModal; } const sessionActions: SessionActionSummary[] = [ - { - name: "Open Website", - ionicon: globeOutline, - ownerOnly: true, - modalComponent: OpenWebsiteModal - } + { + name: "Open Website", + ionicon: globeOutline, + ownerOnly: true, + modalComponent: OpenWebsiteModal, + }, ]; -export class SessionActionsList extends React.Component { - - state: SessionActionsListState = { - actionModalOpen: false - }; +export class SessionActionsList extends React.Component< + SessionActionsListProps, + SessionActionsListState +> { + state: SessionActionsListState = { + actionModalOpen: false, + }; - render() { - console.log("User is session owner", this.props.userIsSessionOwner); - return
-

User is session owner? {this.props.userIsSessionOwner ? "yes" : "no"}

- {sessionActions.filter((action) => this.props.userIsSessionOwner || !action.ownerOnly) - .map((action) => this.renderActionButton(action)) - } - - {this.renderModal()} - -
- } + render() { + console.log("User is session owner", this.props.userIsSessionOwner); + return ( +
+

+ User is session owner? {this.props.userIsSessionOwner ? "yes" : "no"} +

+ {sessionActions + .filter( + (action) => this.props.userIsSessionOwner || !action.ownerOnly + ) + .map((action) => this.renderActionButton(action))} + + {this.renderModal()} + +
+ ); + } - renderActionButton = (action: SessionActionSummary) => { - return
this.onActionClicked(action)} - style={{borderWidth: 1, borderStyle: "solid", borderColor: "white", borderRadius: 20, display: "inline-block"}} - > -

{action.name}

- -
- } + renderActionButton = (action: SessionActionSummary) => { + return ( +
this.onActionClicked(action)} + style={{ + borderWidth: 1, + borderStyle: "solid", + borderColor: "white", + borderRadius: 20, + display: "inline-block", + }} + > +

{action.name}

+ +
+ ); + }; - renderModal = () => { - let ModalComponent = this.state.actionModalComponent; - if (ModalComponent) { - return - } + renderModal = () => { + let ModalComponent = this.state.actionModalComponent; + if (ModalComponent) { + return ( + + ); } + }; - closeModal = () => { - this.setState({ actionModalOpen: false }); - } + closeModal = () => { + this.setState({ actionModalOpen: false }); + }; - onActionClicked = (action: SessionActionSummary) => { - if (action.name === "Open Website") { - this.setState({ actionModalOpen: true, actionModalComponent: action.modalComponent }) - } + onActionClicked = (action: SessionActionSummary) => { + if (action.name === "Open Website") { + this.setState({ + actionModalOpen: true, + actionModalComponent: action.modalComponent, + }); } - -} \ No newline at end of file + }; +} diff --git a/src/feature/session/SessionPage.tsx b/src/feature/session/SessionPage.tsx index 1d0dc8a..500197a 100644 --- a/src/feature/session/SessionPage.tsx +++ b/src/feature/session/SessionPage.tsx @@ -6,65 +6,72 @@ import { SessionMessage } from "./SessionMessage"; import { SessionActionsList } from "../session-actions/SessionActionsList"; export interface SessionPageProps { - sessionId: string | undefined; - sessionOwnerId: string | undefined; - clientMap: Record; - wsClient: WSClient; - onLeaveSession: () => any; + sessionId: string | undefined; + sessionOwnerId: string | undefined; + clientMap: Record; + wsClient: WSClient; + onLeaveSession: () => any; } export interface SessionPageState { - urlInput: string; - sessionMessages: SessionMessage[]; + urlInput: string; + sessionMessages: SessionMessage[]; } -export class SessionPage extends React.Component { +export class SessionPage extends React.Component< + SessionPageProps, + SessionPageState +> { + state = { + urlInput: "", + sessionMessages: [], + } as SessionPageState; - state = { - urlInput: "", - sessionMessages: [] - } as SessionPageState; + constructor(props: SessionPageProps) { + super(props); + props.wsClient.addMessageHandler("session_page", this.onWebsocketMessage); + } - constructor(props: SessionPageProps) { - super(props); - props.wsClient.addMessageHandler("session_page", this.onWebsocketMessage); - } - - render() { - return - Leave Session -

Session Page

-

{this.props.sessionId}

-

Session Messages

-
    - {this.state.sessionMessages.map((sessionMsg) => (
  • {sessionMsg.text}
  • ))} -
-

Session Actions:

- -
- } + render() { + return ( + + Leave Session +

Session Page

+

{this.props.sessionId}

+

Session Messages

+
    + {this.state.sessionMessages.map((sessionMsg) => ( +
  • {sessionMsg.text}
  • + ))} +
+

Session Actions:

+ +
+ ); + } - onWebsocketMessage = (msg: ServerTypes.Msg) => { - if (msg.type === "BroadcastFromSession") { - let payload: SessionMessage = msg.payload; - if (payload.type && payload.text) { - let sessionMsgs: SessionMessage[] = []; - if (this.state.sessionMessages) { - sessionMsgs = this.state.sessionMessages; - } - sessionMsgs.push(payload); - this.setState({ sessionMessages: sessionMsgs}); - } - if (msg.senderId !== this.props.wsClient.getId()) { - if (payload.type === "OPEN_WEBSITE") { - console.log("Opening url ", payload.text); - window.open(payload.text, "_blank"); - } - } + onWebsocketMessage = (msg: ServerTypes.Msg) => { + if (msg.type === "BroadcastFromSession") { + let payload: SessionMessage = msg.payload; + if (payload.type && payload.text) { + let sessionMsgs: SessionMessage[] = []; + if (this.state.sessionMessages) { + sessionMsgs = this.state.sessionMessages; } + sessionMsgs.push(payload); + this.setState({ sessionMessages: sessionMsgs }); + } + if (msg.senderId !== this.props.wsClient.getId()) { + if (payload.type === "OPEN_WEBSITE") { + console.log("Opening url ", payload.text); + window.open(payload.text, "_blank"); + } + } } - -} \ No newline at end of file + }; +} diff --git a/src/index.css b/src/index.css index ec2585e..4a1df4d 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/src/index.tsx b/src/index.tsx index 71eb9cc..b0abd4e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,15 +1,14 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import './index.css'; -import { App } from './App'; -import * as serviceWorker from './serviceWorker'; -import '@ionic/react/css/core.css'; -import { setupIonicReact } from '@ionic/react'; +import React from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import { App } from "./App"; +import * as serviceWorker from "./serviceWorker"; +import "@ionic/react/css/core.css"; +import { setupIonicReact } from "@ionic/react"; setupIonicReact(); - -const container = document.getElementById('root'); +const container = document.getElementById("root"); const root = createRoot(container!); root.render(); diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts index 15d90cb..b3793f8 100644 --- a/src/serviceWorker.ts +++ b/src/serviceWorker.ts @@ -12,12 +12,12 @@ const isLocalhost = Boolean( window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) ); type Config = { @@ -51,7 +51,7 @@ export function register(config?: Config) { navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + 'worker. To learn more, visit https://bit.ly/CRA-PWA' ); }); } else { @@ -79,7 +79,7 @@ function registerValidSW(swUrl: string, config?: Config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' ); // Execute callback diff --git a/tsconfig.json b/tsconfig.json index 5c05ace..889be38 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -21,7 +17,5 @@ "jsx": "react", "noFallthroughCasesInSwitch": true }, - "include": [ - "src" - ] + "include": ["src"] } From cf2bc0555cbcdaf0f85adf4b5e337b9ad3a3a16a Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Sun, 17 Mar 2024 22:25:18 +0000 Subject: [PATCH 5/8] Rename to AddClientToSession --- package-lock.json | 2 +- src/App.tsx | 12 ++++++------ src/ServerTypes.ts | 6 +++--- src/WSClient.ts | 2 +- src/feature/home/HomePage.tsx | 4 ++++ 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b6876d..f71865e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18877,4 +18877,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 156d533..a7364be 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import "./App.css"; -import { IonApp, IonRouterOutlet } from "@ionic/react"; +import { IonApp, IonRouterOutlet, useIonRouter } from "@ionic/react"; import { IonReactRouter } from "@ionic/react-router"; import { Redirect, Route } from "react-router"; import { HomePage } from "./feature/home/HomePage"; @@ -9,7 +9,8 @@ import { ServerTypes } from "./ServerTypes"; import { SessionPage } from "./feature/session/SessionPage"; export const App: React.FC = () => { - let wsUrl = "wss://qrsync.org/api/v1/ws"; + // let wsUrl = "wss://qrsync.org/api/v1/ws"; + let wsUrl = "ws://localhost:4010/api/v1/ws"; const [wsClient] = useState(new WSClient(wsUrl)); const [ourClientId, setOurClientId] = useState(); const [sessionOwnerId, setSessionOwnerId] = useState(); @@ -59,7 +60,7 @@ export const App: React.FC = () => { }); } else { wsClient.sendMessage({ - type: "AddSessionClient", + type: "AddClientToSession", addClientId: clientId, sessionId: sessionId, }); @@ -80,7 +81,7 @@ export const App: React.FC = () => { - + { onLeaveSession={onLeaveSession} > - diff --git a/src/ServerTypes.ts b/src/ServerTypes.ts index f4f51d1..426672d 100644 --- a/src/ServerTypes.ts +++ b/src/ServerTypes.ts @@ -1,5 +1,5 @@ export namespace ServerTypes { - export type Msg = ClientConnectMsg | CreateSessionMsg | UpdateClientMsg | AddSessionClientMsg | ClientJoinedSessionMsg | ClientLeftSessionMsg | BroadcastToSessionMsg | BroadcastFromSessionMsg | ErrorMsg | InfoMsg + export type Msg = ClientConnectMsg | CreateSessionMsg | UpdateClientMsg | AddClientToSessionMsg | ClientJoinedSessionMsg | ClientLeftSessionMsg | BroadcastToSessionMsg | BroadcastFromSessionMsg | ErrorMsg | InfoMsg export interface Client { id: string; @@ -22,8 +22,8 @@ export namespace ServerTypes { type: "UpdateClient"; name: string; } - export interface AddSessionClientMsg { - type: "AddSessionClient"; + export interface AddClientToSessionMsg { + type: "AddClientToSession"; sessionId: string; addClientId: string; } diff --git a/src/WSClient.ts b/src/WSClient.ts index 9d8c3f2..84118e0 100644 --- a/src/WSClient.ts +++ b/src/WSClient.ts @@ -69,7 +69,7 @@ export class WSClient { const prevSessionId = localStorage.getItem("prevSessionId"); if (prevSessionId) { this.sendMessage({ - type: "AddSessionClient", + type: "AddClientToSession", sessionId: prevSessionId, addClientId: this.clientId }); diff --git a/src/feature/home/HomePage.tsx b/src/feature/home/HomePage.tsx index 91b7953..90cb2b2 100644 --- a/src/feature/home/HomePage.tsx +++ b/src/feature/home/HomePage.tsx @@ -7,6 +7,7 @@ import { IonModal, IonPage, IonToolbar, + useIonRouter, } from "@ionic/react"; import { ScanClientModal } from "../scan-client/ScanClientModal"; import { IntroSlides } from "../intro-slides/IntroSlides"; @@ -22,6 +23,8 @@ export const HomePage: React.FC = ({ }) => { const [scanModalOpen, setScanModalOpen] = useState(false); + const router = useIonRouter(); + const closeScannerModal = () => { console.log("Close scanner"); setScanModalOpen(false); @@ -31,6 +34,7 @@ export const HomePage: React.FC = ({ if (result) { closeScannerModal(); onScanClient(result); + router.push("session"); } }; From c2fd503bd8aaa559821f1b2070594fb7834db882 Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Fri, 29 Mar 2024 11:34:53 +0100 Subject: [PATCH 6/8] Component to allow navigating to routes from parent of router. --- src/App.tsx | 25 +++++++++++++++++++++++-- src/NavigateOnStateChange.tsx | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/NavigateOnStateChange.tsx diff --git a/src/App.tsx b/src/App.tsx index a7364be..ded4116 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from "react"; import "./App.css"; -import { IonApp, IonRouterOutlet, useIonRouter } from "@ionic/react"; +import { IonApp, IonPage, IonRouterOutlet, useIonRouter } from "@ionic/react"; import { IonReactRouter } from "@ionic/react-router"; import { Redirect, Route } from "react-router"; import { HomePage } from "./feature/home/HomePage"; import { WSClient } from "./WSClient"; import { ServerTypes } from "./ServerTypes"; import { SessionPage } from "./feature/session/SessionPage"; +import { NavigateOnStateChange } from "./NavigateOnStateChange"; export const App: React.FC = () => { // let wsUrl = "wss://qrsync.org/api/v1/ws"; @@ -18,6 +19,15 @@ export const App: React.FC = () => { const [clientMap, setClientMap] = useState< Record >({}); + // State used to navigate to route via a server sent event (not user link click) + const [changeToRoute, setChangeToRoute] = useState(); + + useEffect(() => { + setTimeout(() => { + console.log("attempt to change to /test"); + setChangeToRoute("test"); + }, 5000); + }, []); const onClientJoinedSessionMsg = ( msg: ServerTypes.ClientJoinedSessionMsg @@ -80,8 +90,13 @@ export const App: React.FC = () => { return ( + {/* + Way to change react router route based on websocket events without + having to create second state wrapper inside of IonReactRouter + */} + setChangeToRoute(undefined)}/> - + { onLeaveSession={onLeaveSession} > + + +

Just a quick test...

+
+
+
diff --git a/src/NavigateOnStateChange.tsx b/src/NavigateOnStateChange.tsx new file mode 100644 index 0000000..1c4315c --- /dev/null +++ b/src/NavigateOnStateChange.tsx @@ -0,0 +1,21 @@ +import { useIonRouter } from "@ionic/react"; +import React, { useContext, useEffect } from "react"; + +type NavigateOnStateChangeProps = { route?: string, onNavigate?: () => void} + +export const NavigateOnStateChange: React.FC = ({route, onNavigate}) => { + const ionRouter = useIonRouter(); + console.log("NavigateOnStateChange", ionRouter); + useEffect(() => { + if (route) { + console.log("Route changed ", route); + setTimeout(() => { + ionRouter.push(route); + }); + if (onNavigate) { + onNavigate(); + } + } + }, [route]); + return <> + } \ No newline at end of file From 5e7d86816741ce385680fa500330e3ae478463c6 Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Fri, 29 Mar 2024 16:10:48 +0100 Subject: [PATCH 7/8] Navigate to session on connect, and back to home on leave. --- src/App.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ded4116..47cf649 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import "./App.css"; -import { IonApp, IonPage, IonRouterOutlet, useIonRouter } from "@ionic/react"; +import { IonApp, IonPage, IonRouterOutlet } from "@ionic/react"; import { IonReactRouter } from "@ionic/react-router"; import { Redirect, Route } from "react-router"; import { HomePage } from "./feature/home/HomePage"; @@ -10,8 +10,8 @@ import { SessionPage } from "./feature/session/SessionPage"; import { NavigateOnStateChange } from "./NavigateOnStateChange"; export const App: React.FC = () => { - // let wsUrl = "wss://qrsync.org/api/v1/ws"; - let wsUrl = "ws://localhost:4010/api/v1/ws"; + let wsUrl = "wss://qrsync.org/api/v1/ws"; + // let wsUrl = "ws://localhost:4010/api/v1/ws"; const [wsClient] = useState(new WSClient(wsUrl)); const [ourClientId, setOurClientId] = useState(); const [sessionOwnerId, setSessionOwnerId] = useState(); @@ -22,13 +22,6 @@ export const App: React.FC = () => { // State used to navigate to route via a server sent event (not user link click) const [changeToRoute, setChangeToRoute] = useState(); - useEffect(() => { - setTimeout(() => { - console.log("attempt to change to /test"); - setChangeToRoute("test"); - }, 5000); - }, []); - const onClientJoinedSessionMsg = ( msg: ServerTypes.ClientJoinedSessionMsg ) => { @@ -36,6 +29,7 @@ export const App: React.FC = () => { setSessionId(msg.sessionId); setSessionOwnerId(msg.sessionOwnerId); setClientMap(msg.clientMap); + setChangeToRoute("/session"); } }; @@ -85,6 +79,7 @@ export const App: React.FC = () => { const onLeaveSession = () => { wsClient.leaveSession(); setSessionId(undefined); + setChangeToRoute("/index.html"); }; return ( From 8fecd5bb30f11273f8afc9e0631fc05680d6fed0 Mon Sep 17 00:00:00 2001 From: Michael Clapham Date: Fri, 29 Mar 2024 16:23:29 +0100 Subject: [PATCH 8/8] Fix lint issues --- src/App.tsx | 2 +- src/NavigateOnStateChange.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 47cf649..2c4b0ca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import "./App.css"; import { IonApp, IonPage, IonRouterOutlet } from "@ionic/react"; import { IonReactRouter } from "@ionic/react-router"; diff --git a/src/NavigateOnStateChange.tsx b/src/NavigateOnStateChange.tsx index 1c4315c..ed14632 100644 --- a/src/NavigateOnStateChange.tsx +++ b/src/NavigateOnStateChange.tsx @@ -1,5 +1,5 @@ import { useIonRouter } from "@ionic/react"; -import React, { useContext, useEffect } from "react"; +import React, { useEffect } from "react"; type NavigateOnStateChangeProps = { route?: string, onNavigate?: () => void} @@ -7,7 +7,7 @@ export const NavigateOnStateChange: React.FC = ({rou const ionRouter = useIonRouter(); console.log("NavigateOnStateChange", ionRouter); useEffect(() => { - if (route) { + if (route && ionRouter) { console.log("Route changed ", route); setTimeout(() => { ionRouter.push(route); @@ -16,6 +16,8 @@ export const NavigateOnStateChange: React.FC = ({rou onNavigate(); } } + /* This useEffect should ONLY run when route changes */ + // eslint-disable-next-line react-hooks/exhaustive-deps }, [route]); return <> } \ No newline at end of file