diff --git a/.github/workflows/_implement-me-check-formatting.md b/.github/workflows/_implement-me-check-formatting.md new file mode 100644 index 000000000..adb296368 --- /dev/null +++ b/.github/workflows/_implement-me-check-formatting.md @@ -0,0 +1,48 @@ +# Check formatting workflow + +This is a fully-ready GitHub Actions worflow for verifying whether a +new PR has been formatted with Prettier before the PR can go through. + +There are just some complications, explaining why it's an MD file right now: + +1. The moment the file gets added as a `.yml` file, it's going to make GitHub reject every single existing file in the codebase that has not been formatted yet. Unfortunately, not every iteration group has followed best practices, so there's a lot to clean up still. +2. This can't be added as a commented-out `.yml` file because GitHub will try to run every single `.yml` file in the `workflows` directory, even if the file has no code to run. If GitHub can't run anything, it's going to spam your emails until you change something. + +The `package.json` file has a `format:check`command and`format:fix`command, both of which use Prettier. I would recommend these steps for rolling this feature: + +1. Create a separate branch +2. Run `prettier:format`, which will run Prettier repo-wide and update any stray files. +3. Copy the below code into a file named something like `check-formatting.yml` +4. Commit the changes and create a new pull request. Make sure nothing has + been done other than running the formatting, so that in the off chance that something goes wrong, it'll be easy to roll things back. +5. Merge the PR +6. Start working on new features! + +With the file in place, you will need to make sure your code is formatted before being able to merge into main. Everybody has different preferences for how their code is set up, but with the large number of developers who've been involved with Swell, there need to be some ground rules. + +```yml +name: Validate Code Formatting + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Check Out Repo + uses: actions/checkout@v3 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + - name: Install NPM Modules + run: npm ci + - name: Check Prettier Formatting + run: npm run format:check +``` + diff --git a/.prettierrc b/.prettierrc index 942702aad..b5190b1e2 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,12 @@ { - "printWidth": 80, - "tabWidth": 2, - "singleQuote": true, - "arrowParens": "always" -} \ No newline at end of file + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "always", + "bracketSpacing": true, + "bracketSameLine": false +} + diff --git a/TRPCBodyEntryForm.tsx b/TRPCBodyEntryForm.tsx index 95bfd5c4b..27f6834ea 100644 --- a/TRPCBodyEntryForm.tsx +++ b/TRPCBodyEntryForm.tsx @@ -1,22 +1,15 @@ -import React, { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { RootState } from './src/client/toolkit-refactor/store'; -import TextCodeArea from './src/client/components/main/sharedComponents/TextCodeArea'; - /** - * renders entry form for TRPC request + * @file Renders entry form for an incoming tRPC request */ +import React from 'react'; +import { useAppSelector, useAppDispatch } from './src/client/rtk/store'; +import { newRequestBodySet } from './src/client/rtk/slices/newRequestSlice'; +import TextCodeArea from './src/client/components/main/sharedComponents/TextCodeArea'; -const TRPCBodyEntryForm = (props: any) => { - const { newRequestBodySet } = props; - const dispatch = useDispatch(); - const newRequestBody = useSelector( - (store: RootState) => store.newRequest.newRequestBody - ); - const { bodyContent } = newRequestBody; - - const isDark = useSelector((store: RootState) => store.ui.isDark); - const [cmValue, setValue] = useState(bodyContent); +const TRPCBodyEntryForm = () => { + const isDark = useAppSelector((store) => store.ui.isDark); + const newBody = useAppSelector((store) => store.newRequest.newRequestBody); + const dispatch = useAppDispatch(); return (
@@ -24,12 +17,12 @@ const TRPCBodyEntryForm = (props: any) => {
{ + value={newBody.bodyContent} + onChange={(bodyContent) => { dispatch( newRequestBodySet({ - ...newRequestBody, - bodyContent: value, + ...newBody, + bodyContent, bodyIsNew: true, }) ); diff --git a/main.js b/main.js index eaaf8f22e..d8095de54 100644 --- a/main.js +++ b/main.js @@ -1,3 +1,12 @@ +/** + * @file Defines the main entry point for the whole Electron app. + * + * @todo 2023-09-01 - Even if you don't convert this to a TS file, and just opt + * into the @ts-check comment, this file lights up with a lot of type errors. + * The code all seems to be working, but this will be one of the more involved + * conversions in the project. + */ + // https://github.com/electron/electron/issues/10257 // Code fix to support NODE_EXTRA_CA_CERTS env. There is currently no other fixes to the NODE_TLS_REJECT_UNAUTHORIZED at the moment. @@ -45,7 +54,6 @@ const url = require('url'); const fs = require('fs'); const log = require('electron-log'); - // proto-parser func for parsing .proto files const protoParserFunc = require('./main_process/protoParser.js'); @@ -65,10 +73,8 @@ require('./main_process/main_trpcController.js')(); // require mac touchbar const { touchBar } = require('./main_process/main_touchbar.js'); - - -const contextMenu = require('electron-context-menu') -contextMenu() +const contextMenu = require('electron-context-menu'); +contextMenu(); // configure logging // autoUpdater.logger = log; @@ -216,6 +222,11 @@ app.on('ready', () => { * this is crucial code, and while VS Code will flag it as not being used, it * should not be removed. The servers must be required upon app startup (especially in * packaged versions) or else the packaged app would not recognize the servers at all. + * + * 2023-09-01 - Decided not to update these paths to use custom aliases, + * because require isn't able to recognize them. You can't Ctrl+Click to + * jump to the source file definition. If this file ever gets converted to a + * .ts file, though, you should be able to update these import pathss */ const express = require('./src/server/server'); const mockServer = require('./src/server/mockServer.js'); diff --git a/package.json b/package.json index c9e8ec2b3..fd7fd74cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swell", - "version": "1.16.0", + "version": "1.16.1", "description": "Swell", "main": "main.js", "repository": "https://github.com/open-source-labs/Swell", @@ -18,7 +18,8 @@ "test-jest": "jest", "test-mocha": "webpack --mode=production --config ./webpack.production.js && cross-env process.env.NODE_ENV=test mocha --timeout 3000 --exit", "test-mocha-zero": "webpack --mode=production --config ./webpack.production.js && cross-env process.env.NODE_ENV=test mocha --timeout 0 --exit", - "format": "prettier --write \"**/*.+(js|jsx| tsx| json|css|md)\"", + "format:check": "prettier . --check --ignore-path .gitignore", + "format:fix": "prettier . --write --ignore-path .gitignore", "lint": "eslint .", "lint:fix": "eslint --fix . ", "dev": "concurrently --success first \"webpack-dev-server --mode=development --config ./webpack.development.js\" \"nodemon --legacy-watch ./src/server/server.js\"", @@ -232,7 +233,7 @@ "ts-node": "^10.9.1", "typescript": "^4.6.3", "url-loader": "^4.1.1", - "webpack": "^5.72.0", + "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^4.8.1", @@ -541,3 +542,4 @@ } ] } + diff --git a/src/client/components/App.tsx b/src/client/components/App.tsx index f613fb55a..5d29cee99 100644 --- a/src/client/components/App.tsx +++ b/src/client/components/App.tsx @@ -4,23 +4,22 @@ import Split from 'react-split'; import { HashRouter } from 'react-router-dom'; // Controllers -import historyController from '../controllers/historyController'; -import collectionsController from '../controllers/collectionsController'; +import historyController from '~/controllers/historyController'; +import collectionsController from '~/controllers/collectionsController'; // Local components -// import UpdatePopUpContainer from './legacy-components/UpdatePopUpContainer'; -import HistoryOrWorkspaceContainer from './workspace/HistoryOrWorkspaceContainer'; -import NavBarContainer from './navbar/NavBarContainer'; -import MainContainer from './main/MainContainer'; +import HistoryOrWorkspaceContainer from '~/components/workspace/HistoryOrWorkspaceContainer'; +import NavBarContainer from '~/components/navbar/NavBarContainer'; +import MainContainer from '~/components/main/MainContainer'; // Types -import { WindowExt } from '../../types'; +import { WindowExt } from '~/types'; // Error handling -import ErrorBoundary from './utilities/ErrorBoundary/ErrorBoundary'; +import ErrorBoundary from '~/components/utilities/ErrorBoundary/ErrorBoundary'; // Import styling -import '../../assets/style/App.scss'; +import '~/assets/style/App.scss'; const { api } = window as unknown as WindowExt; diff --git a/src/client/components/main/GRPC-composer/GRPCAutoInputForm.tsx b/src/client/components/main/GRPC-composer/GRPCAutoInputForm.tsx index d21fbb245..1b5deec26 100644 --- a/src/client/components/main/GRPC-composer/GRPCAutoInputForm.tsx +++ b/src/client/components/main/GRPC-composer/GRPCAutoInputForm.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react'; import GRPCBodyEntryForm from './GRPCBodyEntryForm'; import GRPCServiceOrRequestSelect from './GRPCServiceOrRequestSelect'; -import { $TSFixMe, NewRequestStreams } from '../../../../types'; +import { type $TSFixMe, type NewRequestStreams } from '~/types'; interface Props { newRequestStreams: { @@ -22,7 +22,6 @@ interface Props { } const GRPCAutoInputForm: React.FC = (props) => { - //component state for service and request dropdown const [serviceOption, setServiceOption] = useState('Select Service'); const [requestOption, setRequestOption] = useState('Select Request'); @@ -52,7 +51,7 @@ const GRPCAutoInputForm: React.FC = (props) => { let streamsArr = [props.newRequestStreams.streamsArr[0]]; let streamContent = ['']; setRequestOption('Select Request'); - + // the selected service name is saved in state of the store, mostly everything else is reset props.newRequestStreamsSet({ ...props.newRequestStreams, @@ -134,12 +133,12 @@ const GRPCAutoInputForm: React.FC = (props) => { } } //Deep copy streamsArr and streamCopy to reassign in store - const streamsArrCopy = structuredClone(streamsArr); - const streamContentCopy = structuredClone(streamContent); + const streamsArrCopy = structuredClone(streamsArr); + const streamContentCopy = structuredClone(streamContent); // push JSON formatted query in streamContent arr const queryJSON = JSON.stringify(results, null, 4); - + if (streamsArrCopy[0] !== '') { streamsArrCopy[0].query = queryJSON; } diff --git a/src/client/components/main/GRPC-composer/GRPCBodyEntryForm.tsx b/src/client/components/main/GRPC-composer/GRPCBodyEntryForm.tsx index b5a0d16ba..1932594f1 100644 --- a/src/client/components/main/GRPC-composer/GRPCBodyEntryForm.tsx +++ b/src/client/components/main/GRPC-composer/GRPCBodyEntryForm.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; +import { type $TSFixMe, type NewRequestStreams } from '~/types'; import GRPCBodyStream from './GRPCBodyStream'; -import { $TSFixMe, NewRequestStreams } from '../../../../types'; interface Props { newRequestStreams: NewRequestStreams; @@ -16,12 +16,14 @@ const GRPCBodyEntryForm: React.FC = (props) => { // add additional streams only for CLIENT or BIDIRECTIONAL streaming const addStream = (): void => { - const streamsArr = structuredClone(props.newRequestStreams.streamsArr); - const streamContent = structuredClone(props.newRequestStreams.streamContent); + const streamsArr = structuredClone(props.newRequestStreams.streamsArr); + const streamContent = structuredClone( + props.newRequestStreams.streamContent + ); // save query of initial stream body const firstBodyQuery: $TSFixMe = props.newRequestStreams.initialQuery; // construct new stream body obj & push into the streamsArr - const newStream: $TSFixMe = {}; + const newStream: $TSFixMe = {}; newStream.id = props.newRequestStreams.count; newStream.query = firstBodyQuery; streamsArr.push(newStream); @@ -37,7 +39,7 @@ const GRPCBodyEntryForm: React.FC = (props) => { }; // event handler that updates state in the store when typing into the stream query body - const onChangeUpdateStream = (streamID: $TSFixMe , value: $TSFixMe ) => { + const onChangeUpdateStream = (streamID: $TSFixMe, value: $TSFixMe) => { // props.saveChanges(false); const streamsArr = [...props.newRequestStreams.streamsArr]; const streamContent = [...props.newRequestStreams.streamContent]; @@ -55,21 +57,23 @@ const GRPCBodyEntryForm: React.FC = (props) => { }; // for each stream body in the streamArr, render the GRPCBodyStream component - const streamArr = props.newRequestStreams.streamsArr.map((stream: $TSFixMe , index: $TSFixMe ) => ( - - )); + const streamArr = props.newRequestStreams.streamsArr.map( + (stream: $TSFixMe, index: $TSFixMe) => ( + + ) + ); //if client stream or bidirectional, the add stream btn will be rendered below the stream bodies let addStreamBtn; diff --git a/src/client/components/main/GRPC-composer/GRPCComposer.tsx b/src/client/components/main/GRPC-composer/GRPCComposer.tsx index 9805b6c25..1b2ae1b91 100644 --- a/src/client/components/main/GRPC-composer/GRPCComposer.tsx +++ b/src/client/components/main/GRPC-composer/GRPCComposer.tsx @@ -1,22 +1,23 @@ import React, { useEffect, useState } from 'react'; import { v4 as uuid } from 'uuid'; + +import { type ReqRes } from '~/types'; +import { type ConnectRouterProps } from '~/components/main/MainContainer'; + // Import controllers -import historyController from '../../../controllers/historyController'; -// Import local components +import historyController from '~/controllers/historyController'; +// Import local components import GRPCTypeAndEndpointEntryForm from './GRPCTypeAndEndpointEntryForm'; import HeaderEntryForm from '../sharedComponents/requestForms/HeaderEntryForm'; import GRPCProtoEntryForm from './GRPCProtoEntryForm'; import NewRequestButton from '../sharedComponents/requestButtons/NewRequestButton'; import TestEntryForm from '../sharedComponents/requestForms/TestEntryForm'; -import { $TSFixMe, ReqRes } from '../../../../types.js'; - // Import MUI components import { Box } from '@mui/material'; -export default function GRPCComposer(props: $TSFixMe) { - +export default function GRPCComposer(props: ConnectRouterProps) { // destructure the props from mainContainer const { composerFieldsReset, @@ -66,13 +67,14 @@ export default function GRPCComposer(props: $TSFixMe) { setWorkspaceActiveTab, } = props; - // Initialize local state + // Initialize local state // used to set up boiler plate for StreamsArr // is is initially -> [] // but needs to be -> [{id:0, "query": ""}] - const [streamsArrLength, setStreamsArrayLength] = useState(newRequestStreams.streamsArr.length); - + const [streamsArrLength, setStreamsArrayLength] = useState( + newRequestStreams.streamsArr.length + ); const requestValidationCheck = () => { interface ValidationMessage { @@ -172,8 +174,8 @@ export default function GRPCComposer(props: $TSFixMe) { // triggers the reset for the boiler plate for streamsArr // if not triggered, composerFieldsReset() breaks feature // look at useEffect below - setStreamsArrayLength(0) - + setStreamsArrayLength(0); + // GRPC REQUESTS newRequestBodySet({ ...newRequestBody, @@ -189,10 +191,9 @@ export default function GRPCComposer(props: $TSFixMe) { setWorkspaceActiveTab('workspace'); }; - // need this to make sure + // need this to make sure useEffect(() => { if (streamsArrLength === 0) { - const newStreamsArr = [ { id: newRequestStreams.count, @@ -201,7 +202,7 @@ export default function GRPCComposer(props: $TSFixMe) { ]; // reset the state - setStreamsArrayLength(newStreamsArr.length) + setStreamsArrayLength(newStreamsArr.length); // update state in the store newRequestStreamsSet({ diff --git a/src/client/components/main/GRPC-composer/GRPCProtoEntryForm.tsx b/src/client/components/main/GRPC-composer/GRPCProtoEntryForm.tsx index 8cf6ea421..ba33578ed 100644 --- a/src/client/components/main/GRPC-composer/GRPCProtoEntryForm.tsx +++ b/src/client/components/main/GRPC-composer/GRPCProtoEntryForm.tsx @@ -1,14 +1,15 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useAppSelector } from '../../../rtk/store'; + +import { type NewRequestStreams, type $TSFixMe } from '~/types'; +import grpcController from '~/controllers/grpcController'; + import GRPCAutoInputForm from './GRPCAutoInputForm'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -import grpcController from '../../../controllers/grpcController' -import { NewRequestStreams, $TSFixMe } from '../../../../types'; -import { RootState } from '../../../toolkit-refactor/store'; interface GRPCProtoEntryFormProps { - newRequestStreams: NewRequestStreams - newRequestStreamsSet: $TSFixMe + newRequestStreams: NewRequestStreams; + newRequestStreamsSet: $TSFixMe; } const GRPCProtoEntryForm: React.FC = (props) => { @@ -17,7 +18,7 @@ const GRPCProtoEntryForm: React.FC = (props) => { // import proto file via electron file import dialog and have it displayed in proto textarea box const importProtos = () => { - grpcController.importProto(props.newRequestStreams) + grpcController.importProto(props.newRequestStreams); }; // saves protoContent in the store whenever client make changes to proto file or pastes a copy @@ -29,26 +30,24 @@ const GRPCProtoEntryForm: React.FC = (props) => { }); saveChanges(false); }; - + // update protoContent state in the store after making changes to the proto file const submitUpdatedProto = () => { //only update if changes aren't saved if (!changesSaved) { - try{ + try { grpcController.sendParserData(props.newRequestStreams.protoContent); grpcController.protoParserReturn(props.newRequestStreams); saveChanges(true); - } catch (err) { console.log(err); saveChanges(false); } } - return + return; }; - const isDark = useSelector((state: RootState) => state.ui.isDark); - + const isDark = useAppSelector((state) => state.ui.isDark); const saveChangesBtnText = changesSaved ? 'Changes Saved' : 'Save Changes'; /* diff --git a/src/client/components/main/GRPC-composer/GRPCServiceOrRequestSelect.tsx b/src/client/components/main/GRPC-composer/GRPCServiceOrRequestSelect.tsx index 0b588e563..6d037cb2f 100644 --- a/src/client/components/main/GRPC-composer/GRPCServiceOrRequestSelect.tsx +++ b/src/client/components/main/GRPC-composer/GRPCServiceOrRequestSelect.tsx @@ -1,62 +1,29 @@ -import React, { useState, useRef, useEffect } from 'react'; -import dropDownArrow from '../../../../assets/icons/caret-down.svg'; -import { $TSFixMe } from '../../../../types'; - - +import React from 'react'; +import dropDownArrow from '~/assets/icons/caret-down.svg'; +import useDropdownState from '~/hooks/useDropdownState'; interface Props { value: string; items: string[]; - onClick: (e: React.MouseEvent) => void; + onClick: (e: React.MouseEvent) => void; defaultTitle: string; id: string; } - -const GRPCServiceOrRequestSelect: React.FC = (props) => { - const { - value, - items, - onClick, - defaultTitle, - id - } = props; - - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(null); - - useEffect(() => { - const closeDropdown = (event: $TSFixMe) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - }; - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); - - const listItems: $TSFixMe[] = []; - items.forEach((itemStr, index) => { - if (value !== itemStr) { - listItems.push( - { - setDropdownIsActive(false); - onClick(e); - }} - key={`listItem${index}`} - className="dropdown-item" - > - {itemStr} - - ); - } - }); +const GRPCServiceOrRequestSelect: React.FC = ({ + value, + items, + onClick, + defaultTitle, + id, +}) => { + const { dropdownIsOpen, dropdownRef, toggleDropdown, closeDropdown } = + useDropdownState(); return (
+
diff --git a/src/client/components/main/GRPC-composer/GRPCTypeAndEndpointEntryForm.tsx b/src/client/components/main/GRPC-composer/GRPCTypeAndEndpointEntryForm.tsx index 63d1515b4..218fdf49f 100644 --- a/src/client/components/main/GRPC-composer/GRPCTypeAndEndpointEntryForm.tsx +++ b/src/client/components/main/GRPC-composer/GRPCTypeAndEndpointEntryForm.tsx @@ -1,26 +1,28 @@ /* eslint-disable default-case */ import React from 'react'; -import { useSelector } from 'react-redux'; -import { $TSFixMe, NewRequestFields, NewRequestStreams } from '../../../../types'; -import { RootState } from '../../../toolkit-refactor/store'; +import { useAppSelector } from '../../../rtk/store'; +import { + type $TSFixMe, + type NewRequestFields, + type NewRequestStreams, +} from '~/types'; interface Props { warningMessage: $TSFixMe; // This is a setWarningMessage: (warningMessage: $TSFixMe) => void; fieldsReplaced: (fields: NewRequestFields) => void; - newRequestFields: NewRequestFields ; + newRequestFields: NewRequestFields; newRequestStreams: NewRequestStreams; } const GRPCTypeAndEndpointEntryForm: React.FC = (props) => { - const { warningMessage, setWarningMessage, fieldsReplaced, newRequestFields, newRequestStreams, - } = props + } = props; const warningCheck = (): void => { if (warningMessage.uri) { @@ -44,7 +46,7 @@ const GRPCTypeAndEndpointEntryForm: React.FC = (props) => { // change this to be initial state instead const grpcStreamLabel = newRequestStreams.selectedStreamingType || 'STREAM'; - const isDark = useSelector((state: RootState) => state.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); return (
void; +type Props = { warningMessage: { body: string } | null; introspectionData: Record | null; -} - -const GraphQLBodyEntryForm: React.FC = (props) => { - const { - newRequestBody, - newRequestBody: { bodyContent, bodyIsNew }, - newRequestBodySet, - warningMessage, - introspectionData, - } = props; +}; - const [cmValue, setValue] = useState(bodyContent); +const GraphQLBodyEntryForm = ({ warningMessage, introspectionData }: Props) => { + const newBody = useAppSelector((store) => store.newRequest.newRequestBody); + const isDark = useAppSelector((store) => store.ui.isDark); + const dispatch = useAppDispatch(); - // set a new value for codemirror only if loading from history or changing query type + /** + * @todo useEffect should not be used for syncing different state values that + * all exist inside React. This useEffect call should be removed in favor of + * another approach (render keys, no state at all, etc.) + */ + const [codeMirrorValue, setCodeMirrorValue] = useState(newBody.bodyContent); useEffect(() => { - if (!bodyIsNew) setValue(bodyContent); - }, [bodyContent, bodyIsNew]); - - const isDark = useSelector((store: { ui: { isDark: boolean } }) => store.ui.isDark); + if (!newBody.bodyIsNew) { + setCodeMirrorValue(newBody.bodyContent); + } + }, [newBody]); return (
- { - // conditionally render warning message - warningMessage ?
{warningMessage.body}
: null - } + {warningMessage !== null &&
{warningMessage.body}
} +
Body
{ - newRequestBodySet({ - ...newRequestBody, - bodyContent: value, - bodyIsNew: true, - }); + value={codeMirrorValue} + onChange={(bodyContent) => { + dispatch( + newRequestBodySet({ + ...newBody, + bodyContent, + bodyIsNew: true, + }) + ); }} />
diff --git a/src/client/components/main/GraphQL-composer/GraphQLComposer.tsx b/src/client/components/main/GraphQL-composer/GraphQLComposer.tsx index 51a6cc062..209b7600a 100644 --- a/src/client/components/main/GraphQL-composer/GraphQLComposer.tsx +++ b/src/client/components/main/GraphQL-composer/GraphQLComposer.tsx @@ -1,10 +1,14 @@ import React from 'react'; import gql from 'graphql-tag'; import { v4 as uuid } from 'uuid'; + +import { type ReqRes } from '~/types'; +import { type ConnectRouterProps } from '../MainContainer'; + // Import controllers -import historyController from '../../../controllers/historyController'; -// Import local components +import historyController from '~/controllers/historyController'; +// Import local components import HeaderEntryForm from '../sharedComponents/requestForms/HeaderEntryForm'; import GraphQLMethodAndEndpointEntryForm from './GraphQLMethodAndEndpointEntryForm'; import CookieEntryForm from '../sharedComponents/requestForms/CookieEntryForm'; @@ -17,10 +21,9 @@ import TestContainer from '../sharedComponents/stressTest/TestContainer'; // Import MUI components import { Box } from '@mui/material'; -import { $TSFixMe, ReqRes } from '../../../../types'; // Translated from GraphQLContainer.jsx -export default function GraphQLComposer(props: $TSFixMe) { +export default function GraphQLComposer(props: ConnectRouterProps) { const { composerFieldsReset, fieldsReplaced, @@ -148,12 +151,8 @@ export default function GraphQLComposer(props: $TSFixMe) { protoPath, request: { method, - headers: headersArr.filter( - (header: $TSFixMe) => header.active && !!header.key - ), - cookies: cookiesArr.filter( - (cookie: $TSFixMe) => cookie.active && !!cookie.key - ), + headers: headersArr.filter((header) => header.active && !!header.key), + cookies: cookiesArr.filter((cookie) => cookie.active && !!cookie.key), body: bodyContent || '', bodyType, bodyVariables: bodyVariables || '', diff --git a/src/client/components/main/GraphQL-composer/GraphQLIntrospectionLog.tsx b/src/client/components/main/GraphQL-composer/GraphQLIntrospectionLog.tsx index 90347c439..7578f6c38 100644 --- a/src/client/components/main/GraphQL-composer/GraphQLIntrospectionLog.tsx +++ b/src/client/components/main/GraphQL-composer/GraphQLIntrospectionLog.tsx @@ -1,44 +1,38 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { GraphQLSchema } from 'graphql'; - -import graphQLController from '../../../controllers/graphQLController'; -import TextCodeArea from '../sharedComponents/TextCodeArea'; -import { RootState } from '../../../toolkit-refactor/store'; - -interface IntrospectionData { - schemaSDL: string | null; - clientSchema: GraphQLSchema | null; -} +import { useAppSelector } from '../../../rtk/store'; +import graphQLController from '~/controllers/graphQLController'; +import TextCodeArea from '~/components/main/sharedComponents/TextCodeArea'; const GraphQLIntrospectionLog: React.FC = () => { - const headers = useSelector( - (store: RootState) => store.newRequest.newRequestHeaders.headersArr - ); - const cookies = useSelector( - (store: RootState) => store.newRequest.newRequestCookies.cookiesArr - ); - const introspectionData: (IntrospectionData | string) = useSelector( - (store: RootState) => store.introspectionData - ); - const url: string = useSelector( - (store: RootState) => store.newRequestFields.url - ); - const isDark: boolean = useSelector((store: RootState) => store.ui.isDark); + const newReq = useAppSelector((store) => store.newRequest); + const url = useAppSelector((store) => store.newRequestFields.url); + const introspectionData = useAppSelector((store) => store.introspectionData); + const isDark = useAppSelector((store) => store.ui.isDark); + + const headers = newReq.newRequestHeaders.headersArr; + const cookies = newReq.newRequestCookies.cookiesArr; return (
- {introspectionData === 'Error: Please enter a valid GraphQL API URI' && ( + {/** + * @todo 2023-08-30 - Only formatted code; logic untouched. Need to + * figure out what this condition was trying to account for. + */} + {introspectionData === + 'Error: Please enter a valid GraphQL API URI' && (
{introspectionData}
)} - {!!introspectionData.schemaSDL && ( + + {introspectionData.schemaSDL !== null && ( { }; export default GraphQLIntrospectionLog; - - - - - - - - - - -// import React from 'react'; -// import { useSelector } from 'react-redux'; -// import graphQLController from '../../../controllers/graphQLController'; -// import TextCodeArea from '../new-request/TextCodeArea.tsx'; - -// const GraphQLIntrospectionLog = () => { -// const headers = useSelector( -// (store) => store.newRequest.newRequestHeaders.headersArr -// ); -// const cookies = useSelector( -// (store) => store.newRequest.newRequestCookies.cookiesArr -// ); -// const introspectionData = useSelector((store) => store.introspectionData); -// const url = useSelector((store) => store.newRequestFields.url); -// const isDark = useSelector((store) => store.ui.isDark); - -// return ( -//
-// -//
-// {introspectionData === -// 'Error: Please enter a valid GraphQL API URI' && ( -//
{introspectionData}
-// )} -// {!!introspectionData.schemaSDL && ( -// {}} -// readOnly={true} -// /> -// )} -//
-//
-// ); -// }; -// export default GraphQLIntrospectionLog; diff --git a/src/client/components/main/GraphQL-composer/GraphQLMethodAndEndpointEntryForm.tsx b/src/client/components/main/GraphQL-composer/GraphQLMethodAndEndpointEntryForm.tsx index a2b573691..cd09d8f15 100644 --- a/src/client/components/main/GraphQL-composer/GraphQLMethodAndEndpointEntryForm.tsx +++ b/src/client/components/main/GraphQL-composer/GraphQLMethodAndEndpointEntryForm.tsx @@ -1,9 +1,11 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import React from 'react'; import { GraphQLSchema } from 'graphql'; -import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; -import { NewRequestFields, NewRequestBody } from '../../../../types'; +import useDropdownState from '~/hooks/useDropdownState'; +import { useAppSelector } from '../../../rtk/store'; +import { type NewRequestFields, type NewRequestBody } from '~/types'; + +import dropDownArrow from '~/assets/icons/arrow_drop_down_white_192x192.png'; interface IntrospectionData { schemaSDL: string | null; @@ -27,21 +29,8 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ newRequestBodySet, newRequestBody, }) => { - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(null); - - useEffect(() => { - const closeDropdown = (event: MouseEvent) => { - if ( - dropdownEl.current && - !dropdownEl.current.contains(event.target as Node) - ) { - setDropdownIsActive(false); - } - }; - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); + const { dropdownIsOpen, dropdownRef, closeDropdown, toggleDropdown } = + useDropdownState(); const clearWarningIfApplicable = () => { if (warningMessage.uri) setWarningMessage({}); @@ -61,24 +50,25 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ const methodChangeHandler = (value: string) => { clearWarningIfApplicable(); - let methodReplaceRegexType: string | undefined + let methodReplaceRegexType: string | undefined; const methodReplaceRegex = new RegExp(`${newRequestFields.method}`, 'mi'); // GraphQL features if (value === 'QUERY' || 'MUTATION' || 'SUBSCRIPTION') { if (value === 'QUERY') { - methodReplaceRegexType = 'query' - } - else if (value === 'MUTATION') { - methodReplaceRegexType = 'mutation' - } - else if (value === 'SUBSCRIPTION') { - methodReplaceRegexType = 'subscription' + methodReplaceRegexType = 'query'; + } else if (value === 'MUTATION') { + methodReplaceRegexType = 'mutation'; + } else if (value === 'SUBSCRIPTION') { + methodReplaceRegexType = 'subscription'; } const newBody = methodReplaceRegex.test(newRequestBody.bodyContent) - ? newRequestBody.bodyContent.replace(methodReplaceRegex, methodReplaceRegexType) - : `${newRequestBody.bodyContent}`; + ? newRequestBody.bodyContent.replace( + methodReplaceRegex, + methodReplaceRegexType + ) + : `${newRequestBody.bodyContent}`; newRequestBodySet({ ...newRequestBody, @@ -94,15 +84,14 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ }); }; - - const isDark = useSelector((store: { ui: { isDark: boolean } }) => store.ui.isDark); + const isDark = useAppSelector((store) => store.ui.isDark); return (
@@ -112,7 +101,7 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ id="graphql-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => setDropdownIsActive(!dropdownIsActive)} + onClick={toggleDropdown} > {newRequestFields.method} @@ -131,7 +120,7 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ {newRequestFields.method !== 'QUERY' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('QUERY'); }} className="dropdown-item" @@ -142,7 +131,7 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ {newRequestFields.method !== 'MUTATION' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('MUTATION'); }} className="dropdown-item" @@ -153,7 +142,7 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ {newRequestFields.method !== 'SUBSCRIPTION' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('SUBSCRIPTION'); }} className="dropdown-item" @@ -186,4 +175,3 @@ const GraphQLMethodAndEndpointEntryForm: React.FC = ({ }; export default GraphQLMethodAndEndpointEntryForm; - diff --git a/src/client/components/main/GraphQL-composer/GraphQLVariableEntryForm.tsx b/src/client/components/main/GraphQL-composer/GraphQLVariableEntryForm.tsx index c17e1377c..4eed6371a 100644 --- a/src/client/components/main/GraphQL-composer/GraphQLVariableEntryForm.tsx +++ b/src/client/components/main/GraphQL-composer/GraphQLVariableEntryForm.tsx @@ -1,28 +1,23 @@ import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useAppSelector } from '../../../rtk/store'; +import { newRequestBodySet } from '../../../rtk/slices/newRequestSlice'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -interface GraphQLVariableEntryFormProps { - newRequestBody: { - bodyVariables: string; - bodyIsNew: boolean; - }; - newRequestBodySet: (arg: { [key: string]: any }) => void; -} +const GraphQLVariableEntryForm = () => { + const newBody = useAppSelector((store) => store.newRequest.newRequestBody); + const isDark = useAppSelector((store) => store.ui.isDark); -const GraphQLVariableEntryForm: React.FC = ({ - newRequestBody: { bodyVariables, bodyIsNew }, - newRequestBody, - newRequestBodySet, -}) => { - const [cmValue, setValue] = useState(bodyVariables); - - // set a new value for codemirror only if loading from history or changing gQL type + /** + * @todo useEffect should not be used for syncing different state values that + * all exist inside React. This useEffect call should be removed in favor of + * another approach (render keys, no state at all, etc.) + */ + const [codeMirrorValue, setCodeMirrorValue] = useState(newBody.bodyVariables); useEffect(() => { - if (!bodyIsNew) setValue(bodyVariables); - }, [bodyVariables, bodyIsNew]); - - const isDark = useSelector((store: any) => store.ui.isDark); + if (!newBody.bodyIsNew) { + setCodeMirrorValue(newBody.bodyVariables); + } + }, [newBody]); return (
@@ -30,14 +25,14 @@ const GraphQLVariableEntryForm: React.FC = ({
{ - setValue(value); + onChange={(bodyVariables: string) => { + setCodeMirrorValue(bodyVariables); newRequestBodySet({ - ...newRequestBody, - bodyVariables: value, + ...newBody, + bodyVariables, bodyIsNew: true, }); }} @@ -47,4 +42,4 @@ const GraphQLVariableEntryForm: React.FC = ({ ); }; -export default GraphQLVariableEntryForm; \ No newline at end of file +export default GraphQLVariableEntryForm; diff --git a/src/client/components/main/MainContainer.tsx b/src/client/components/main/MainContainer.tsx index 37a2d1efc..b5adbd29b 100644 --- a/src/client/components/main/MainContainer.tsx +++ b/src/client/components/main/MainContainer.tsx @@ -1,31 +1,61 @@ +/** + * @file This defines the top-level router used for navigating between different + * tabs in the application. + * + * @todo This file uses the old React-Redux connect API. The main component sets + * up subscriptions to the Redux store for every single state value and dispatch + * that every child component could ever need, rolls them up into a single + * props object, and then passes that one object down to every single component. + * + * This makes the code harder to maintain, and potentially makes the app's + * performance worse, because any one of the subscribed values could cause all + * children to re-render at any time, even if a child doesn't care about the + * value that changed. + * + * The connect API should be removed in favor of moving the state further down + * to their relevant components, where the subscriptions can be set up with + * useAppSelector and useAppDispatch. + */ import React from 'react'; import { Routes, Route } from 'react-router-dom'; -import { connect } from 'react-redux'; -import { ReqRes, $TSFixMe, $TSFixMeObject, RequestWebRTC } from '../../../types'; -import * as ReqResSlice from '../../toolkit-refactor/slices/reqResSlice'; +import { + type ReqRes, + type NewRequestStreams, + type NewRequestFields, + type NewRequestBody, + type RequestWebRTC, +} from '~/types'; + +import { type ConnectedProps, connect } from 'react-redux'; +import { AppDispatch, type RootState } from '../../rtk/store'; +import { reqResItemAdded } from '../../rtk/slices/reqResSlice'; +import { + NewRequestOpenApi, + openApiRequestsReplaced, +} from '../../rtk/slices/newRequestOpenApiSlice'; import { + type NewRequestState, composerFieldsReset, newRequestSSESet, newRequestCookiesSet, newRequestStreamsSet, newRequestBodySet, newRequestHeadersSet, -} from '../../toolkit-refactor/slices/newRequestSlice'; +} from '../../rtk/slices/newRequestSlice'; -import { openApiRequestsReplaced } from '../../toolkit-refactor/slices/newRequestOpenApiSlice'; - -import { - setWorkspaceActiveTab, - /*, setComposerDisplay */ -} from '../../toolkit-refactor/slices/uiSlice'; +import { setWorkspaceActiveTab } from '../../rtk/slices/uiSlice'; import { fieldsReplaced, newTestContentSet, -} from '../../toolkit-refactor/slices/newRequestFieldsSlice'; -import { setWarningMessage } from '../../toolkit-refactor/slices/warningMessageSlice'; +} from '../../rtk/slices/newRequestFieldsSlice'; + +import { + type WarningMessage, + setWarningMessage, +} from '../../rtk/slices/warningMessageSlice'; // Import local components. import Http2Composer from './http2-composer/Http2Composer'; @@ -41,18 +71,19 @@ import ResponsePaneContainer from './response-composer/ResponsePaneContainer'; // Import MUI components import { Box } from '@mui/material'; -import { AppDispatch, RootState } from '../../toolkit-refactor/store'; + import Split from 'react-split'; -/**@todo switch to hooks? */ +/**@todo Switch to hooks */ const mapStateToProps = (store: RootState) => { return { + currentTab: store.ui.workspaceActiveTab, reqResArray: store.reqRes.reqResArray, newRequestFields: store.newRequestFields, newRequestHeaders: store.newRequest.newRequestHeaders, newRequestStreams: store.newRequest.newRequestStreams, newRequestBody: store.newRequest.newRequestBody, - // newRequestOpenAPI: store.newRequestOpenApi, + newRequestOpenAPI: store.newRequestOpenApi, newRequestCookies: store.newRequest.newRequestCookies, newRequestSSE: store.newRequest.newRequestSSE, warningMessage: store.warningMessage, @@ -60,52 +91,91 @@ const mapStateToProps = (store: RootState) => { }; }; -/**@todo switch to hooks? */ +/**@todo Switch to hooks */ const mapDispatchToProps = (dispatch: AppDispatch) => ({ reqResItemAdded: (reqRes: ReqRes) => { - dispatch(ReqResSlice.reqResItemAdded(reqRes)); + dispatch(reqResItemAdded(reqRes)); }, - setWarningMessage: (message: $TSFixMe) => { + + setWarningMessage: (message: WarningMessage) => { dispatch(setWarningMessage(message)); }, - // setComposerDisplay: (composerDisplay) => { - // dispatch(setComposerDisplay(composerDisplay)); - // }, - newRequestHeadersSet: (requestHeadersObj: $TSFixMeObject) => { - dispatch(newRequestHeadersSet(requestHeadersObj)); + + newRequestHeadersSet: (headers: NewRequestState['newRequestHeaders']) => { + dispatch(newRequestHeadersSet(headers)); }, - newRequestStreamsSet: (requestStreamsObj: $TSFixMeObject) => { + + newRequestStreamsSet: (requestStreamsObj: NewRequestStreams) => { dispatch(newRequestStreamsSet(requestStreamsObj)); }, - fieldsReplaced: (requestFields: $TSFixMe) => { + + fieldsReplaced: (requestFields: NewRequestFields) => { dispatch(fieldsReplaced(requestFields)); }, - newRequestBodySet: (requestBodyObj: $TSFixMeObject) => { + + newRequestBodySet: (requestBodyObj: NewRequestBody) => { dispatch(newRequestBodySet(requestBodyObj)); }, - newTestContentSet: (testContent: $TSFixMe) => { + + newTestContentSet: (testContent: NewRequestFields['testContent']) => { dispatch(newTestContentSet(testContent)); }, - newRequestCookiesSet: (requestCookiesObj: $TSFixMeObject) => { + + newRequestCookiesSet: ( + requestCookiesObj: NewRequestState['newRequestCookies'] + ) => { dispatch(newRequestCookiesSet(requestCookiesObj)); }, - newRequestSSESet: (requestSSEBool: boolean) => { - dispatch(newRequestSSESet(requestSSEBool)); + + newRequestSSESet: (newValue: boolean) => { + dispatch(newRequestSSESet(newValue)); }, - openApiRequestsReplaced: (parsedDocument: $TSFixMe) => { + + openApiRequestsReplaced: (parsedDocument: NewRequestOpenApi) => { dispatch(openApiRequestsReplaced(parsedDocument)); }, + composerFieldsReset: () => { dispatch(composerFieldsReset()); }, - setWorkspaceActiveTab: (tabName: $TSFixMe) => { + + setWorkspaceActiveTab: (tabName: string) => { dispatch(setWorkspaceActiveTab(tabName)); }, - }); +const connector = connect(mapStateToProps, mapDispatchToProps); + +/** + * This defines the top-level props defined at the app's router root, which are + * then passed to basically every single component + * + * @todo 2023-08-31 - This should be treated as a temporary type. The goal + * should be to get all the components redesigned to receive values through + * the custom Redux hooks, and then delete this for good. + * + * Once all of the Redux values provided via connect has been "tamped down" to + * their respective components, you can do the following: + * 1. Delete the ConnectedProps part from the props + * 2. Delete connector, mapStateToProps, and mapDispatchToProps, and related + * imports + * 3. Remove the export from the type, so that it can go back to being an + * internal implementation detail. + * + * Please, PLEASE do not move this type into the global types file. That file + * needs to get smaller, not bigger. + * + * --- + * + * @todo part 2 - Because all of these components have been without any true + * type safety for so long, a lot of props are mismatched or missing. Need to + * figure out where the missing values should come from + */ +export type ConnectRouterProps = ConnectedProps & { + currentWorkspaceId: string; +}; -function MainContainer(props: $TSFixMeObject) { +function MainContainer(props: ConnectRouterProps) { return ( @@ -114,12 +184,15 @@ function MainContainer(props: $TSFixMeObject) { } /> } /> } /> - } /> {/* WebRTC has been completely refactored to hooks - no props needed */} } /> } /> } /> } /> + } + /> } @@ -132,5 +205,4 @@ function MainContainer(props: $TSFixMeObject) { ); } -export default connect(mapStateToProps, mapDispatchToProps)(MainContainer); - +export default connector(MainContainer); diff --git a/src/client/components/main/MockServer-composer/MockServerComposer.tsx b/src/client/components/main/MockServer-composer/MockServerComposer.tsx index b97134ff3..d9de46241 100644 --- a/src/client/components/main/MockServer-composer/MockServerComposer.tsx +++ b/src/client/components/main/MockServer-composer/MockServerComposer.tsx @@ -1,11 +1,10 @@ // react-redux import React, { useState, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { - startServer, - stopServer, -} from '../../../toolkit-refactor/slices/mockServerSlice'; -import { newRequestFieldsByProtocol } from '../../../toolkit-refactor/slices/newRequestFieldsSlice'; +import { type ConnectRouterProps } from '~/components/main/MainContainer'; + +import { useAppSelector, useAppDispatch } from '../../../rtk/store'; +import { startServer, stopServer } from '../../../rtk/slices/mockServerSlice'; +import { newRequestFieldsByProtocol } from '../../../rtk/slices/newRequestFieldsSlice'; // forms import RestMethodAndEndpointEntryForm from '../http2-composer/RestMethodAndEndpointEntryForm'; @@ -22,11 +21,15 @@ import { Box, Button, Modal, Typography } from '@mui/material'; */ const { api } = window as any; -// TODO: add typing to the props object -// TODO: add an option to see the list of existing routes that shows up in the response window -// TODO: add endpoint validation -// TODO: add the ability to mock HTML responses (or remove the HTML option from the BodyEntryForm component) -// TODO: hook up the headers and cookies to the mock endpoint creation +/** + * Formatted todo's from Chris Suzukida (2023-04-11) + * @todo Add an option to see the list of existing routes that shows up in the + * response window + * @todo Add endpoint validation + * @todo Add the ability to mock HTML responses (or remove the HTML option from + * the BodyEntryForm component) + * @todo Hook up the headers and cookies to the mock endpoint creation + */ // styling for the mui box modal component const style = { @@ -41,16 +44,18 @@ const style = { p: 4, }; -const MockServerComposer = (props) => { +const MockServerComposer = (props: ConnectRouterProps) => { const [showModal, setShowModal] = useState(false); const [userDefinedEndpoint, setUserDefinedEndpoint] = useState(''); - const dispatch = useDispatch(); - // grab the isServerStarted state from the Redux store - let isServerStarted = useSelector( - (state: any) => state.mockServer.isServerStarted + const requestFields = useAppSelector((store) => store.newRequestFields); + const reqBody = useAppSelector((store) => store.newRequest.newRequestBody); + const isServerStarted = useAppSelector( + (store) => store.mockServer.isServerStarted ); + const dispatch = useAppDispatch(); + useEffect(() => { dispatch(newRequestFieldsByProtocol('mock')); }, [dispatch]); @@ -69,7 +74,11 @@ const MockServerComposer = (props) => { // toggles the mock server on and off const handleServerButtonClick = () => { - isServerStarted ? stopMockServer() : startMockServer(); + if (isServerStarted) { + stopMockServer(); + } else { + startMockServer(); + } }; // triggers when the user clicks the submit button @@ -78,24 +87,22 @@ const MockServerComposer = (props) => { if (isServerStarted) { // check if endpoint starts with a forward slash const parsedUserDefinedEndpoint = - props.newRequestFields.restUrl[0] === '/' - ? props.newRequestFields.restUrl - : `/${props.newRequestFields.restUrl}`; + requestFields.restUrl[0] === '/' + ? requestFields.restUrl + : `/${requestFields.restUrl}`; // grab the method type from the RestMethodAndEndpointEntryForm component - const methodType = props.newRequestFields.method; + const methodType = requestFields.method; // check if the body is parsable JSON try { - JSON.parse(props.newRequestBody.bodyContent); + JSON.parse(reqBody.bodyContent); } catch (err) { alert('Please enter a JSON parsable body'); } // parse the response from the BodyEntryForm component because it is a stringified JSON object in props - const parsedCodeMirrorBodyContent = JSON.parse( - props.newRequestBody.bodyContent - ); + const parsedCodeMirrorBodyContent = JSON.parse(reqBody.bodyContent); // create an object that contains the method, endpoint, and response const postData = { @@ -154,7 +161,7 @@ const MockServerComposer = (props) => {
diff --git a/src/client/components/main/OpenAPI-composer/OpenAPIComposer.tsx b/src/client/components/main/OpenAPI-composer/OpenAPIComposer.tsx index cfd7298b8..e10dabee3 100644 --- a/src/client/components/main/OpenAPI-composer/OpenAPIComposer.tsx +++ b/src/client/components/main/OpenAPI-composer/OpenAPIComposer.tsx @@ -1,9 +1,12 @@ -import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useState } from 'react'; +import { useAppSelector } from '../../../rtk/store'; + +import { type $TSFixMe, type ReqRes } from '~/types'; +import { type ConnectRouterProps } from '~/components/main/MainContainer'; + import { v4 as uuid } from 'uuid'; -import { RootState } from '../../../toolkit-refactor/store'; // Import controllers -import historyController from '../../../controllers/historyController'; +import historyController from '~/controllers/historyController'; // Import local components import NewRequestButton from '../sharedComponents/requestButtons/NewRequestButton'; @@ -14,13 +17,10 @@ import OpenAPIServerForm from './OpenAPIServerForm'; // Import MUI components import { Box } from '@mui/material'; -import { $TSFixMe, ReqRes } from '../../../../types'; -export default function OpenAPIComposer(props: $TSFixMe) { +export default function OpenAPIComposer(props: ConnectRouterProps) { + const newRequestsOpenAPI = useAppSelector((store) => store.newRequestOpenApi); - // This is a better way to import what the components needs, not the mess of prop drilling - const newRequestsOpenAPI: $TSFixMe = useSelector((state: RootState) => state.newRequestOpenApi); - const { composerFieldsReset, openApiRequestsReplaced, @@ -53,7 +53,9 @@ export default function OpenAPIComposer(props: $TSFixMe) { // We are only ever sending a request to one server, this one. // you can toggle which is the primary server in the serverEntryForm - const [primaryServer, setPrimaryServer] = useState(newRequestsOpenAPI?.openapiMetadata?.serverUrls[0] || '') + const [primaryServer, setPrimaryServer] = useState( + newRequestsOpenAPI.openApiMetadata.serverUrls[0] || '' + ); const requestValidationCheck = () => { const validationMessage = {}; @@ -68,12 +70,12 @@ export default function OpenAPIComposer(props: $TSFixMe) { return; } - newRequestsOpenAPI.openapiReqArray.forEach((req: $TSFixMe) => { + newRequestsOpenAPI.openApiReqArray.forEach((req: $TSFixMe) => { const reqRes: ReqRes = { id: uuid(), createdAt: new Date(), host: `${primaryServer}`, - protocol: "http://", + protocol: 'http://', url: `${primaryServer}${req.endpoint}`, graphQL, gRPC, @@ -85,7 +87,9 @@ export default function OpenAPIComposer(props: $TSFixMe) { checkSelected: false, request: { method: req.method, - headers: headersArr.filter((header: $TSFixMe) => header.active && !!header.key), + headers: headersArr.filter( + (header: $TSFixMe) => header.active && !!header.key + ), body: req.body, bodyType, rawType, @@ -106,7 +110,7 @@ export default function OpenAPIComposer(props: $TSFixMe) { minimized: false, tab: currentTab, }; - console.log('Open_API_COmposer-> reqRes',reqRes) + console.log('Open_API_COmposer-> reqRes', reqRes); // add request to history /** @todo Fix TS type error */ historyController.addHistoryToIndexedDb(reqRes); @@ -153,10 +157,8 @@ export default function OpenAPIComposer(props: $TSFixMe) { warningMessage={warningMessage} /> - - + + { - const isDark = useSelector((state: RootState) => state.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); const importDoc = (): void => { openApiController.sendDocument(); openApiController.importDocument(); - } + }; return (
diff --git a/src/client/components/main/OpenAPI-composer/OpenAPIEntryForm.tsx b/src/client/components/main/OpenAPI-composer/OpenAPIEntryForm.tsx index e5a5d2be2..8a7439968 100644 --- a/src/client/components/main/OpenAPI-composer/OpenAPIEntryForm.tsx +++ b/src/client/components/main/OpenAPI-composer/OpenAPIEntryForm.tsx @@ -1,37 +1,42 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../toolkit-refactor/store'; -import { $TSFixMe } from '../../../../types'; +import { useAppSelector } from '../../../rtk/store'; +import { type $TSFixMe } from '~/types'; interface Props { warningMessage: { uri?: string; }; - newRequestsOpenAPI: $TSFixMe - primaryServer: string + newRequestsOpenAPI: $TSFixMe; + primaryServer: string; } // This component is working as intended, thouhg needs to have the TS tightened up const OpenAPIEntryForm: React.FC = ({ warningMessage, newRequestsOpenAPI, - primaryServer + primaryServer, }) => { // This loads the input field at the top of the page - const isDark = useSelector((state: RootState) => state.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); return ( -
+
{return 'testTest'}} + onChange={() => { + return 'testTest'; + }} /> {warningMessage.uri && (
{warningMessage.uri}
diff --git a/src/client/components/main/OpenAPI-composer/OpenAPIMetadata.tsx b/src/client/components/main/OpenAPI-composer/OpenAPIMetadata.tsx index 0ce8228dc..205ebeb8b 100644 --- a/src/client/components/main/OpenAPI-composer/OpenAPIMetadata.tsx +++ b/src/client/components/main/OpenAPI-composer/OpenAPIMetadata.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { $TSFixMe } from '../../../../types'; +import { type $TSFixMe } from '~/types'; interface Props { newRequestsOpenAPI: $TSFixMe; @@ -8,16 +8,17 @@ interface Props { // This component is working as intended, but need the TS tightened up const OpenAPIMetaData: React.FC = (props: Props) => { + const { newRequestsOpenAPI } = props; - const { newRequestsOpenAPI } = props - - const conditionalChecker = (key: string) => { + const conditionalChecker = (key: string) => { if (newRequestsOpenAPI.openapiMetadata) { - return newRequestsOpenAPI.openapiMetadata.info[key] ? newRequestsOpenAPI.openapiMetadata.info[key] : ''; + return newRequestsOpenAPI.openapiMetadata.info[key] + ? newRequestsOpenAPI.openapiMetadata.info[key] + : ''; } return ''; - } - + }; + return (
@@ -46,6 +47,6 @@ const OpenAPIMetaData: React.FC = (props: Props) => {
); -} +}; export default OpenAPIMetaData; diff --git a/src/client/components/main/OpenAPI-composer/OpenAPIServerForm.tsx b/src/client/components/main/OpenAPI-composer/OpenAPIServerForm.tsx index db9285d32..a570f80b1 100644 --- a/src/client/components/main/OpenAPI-composer/OpenAPIServerForm.tsx +++ b/src/client/components/main/OpenAPI-composer/OpenAPIServerForm.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, Dispatch, SetStateAction} from 'react'; -import { useSelector } from 'react-redux'; +import React, { useState, useEffect } from 'react'; +import { useAppSelector } from '../../../rtk/store'; +import { type $TSFixMe, type OpenAPIRequest } from '~/types'; + import ContentReqRowComposer from '../sharedComponents/requestForms/ContentReqRowComposer'; -import { $TSFixMe, OpenAPIRequest } from '../../../../types'; -import { RootState } from '../../../toolkit-refactor/store'; interface Props { newRequestsOpenAPI: OpenAPIRequest; @@ -12,50 +12,48 @@ interface Props { const OpenAPIServerForm: React.FC = ({ newRequestsOpenAPI, - openApiRequestsReplaced, - setPrimaryServer + openApiRequestsReplaced, + setPrimaryServer, }) => { - // This holds onto state for the ContentReqRowComposer - const [contentDataArr, setContentDataArr]: $TSFixMe = useState([]) - const [trueIndex, setTrueIndex] = useState(0) + const [contentDataArr, setContentDataArr]: $TSFixMe = useState([]); + const [trueIndex, setTrueIndex] = useState(0); // populate the contentDataArr upon upload of yml/json openApi file useEffect(() => { if (newRequestsOpenAPI?.openapiMetadata?.serverUrls) { - setPrimaryServer(newRequestsOpenAPI.openapiMetadata.serverUrls[0]) - const serverUrls: string[] = newRequestsOpenAPI.openapiMetadata.serverUrls - // + setPrimaryServer(newRequestsOpenAPI.openapiMetadata.serverUrls[0]); + const serverUrls: string[] = + newRequestsOpenAPI.openapiMetadata.serverUrls; + // // setToggleUrlsArr((oldArr: string[]) => [...oldArr, serverUrls]) const updatedServers = serverUrls.map((server: string, index: number) => { - let checked: undefined | boolean - index == 0 ? checked = true : checked = false + let checked: undefined | boolean; + index == 0 ? (checked = true) : (checked = false); return { id: Math.floor(Math.random() * 1000000), active: checked, key: `Server ${index + 1}`, value: server, - } - }) + }; + }); setContentDataArr(updatedServers || []); // setUpdateRef(false) } - }, [newRequestsOpenAPI]) - + }, [newRequestsOpenAPI]); // Responsible for adding servers to the OpenAPI request const addServer = (url: string = '', i?: number) => { - const newOpenApi = structuredClone(newRequestsOpenAPI); if (newOpenApi?.openapiMetadata?.serverUrls) { if (url === '') { const index = newOpenApi.openapiMetadata.serverUrls.length; - newOpenApi.openapiMetadata.serverUrls.push({index: url}); + newOpenApi.openapiMetadata.serverUrls.push({ index: url }); - openApiRequestsReplaced({...newOpenApi}) + openApiRequestsReplaced({ ...newOpenApi }); } else if (i != undefined) { - newOpenApi.openapiMetadata.serverUrls[i] = {i: url}; + newOpenApi.openapiMetadata.serverUrls[i] = { i: url }; } } }; @@ -64,70 +62,67 @@ const OpenAPIServerForm: React.FC = ({ const deleteServer = (index: number) => { const newOpenApi = structuredClone(newRequestsOpenAPI); - newOpenApi?.openapiMetadata?.serverUrls.splice(index, 1) + newOpenApi?.openapiMetadata?.serverUrls.splice(index, 1); if (newOpenApi?.openapiMetadata?.serverUrls) { - openApiRequestsReplaced({...newOpenApi}) + openApiRequestsReplaced({ ...newOpenApi }); } }; // Responsible for changing/updating the input fields of the servers const onChangeUpdateHeader = ( - id: string, - field: 'key' | 'value' | 'active', + id: string, + field: 'key' | 'value' | 'active', value: boolean | string | number - ) => { - - // make a copy to update the state upon change - const updatedContentDataArr = [ ...contentDataArr ]; + ) => { + // make a copy to update the state upon change + const updatedContentDataArr = [...contentDataArr]; // find server to update (update in component state) for (let i = 0; i < contentDataArr.length; i += 1) { if (contentDataArr[i].id === id) { - if (updatedContentDataArr[i].active = true) { - setTrueIndex(i) + if ((updatedContentDataArr[i].active = true)) { + setTrueIndex(i); } // check or uncheck the box if (field === 'active') { - updatedContentDataArr[i].active = value + updatedContentDataArr[i].active = value; if (updatedContentDataArr[i].active == true) { - updatedContentDataArr[trueIndex].active = false - setPrimaryServer(contentDataArr[i].value) - setTrueIndex(i) + updatedContentDataArr[trueIndex].active = false; + setPrimaryServer(contentDataArr[i].value); + setTrueIndex(i); } } if (field === 'key') { - updatedContentDataArr[i].key = value + updatedContentDataArr[i].key = value; } if (field === 'value') { - updatedContentDataArr[i].value = value - addServer(updatedContentDataArr[i].value, i) + updatedContentDataArr[i].value = value; + addServer(updatedContentDataArr[i].value, i); if (updatedContentDataArr[i].active == true) { - setPrimaryServer(contentDataArr[i].value) + setPrimaryServer(contentDataArr[i].value); } } - setContentDataArr(updatedContentDataArr) + setContentDataArr(updatedContentDataArr); } } }; -// TODO Handle error when contentDataArr is empty - const serversArr = contentDataArr.map( - (server: string, index: number) => { - return ( - - ); - } - ); - - const isDark = useSelector((state: RootState) => state.ui.isDark); + // TODO Handle error when contentDataArr is empty + const serversArr = contentDataArr.map((server: string, index: number) => { + return ( + + ); + }); + + const isDark = useAppSelector((state) => state.ui.isDark); return (
diff --git a/src/client/components/main/TRPC-composer/TRPCComposer.tsx b/src/client/components/main/TRPC-composer/TRPCComposer.tsx index 99816c56d..5fcd126dd 100644 --- a/src/client/components/main/TRPC-composer/TRPCComposer.tsx +++ b/src/client/components/main/TRPC-composer/TRPCComposer.tsx @@ -1,93 +1,116 @@ import React, { useReducer, useState } from 'react'; import { v4 as uuid } from 'uuid'; -// Import controllers + +import { useAppDispatch, useAppSelector } from '../../../rtk/store'; +import { responseDataSaved } from '../../../rtk/slices/reqResSlice'; + +import { type ConnectRouterProps } from '~/components/main/MainContainer'; + import SendRequestButton from '../sharedComponents/requestButtons/SendRequestButton'; -// Import local components +import CookieEntryForm from '../sharedComponents/requestForms/CookieEntryForm'; + +import TRPCProceduresContainer from './TRPCProceduresContainer'; +import TRPCSubscriptionContainer from './TRPCSubscriptionContainer'; import TRPCMethodAndEndpointEntryForm from './TRPCMethodAndEndpointEntryForm'; -// Import Redux hooks -import { useSelector, useDispatch } from 'react-redux'; -// Import Actions from RTK slice -import historyController from '../../../controllers/historyController'; -import connectionController from '../../../controllers/reqResController'; +import historyController from '~/controllers/historyController'; +import connectionController from '~/controllers/reqResController'; -// Import MUI components import { Box } from '@mui/material'; -import { RootState } from '../../../toolkit-refactor/store'; import HeaderEntryForm from '../sharedComponents/requestForms/HeaderEntryForm'; -import CookieEntryForm from '../sharedComponents/requestForms/CookieEntryForm'; -import TRPCProceduresContainer from './TRPCProceduresContainer'; - -import TRPCSubscriptionContainer from './TRPCSubscriptionContainer'; -import { responseDataSaved } from '../../../toolkit-refactor/slices/reqResSlice'; /** + * @todo 2023-08-30 - Decided to refactor a useReducer implementation for + * clarity. Redux doesn't make useReducer unnecessary – it can be great for + * handling complex state that only belongs to one component. * + * The problem is that the whole implementation was left half-finished in the + * component itself, so I don't know where this was meant to go. + * + * Logic-wise, nothing has changed. Just need to figure out if this is even + * necessary, or if the Redux reducer can fulfill a similar purpose. It's + * possible that the procedure should be using types defined elsewhere, too */ +type Procedure = Readonly<{ + // Because the default procedure is initialized to the value QUERY, I don't + // know if the method is meant to be any arbitrary string, or if it should be + // a union of discrete values (e.g., QUERY | TYPE_A | TYPE_B) + method: string; + endpoint: string; + variable: string; +}>; -const PROCEDURE_DEFAULT = { - //the default format for whenever a new prcedure get add - method: 'QUERY', // the type of method either query or mutate - endpoint: '', // endpoint for this specific procedure - variable: '', // argument that is to be attach to this procedure +const defaultProcedure: Procedure = { + method: 'QUERY', + endpoint: '', + variable: '', }; -//************** reducer function******************** */ -// this function is used to manage the main state of this component which is an array of procedures [{method,endpoint,variable}] -// it will take in the old state (array of procedures) as well as an action object where the type will dictate which action will get trigger -// The action object wil also contain the index as well as the new value for the procedure that is being manipulated (all inside of action.payload) -// after its done manipulating the state it will return the updated array that will be use as the new procedures state +const initialProcedures: readonly Procedure[] = [defaultProcedure]; + +/** + * @todo 2023-08-31 - Hate that I have to leak out an implementation detail + * type, but it was the fastest way of making some child components type-safe. + * + * This whole set of tRPC components should be redesigned so that the children + * don't receive a direct dispatch, and don't have to be aware that this + * component is using useReducer under the hood. + * + * Please do not move this to types.ts. Once the tRPC components have been + * redesigned, this type should stop being exported, so it can go back to just + * being an implementation detail. + */ +export type ProcedureAction = + | { + type: 'procedureUpdated'; + payload: { + procedureIndex: number; + key: keyof Procedure; + newValue: string; + }; + } + | { type: 'procedureAdded'; payload: { newProcedure: Procedure } } + | { type: 'procedureDeleted'; payload: { procedureIndex: number } }; + +function reduceProcedures( + procedures: readonly Procedure[], + action: ProcedureAction +): readonly Procedure[] { + switch (action.type) { + case 'procedureUpdated': { + const { newValue, key, procedureIndex } = action.payload; -//********************************** */ + return procedures.map((procedure, i) => { + if (i !== procedureIndex) return procedure; + return { ...procedure, [key]: newValue }; + }); + } -function reducer(procedures, action) { - if (action.type === 'METHOD') { - // - const proceduresCopy = [...procedures]; - const procedure = proceduresCopy[action.payload.index]; - const newState = { - ...procedure, - method: action.payload.value, - }; - proceduresCopy[action.payload.index] = newState; - return proceduresCopy; - } else if (action.type === 'ENDPOINT') { - const proceduresCopy = [...procedures]; - const procedure = proceduresCopy[action.payload.index]; - const newState = { - ...procedure, - endpoint: action.payload.value, - }; - proceduresCopy[action.payload.index] = newState; - return proceduresCopy; - } else if (action.type === 'VARIABLE') { - const proceduresCopy = [...procedures]; - const procedure = proceduresCopy[action.payload.index]; - const newState = { - ...procedure, - variable: action.payload.value, - }; - proceduresCopy[action.payload.index] = newState; - return proceduresCopy; - } else if (action.type === 'ADD') { - const proceduresCopy = [...procedures]; - proceduresCopy.push(PROCEDURE_DEFAULT); - return proceduresCopy; - } else if (action.type === 'DELETE') { - const proceduresCopy = [...procedures]; - proceduresCopy.splice(action.payload.index, 1); - return proceduresCopy; + case 'procedureAdded': { + return [...procedures, action.payload.newProcedure]; + } + + case 'procedureDeleted': { + const { procedureIndex } = action.payload; + return procedures.filter((_, i) => i !== procedureIndex); + } + + default: { + const invalidAction: never = action; + throw new Error( + `Received unknown action ${JSON.stringify(invalidAction)}` + ); + } } - return procedures; } -export default function TRPCComposer(props) { - const dispatch = useDispatch(); // dispatch for main redux store - - const [showSubscription, setShowSubscription] = useState(false); // manage subscription component - const subscriptionHandler = (bool) => { - setShowSubscription(bool); - }; +export default function TRPCComposer(props: ConnectRouterProps) { + const reduxDispatch = useAppDispatch(); + const [showSubscription, setShowSubscription] = useState(false); + const [procedures, proceduresDispatch] = useReducer( + reduceProcedures, + initialProcedures + ); const requestValidationCheck = () => { interface ValidationMessage { @@ -107,11 +130,6 @@ export default function TRPCComposer(props) { return validationMessage; }; - const [procedures, proceduresDipatch] = useReducer(reducer, [ - //userReducer hook to manage the main state (the array of procedures) - PROCEDURE_DEFAULT, - ]); - const { //grabbing all neccessary information to build a new reqRes object from the main redux store through props drilling newRequestFields, @@ -142,17 +160,14 @@ export default function TRPCComposer(props) { reqResItemAdded, } = props; - /** newRequestFields slice from redux store, contains general request info*/ - const requestFields = useSelector( - (state: RootState) => state.newRequestFields - ); - - /** reqRes slice from redux store, contains request and response data */ - const newRequest = useSelector((state: RootState) => state.newRequest); + const requestFields = useAppSelector((store) => store.newRequestFields); + const newRequest = useAppSelector((store) => store.newRequest); const addProcedures = () => { - // reducer dispatch for adding a new procedure to the procedures array - proceduresDipatch({ type: 'ADD' }); + /** + * @todo Where did they expect the new procedure values to come from? + */ + proceduresDispatch({ type: 'procedureAdded' }); }; const sendRequest = async () => { @@ -209,7 +224,7 @@ export default function TRPCComposer(props) { reqResItemAdded(reqRes); // saving the reqRes object to the array of reqRes managed by the redux store - dispatch(responseDataSaved(reqRes)); + reduxDispatch(responseDataSaved(reqRes)); connectionController.openReqRes(reqRes.id); // passing the reqRes object to the connectionController inside of reqRes controller }; @@ -243,7 +258,7 @@ export default function TRPCComposer(props) { />
diff --git a/src/client/components/main/TRPC-composer/TRPCMethodAndEndpointEntryForm.tsx b/src/client/components/main/TRPC-composer/TRPCMethodAndEndpointEntryForm.tsx index 47405b4b7..922af628f 100644 --- a/src/client/components/main/TRPC-composer/TRPCMethodAndEndpointEntryForm.tsx +++ b/src/client/components/main/TRPC-composer/TRPCMethodAndEndpointEntryForm.tsx @@ -1,17 +1,14 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState, useRef, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; - -import { RootState } from '../../../toolkit-refactor/store'; -import { fieldsReplaced } from '../../../toolkit-refactor/slices/newRequestFieldsSlice'; +import React from 'react'; +import { useAppSelector, useAppDispatch } from '../../../rtk/store'; +import { fieldsReplaced } from '../../../rtk/slices/newRequestFieldsSlice'; const TRPCMethodAndEndpointEntryForm = (props) => { - const requestFields = useSelector( - (state: RootState) => state.newRequestFields - ); - const dispatch = useDispatch(); + const requestFields = useAppSelector((state) => state.newRequestFields); + const dispatch = useAppDispatch(); + const clearWarningIfApplicable = () => { if (props.warningMessage.uri) props.setWarningMessage({}); }; diff --git a/src/client/components/main/TRPC-composer/TRPCPrceduresEndPoint.tsx b/src/client/components/main/TRPC-composer/TRPCPrceduresEndPoint.tsx index ef2d1bab0..467533f6a 100644 --- a/src/client/components/main/TRPC-composer/TRPCPrceduresEndPoint.tsx +++ b/src/client/components/main/TRPC-composer/TRPCPrceduresEndPoint.tsx @@ -1,53 +1,57 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import React, { useState, useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import React, { type ChangeEvent } from 'react'; +import { useAppSelector } from '../../../rtk/store'; +import useDropdownState from '~/hooks/useDropdownState'; +import dropDownArrow from '~/assets/icons/arrow_drop_down_white_192x192.png'; +import { type ProcedureAction } from './TRPCComposer'; -import dropDownArrow from './../../../../assets/icons/arrow_drop_down_white_192x192.png'; - -import { RootState } from '../../../toolkit-refactor/store'; - -const TRPCPrceduresEndPoint = (props) => { - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(); +type Props = { + index: number; + procedureData: { method: string; endpoint: string }; + proceduresDispatch: React.Dispatch; +}; - useEffect(() => { - const closeDropdown = (event: MouseEvent) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - }; - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); +const TRPCProceduresEndPoint = ({ + procedureData, + index, + proceduresDispatch, +}: Props) => { + const isDark = useAppSelector((state) => state.ui.isDark); + const { dropdownIsOpen, dropdownRef, toggleDropdown, closeDropdown } = + useDropdownState(); - /// these functions exists to dispatch function to the reducer function inside of main trpc composer file. - const methodHandler = (method) => { - props.proceduresDipatch({ - type: 'METHOD', - payload: { index: props.index, value: method }, + const methodHandler = (method: 'QUERY' | 'MUTATE') => { + proceduresDispatch({ + type: 'procedureUpdated', + payload: { procedureIndex: index, key: 'method', newValue: method }, }); }; - const onChangeHandler = (e) => { - props.proceduresDipatch({ - type: 'ENDPOINT', - payload: { index: props.index, value: e.target.value }, + + const onChangeHandler = (e: ChangeEvent) => { + proceduresDispatch({ + type: 'procedureUpdated', + payload: { + procedureIndex: index, + key: 'endpoint', + newValue: e.target.value, + }, }); }; - const onDeleteHandler = (e) => { - props.proceduresDipatch({ - type: 'DELETE', - payload: { index: props.index }, + + const onDeleteHandler = () => { + proceduresDispatch({ + type: 'procedureDeleted', + payload: { procedureIndex: index }, }); }; - const isDark = useSelector((store: RootState) => store.ui.isDark); return (
@@ -57,9 +61,9 @@ const TRPCPrceduresEndPoint = (props) => { id="graphql-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => setDropdownIsActive(!dropdownIsActive)} + onClick={toggleDropdown} > - {props.procedureData.method} + {procedureData.method} { @@ -105,7 +110,7 @@ const TRPCPrceduresEndPoint = (props) => { type="text" id="url-input" placeholder="Enter endpoint" - value={props.procedureData.endpoint} + value={procedureData.endpoint} onChange={onChangeHandler} /> @@ -116,5 +121,5 @@ const TRPCPrceduresEndPoint = (props) => { ); }; -export default TRPCPrceduresEndPoint; +export default TRPCProceduresEndPoint; diff --git a/src/client/components/main/TRPC-composer/TRPCSubscriptionContainer.tsx b/src/client/components/main/TRPC-composer/TRPCSubscriptionContainer.tsx index 4b2b0df6b..5c9ec2dee 100644 --- a/src/client/components/main/TRPC-composer/TRPCSubscriptionContainer.tsx +++ b/src/client/components/main/TRPC-composer/TRPCSubscriptionContainer.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; -const { api } = window; +import React, { useRef, useState } from 'react'; // import tRPC client Module import { + type TRPCUntypedClient, createTRPCProxyClient, httpBatchLink, createWSClient, @@ -12,24 +12,51 @@ import { import SendRequestButton from '../sharedComponents/requestButtons/SendRequestButton'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -export default function TRPCSubscriptionContainer(props) { +type Props = { + onClose: () => void; +}; + +const h3Styles = { + display: 'block', + fontSize: '1.17em', + fontWeight: 'bold', +}; + +/** + * @todo 2023-08-31 - This component needs a mutable container for storing any + * unsubscribe functions generated by the tRPC client. + * + * But because the client is so jacked up, there's no way to get the exact type + * easily. I had to make a hack by shoving an "any" into the type parameter for + * tRPC's untyped client class instance type. + * + * Very bad, very evil, very cursed. If the client ever gets fixed up, this type + * should be cleaned up, too. + */ +type UnsubFixMe = ReturnType['subscription']>; + +export default function TRPCSubscriptionContainer({ onClose }: Props) { + /** + * @todo 2023-08-31 - I have no idea what the type of endPoint is supposed to + * be. It's defined here as a string, but because the tRPC client is jacked + * up, when you use the endpoint to index the client value, TypeScript thinks + * endpoint should be a number. + * + * Impossible to know the intended type without fixing the router setup first. + * Fix this type when convenient. + */ const [endPoint, setEndpoint] = useState(''); - const [link, setLink] = useState(''); + const [url, setUrl] = useState(''); const [responseBody, setResponseBody] = useState(''); const [subscriptionStarted, setsubscriptionStarted] = useState(false); - const endpointHandler = (e) => { - setEndpoint(e.target.value); - }; - const urlChangeHandler = (e) => { - setLink(e.target.value); - }; - let sub; - const startSubscription = async () => { + + const trpcUnsubscribeRef = useRef(null); + + const startSubscription = () => { setsubscriptionStarted(true); + try { - const wsClient = createWSClient({ - url: link, - }); + const wsClient = createWSClient({ url }); const client = createTRPCProxyClient({ links: [ @@ -46,9 +73,8 @@ export default function TRPCSubscriptionContainer(props) { }), ], }); - const endpoint = endPoint; - sub = client[endPoint].subscribe(undefined, { + trpcUnsubscribeRef.current = client[endPoint].subscribe(undefined, { onData: (message) => { setResponseBody((pre) => { if (pre === 'subscription started') { @@ -65,9 +91,13 @@ export default function TRPCSubscriptionContainer(props) { setResponseBody(JSON.stringify(e)); } }; + const endSubscription = () => { - sub?.unsubscribe(); setsubscriptionStarted(false); + if (trpcUnsubscribeRef.current !== null) { + trpcUnsubscribeRef.current.unsubscribe(); + trpcUnsubscribeRef.current = null; + } }; return ( @@ -84,18 +114,11 @@ export default function TRPCSubscriptionContainer(props) { className="ml-1 input input-is-medium is-info" type="text" placeholder="Enter your WS url" - value={link} - onChange={(e) => { - urlChangeHandler(e); - }} + value={url} + onChange={(e) => setUrl(e.target.value)} />
-
{ - props.subscriptionHandler(false); - }} - /> +
{ - endpointHandler(e); - }} + onChange={(e) => setEndpoint(e.target.value)} /> {subscriptionStarted && (
@@ -133,9 +154,3 @@ export default function TRPCSubscriptionContainer(props) { ); } -const h3Styles = { - display: 'block', - fontSize: '1.17em', - fontWeight: 'bold', -}; - diff --git a/src/client/components/main/TRPC-composer/TRPCVariableForm.tsx b/src/client/components/main/TRPC-composer/TRPCVariableForm.tsx index 7a5988c96..d8241185d 100644 --- a/src/client/components/main/TRPC-composer/TRPCVariableForm.tsx +++ b/src/client/components/main/TRPC-composer/TRPCVariableForm.tsx @@ -1,11 +1,11 @@ import React from 'react'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -import { useSelector } from 'react-redux'; -import { vscodeDark } from '@uiw/codemirror-theme-vscode'; +import { useAppSelector } from '../../../rtk/store'; export default function TRPCVariableForm(props) { // input for for user to attach argument with their procedures - const isDark = useSelector((store: any) => store.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); + const onChangeHandler = (string) => { // this function dispatch action to the main reducer function inside of trpc composer props.proceduresDipatch({ diff --git a/src/client/components/main/WebHook-composer/WebhookComposer.tsx b/src/client/components/main/WebHook-composer/WebhookComposer.tsx index 7e6ed8f0f..98f273443 100644 --- a/src/client/components/main/WebHook-composer/WebhookComposer.tsx +++ b/src/client/components/main/WebHook-composer/WebhookComposer.tsx @@ -1,14 +1,13 @@ import React, { useState, useEffect, FC } from 'react'; -import { useSelector } from 'react-redux'; import { v4 as uuid } from 'uuid'; import { io } from 'socket.io-client'; // Import MUI components import { Box } from '@mui/material'; -import { $TSFixMe } from '../../../../types'; +import { $TSFixMe } from '~/types'; +import { type ConnectRouterProps } from '~/components/main/MainContainer'; - -export default function WebhookComposer(props: $TSFixMe) { +export default function WebhookComposer(props: ConnectRouterProps) { /** * @todo There was a previous todo with the text "A relic of the past... it * must be purged." We're 99% sure this refers to the isDark variable, rather @@ -70,10 +69,10 @@ export default function WebhookComposer(props: $TSFixMe) { // This stops the polling of the server. // You get tons of errors in the browser console // without this code block - let socket: $TSFixMe + let socket: $TSFixMe; if (serverStatus) { socket = io('http://localhost:3000'); - } + } const copyClick = () => { console.log('copying'); @@ -131,7 +130,6 @@ export default function WebhookComposer(props: $TSFixMe) { }, []); const startServerButton = () => { - if (!serverStatus) { updateServerStatus(true); diff --git a/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx b/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx index 6b4a22445..7087e1698 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCComposer.tsx @@ -1,7 +1,12 @@ import React, { useState } from 'react'; -import { v4 as uuid } from 'uuid'; -import { ReqRes, RequestWebRTC, MainContainerProps } from '../../../../types'; +import { useAppDispatch, useAppSelector } from '../../../rtk/store'; +import { composerFieldsReset } from '../../../rtk/slices/newRequestSlice'; +import { setWorkspaceActiveTab } from '../../../rtk/slices/uiSlice'; +import { reqResItemAdded } from '../../../rtk/slices/reqResSlice'; + +import { v4 as uuid } from 'uuid'; +import { type ReqRes, type RequestWebRTC } from '~/types'; /** * @todo Refactor all of the below components to use MUI, place them in a new @@ -13,17 +18,11 @@ import NewRequestButton from '../sharedComponents/requestButtons/NewRequestButto // Import MUI components import { Box } from '@mui/material'; import WebRTCVideoBox from './WebRTCVideoBox'; -import { useSelector } from 'react-redux'; -import { RootState } from '../../../toolkit-refactor/store'; -import { useDispatch } from 'react-redux'; -import { composerFieldsReset } from '../../../toolkit-refactor/slices/newRequestSlice'; -import { setWorkspaceActiveTab } from '../../../toolkit-refactor/slices/uiSlice'; -import { reqResItemAdded } from '../../../toolkit-refactor/slices/reqResSlice'; export default function WebRTCComposer() { - const dispatch = useDispatch(); - const newRequestWebRTC: RequestWebRTC = useSelector( - (store: RootState) => store.newRequest.newRequestWebRTC + const dispatch = useAppDispatch(); + const newRequestWebRTC = useAppSelector( + (store) => store.newRequest.newRequestWebRTC ); const [showRTCEntryForms, setShowRTCEntryForms] = useState(false); @@ -41,7 +40,7 @@ export default function WebRTCComposer() { checkSelected: false, request: newRequestWebRTC, response: { - webRTCMessages: [] + webRTCMessages: [], }, checked: false, minimized: false, @@ -57,14 +56,19 @@ export default function WebRTCComposer() { ) return true; } catch { - return false + return false; } return false; - } + }; const addNewRequest = (): void => { - if (!(checkValidSDP(newRequestWebRTC.webRTCOffer) && checkValidSDP(newRequestWebRTC.webRTCAnswer))){ - return alert('Invalid offer or answer SDP') + if ( + !( + checkValidSDP(newRequestWebRTC.webRTCOffer) && + checkValidSDP(newRequestWebRTC.webRTCAnswer) + ) + ) { + return alert('Invalid offer or answer SDP'); } const reqRes: ReqRes = composeReqRes(); diff --git a/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx b/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx index eefafa6a0..011f3ba1a 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCServerEntryForm.tsx @@ -1,32 +1,15 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -// import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; -// import CodeMirror from '@uiw/react-codemirror'; -// import { EditorView } from '@codemirror/view'; -// import { javascript } from '@codemirror/lang-javascript'; -// import { vscodeDark } from '@uiw/codemirror-theme-vscode'; -// import Select from '@mui/material/Select'; -// import MenuItem from '@mui/material/MenuItem'; -import { RequestWebRTC } from '../../../../types'; -import TextCodeArea from '../sharedComponents/TextCodeArea'; -import { useDispatch } from 'react-redux'; -import { newRequestWebRTCSet } from '../../../toolkit-refactor/slices/newRequestSlice'; -import webrtcPeerController from '../../../controllers/webrtcPeerController'; -import { RootState } from '../../../toolkit-refactor/store'; +import { useAppDispatch, useAppSelector } from '../../../rtk/store'; -// const jBeautify = require('js-beautify').js; +import { newRequestWebRTCSet } from '../../../rtk/slices/newRequestSlice'; +import webrtcPeerController from '~/controllers/webrtcPeerController'; -interface Props { - // newRequestWebRTC: RequestWebRTC; - // warningMessage: { - // body: string; - // } | null; -} +import TextCodeArea from '../sharedComponents/TextCodeArea'; -const WebRTCServerEntryForm: React.FC = (props: Props) => { - const dispatch = useDispatch(); - const newRequestWebRTC: RequestWebRTC = useSelector( - (store: RootState) => store.newRequest.newRequestWebRTC +const WebRTCServerEntryForm: React.FC = () => { + const dispatch = useAppDispatch(); + const newRequestWebRTC = useAppSelector( + (store) => store.newRequest.newRequestWebRTC ); return ( diff --git a/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx b/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx index 3da72d19b..ccc60e569 100644 --- a/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx +++ b/src/client/components/main/WebRTC-composer/WebRTCSessionEntryForm.tsx @@ -1,33 +1,40 @@ -import React, { useState } from 'react'; -import { NewRequestWebRTCSet, RequestWebRTC } from '../../../../types'; -import webrtcPeerController from '../../../controllers/webrtcPeerController'; +import React from 'react'; +import { useAppDispatch, useAppSelector } from '../../../rtk/store'; +import { newRequestWebRTCSet } from '../../../rtk/slices/newRequestSlice'; +import useDropdownState from '~/hooks/useDropdownState'; -import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; -import { useDispatch, useSelector } from 'react-redux'; -import { newRequestWebRTCSet } from '../../../toolkit-refactor/slices/newRequestSlice'; -import { RootState } from '../../../toolkit-refactor/store'; +import { type NewRequestWebRTCSet, type RequestWebRTC } from '~/types'; +import webrtcPeerController from '~/controllers/webrtcPeerController'; +import dropDownArrow from '~/assets/icons/arrow_drop_down_white_192x192.png'; interface Props { - setShowRTCEntryForms: (val: boolean) => any; + setShowRTCEntryForms: (val: boolean) => void; } -const WebRTCSessionEntryForm: React.FC = (props: Props) => { - const dispatch = useDispatch(); - const newRequestWebRTC: RequestWebRTC = useSelector( - (store: RootState) => store.newRequest.newRequestWebRTC +const WebRTCSessionEntryForm: React.FC = ({ setShowRTCEntryForms }) => { + const dispatch = useAppDispatch(); + const newRequestWebRTC = useAppSelector( + (store) => store.newRequest.newRequestWebRTC ); - const { setShowRTCEntryForms } = props; - const [entryTypeDropdownIsActive, setEntryTypeDropdownIsActive] = - useState(false); - const [dataTypeDropdownIsActive, setDataTypeDropdownIsActive] = - useState(false); + /** + * @todo 2023-08-31 - This seems like a huge code smell. I think that one of + * the later iterators on the project tried building the dropdowns from + * scratch, without realizing that there was already code in place in a few + * other places for doing that. + * + * Ended up using the useDropdownState hook to clean up the implementations, + * but several properties from the hook still aren't being used, even though + * they may still need to be. + */ + const entryDropdownState = useDropdownState(); + const dataDropdownState = useDropdownState(); return (
@@ -37,9 +44,7 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { id="rest-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => - setEntryTypeDropdownIsActive(!entryTypeDropdownIsActive) - } + onClick={entryDropdownState.toggleDropdown} > {newRequestWebRTC.webRTCEntryMode} @@ -58,11 +63,13 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { {newRequestWebRTC.webRTCEntryMode !== 'Manual' && ( { - dispatch(newRequestWebRTCSet({ - ...newRequestWebRTC, - webRTCEntryMode: 'Manual', - })); - setEntryTypeDropdownIsActive(false); + entryDropdownState.closeDropdown(); + dispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCEntryMode: 'Manual', + }) + ); }} className="dropdown-item" > @@ -73,11 +80,13 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { {newRequestWebRTC.webRTCEntryMode !== 'WS' && ( { - dispatch(newRequestWebRTCSet({ - ...newRequestWebRTC, - webRTCEntryMode: 'WS', - })); - setEntryTypeDropdownIsActive(false); + entryDropdownState.closeDropdown(); + dispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCEntryMode: 'WS', + }) + ); }} className="dropdown-item" > @@ -86,6 +95,7 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { )}
+ = (props: Props) => { disabled={newRequestWebRTC.webRTCEntryMode === 'Manual'} />
+
@@ -109,9 +120,7 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { id="input-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => { - setDataTypeDropdownIsActive(!dataTypeDropdownIsActive); - }} + onClick={dataDropdownState.toggleDropdown} > {newRequestWebRTC.webRTCDataChannel} @@ -144,7 +153,7 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { webRTCDataChannel: 'Audio', }); setShowRTCEntryForms(false); - setDataTypeDropdownIsActive(false); + dataDropdownState.closeDropdown(); }} className="dropdown-item" > @@ -161,7 +170,7 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { } as RequestWebRTC) ); setShowRTCEntryForms(false); - setDataTypeDropdownIsActive(false); + dataDropdownState.closeDropdown(); }} className="dropdown-item" > @@ -171,12 +180,14 @@ const WebRTCSessionEntryForm: React.FC = (props: Props) => { {newRequestWebRTC.webRTCDataChannel !== 'Text' && ( { - dispatch(newRequestWebRTCSet({ - ...newRequestWebRTC, - webRTCDataChannel: 'Text', - } as RequestWebRTC)); + dispatch( + newRequestWebRTCSet({ + ...newRequestWebRTC, + webRTCDataChannel: 'Text', + } as RequestWebRTC) + ); setShowRTCEntryForms(false); - setDataTypeDropdownIsActive(false); + dataDropdownState.closeDropdown(); }} className="dropdown-item" > diff --git a/src/client/components/main/WebSocket-composer/WSEndpointEntryForm.tsx b/src/client/components/main/WebSocket-composer/WSEndpointEntryForm.tsx index aa077a512..e7f8f0da4 100644 --- a/src/client/components/main/WebSocket-composer/WSEndpointEntryForm.tsx +++ b/src/client/components/main/WebSocket-composer/WSEndpointEntryForm.tsx @@ -1,10 +1,11 @@ - import React from 'react'; -import { useSelector } from 'react-redux'; +import { useAppSelector } from '../../../rtk/store'; interface Props { warningMessage: Record; - setWarningMessage: React.Dispatch>>; + setWarningMessage: React.Dispatch< + React.SetStateAction> + >; fieldsReplaced: (newFields: Record) => void; newRequestFields: Record; } @@ -33,7 +34,7 @@ const WSEndpointEntryForm: React.FC = ({ }); }; - const isDark = useSelector((store: any) => store.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); return (
{ - return { - newRequestHeaders: store.newRequest.newRequestHeaders, - newRequestBody: store.newRequest.newRequestBody, - }; -}; - -/**@todo switch to use hooks? */ -const mapDispatchToProps = (dispatch) => ({ - newRequestHeadersSet: (requestHeadersObj) => { - dispatch(newRequestHeadersSet(requestHeadersObj)); - }, - newRequestBodySet: (requestBodyObj) => { - dispatch(newRequestBodySet(requestBodyObj)); - }, -}); +function BodyTypeSelect() { + const dispatch = useAppDispatch(); + const newRequestBody = useAppSelector( + (store) => store.newRequest.newRequestBody + ); -function BodyTypeSelect({ - newRequestHeaders, - newRequestBody, - newRequestHeadersSet, - newRequestBodySet, -}) { const handleBodyTypeSelect = (event: SelectChangeEvent) => { - newRequestBodySet({ - ...newRequestBody, - bodyType: event.target.value, - }); + dispatch( + newRequestBodySet({ + ...newRequestBody, + bodyType: event.target.value, + }) + ); }; const handleRawBodyTypeSelect = (event: SelectChangeEvent) => { - newRequestBodySet({ - ...newRequestBody, - rawType: event.target.value, - }); + dispatch( + newRequestBodySet({ + ...newRequestBody, + rawType: event.target.value, + }) + ); }; const prettifyJSON = () => { @@ -133,4 +113,4 @@ function BodyTypeSelect({ ); } -export default connect(mapStateToProps, mapDispatchToProps)(BodyTypeSelect); +export default BodyTypeSelect; diff --git a/src/client/components/main/http2-composer/Http2Body.tsx b/src/client/components/main/http2-composer/Http2Body.tsx index 9833e8da5..9b48c2492 100644 --- a/src/client/components/main/http2-composer/Http2Body.tsx +++ b/src/client/components/main/http2-composer/Http2Body.tsx @@ -1,68 +1,46 @@ import React from 'react'; -// Http2Body needs access to the Redux store. -import { connect } from 'react-redux'; +import { useAppDispatch, useAppSelector } from '../../../rtk/store'; +import { newRequestBodySet } from '../../../rtk/slices/newRequestSlice'; -import { AppDispatch, RootState } from '../../../toolkit-refactor/store'; -import { $TSFixMeObject } from '../../../../types'; - -import { - newRequestBodySet, - newRequestHeadersSet, -} from '../../../toolkit-refactor/slices/newRequestSlice'; +import { type NewRequestBody } from '~/types'; // Import local components import BodyTypeSelect from './BodyTypeSelect'; + // Import MUI components import { Box } from '@mui/material'; import WWWForm from '../sharedComponents/requestForms/WWWForm'; import JSONTextArea from '../sharedComponents/JSONTextArea'; import TextCodeArea from '../sharedComponents/TextCodeArea'; -/**@todo switch to hooks? */ -const mapStateToProps = (store: RootState) => { - return { - newRequestHeaders: store.newRequest.newRequestHeaders, - newRequestBody: store.newRequest.newRequestBody, - warningMessage: store.warningMessage, - }; -}; +function Http2Body() { + const dispatch = useAppDispatch(); + const newRequestBody = useAppSelector( + (store) => store.newRequest.newRequestBody + ); -/**@todo switch to hooks? */ -const mapDispatchToProps = (dispatch: AppDispatch) => ({ - newRequestHeadersSet: (requestHeadersObj: $TSFixMeObject) => { - dispatch(newRequestHeadersSet(requestHeadersObj)); - }, - newRequestBodySet: (requestBodyObj: $TSFixMeObject) => { - dispatch(newRequestBodySet(requestBodyObj)); - }, -}); + const setBody = (newBod: NewRequestBody) => { + dispatch(newRequestBodySet(newBod)); + }; -function Http2Body({ - newRequestHeaders, - newRequestBody, - warningMessage, - newRequestHeadersSet, - newRequestBodySet, -}) { - const bodyEntryArea = () => { + const bodyEntryPicker = () => { //BodyType of none : display nothing if (newRequestBody.bodyType === 'none') { - return; + return null; } + //BodyType of XWWW... : display WWWForm entry if (newRequestBody.bodyType === 'x-www-form-urlencoded') { return ( - + ); } + //RawType of application/json : Text area box with error checking if (newRequestBody.rawType === 'application/json') { return ( ); @@ -81,17 +59,13 @@ function Http2Body({ /> ); }; + return ( - + - {bodyEntryArea()} + {bodyEntryPicker()} ); } -export default connect(mapStateToProps, mapDispatchToProps)(Http2Body); +export default Http2Body; diff --git a/src/client/components/main/http2-composer/Http2Composer.tsx b/src/client/components/main/http2-composer/Http2Composer.tsx index 2db0a7683..31454ef0a 100644 --- a/src/client/components/main/http2-composer/Http2Composer.tsx +++ b/src/client/components/main/http2-composer/Http2Composer.tsx @@ -1,16 +1,23 @@ import React from 'react'; import { v4 as uuid } from 'uuid'; -import { useDispatch } from 'react-redux'; -import { responseDataSaved } from '../../../toolkit-refactor/slices/reqResSlice'; +import { useAppDispatch } from '../../../rtk/store'; +import { responseDataSaved } from '../../../rtk/slices/reqResSlice'; import { setResponsePaneActiveTab, setSidebarActiveTab, -} from '../../../toolkit-refactor/slices/uiSlice'; +} from '../../../rtk/slices/uiSlice'; + +import { type ConnectRouterProps } from '~/components/main/MainContainer'; +import { + type CookieOrHeader, + type ReqRes, + type ValidationMessage, +} from '~/types'; // Import controllers -import connectionController from '../../../controllers/reqResController'; -import historyController from '../../../controllers/historyController'; +import connectionController from '~/controllers/reqResController'; +import historyController from '~/controllers/historyController'; import RestMethodAndEndpointEntryForm from './RestMethodAndEndpointEntryForm'; import HeaderEntryForm from '../sharedComponents/requestForms/HeaderEntryForm'; @@ -19,20 +26,19 @@ import SendRequestButton from '../sharedComponents/requestButtons/SendRequestBut import NewRequestButton from '../sharedComponents/requestButtons/NewRequestButton'; import BodyEntryForm from '../sharedComponents/requestForms/BodyEntryForm'; import TestEntryForm from '../sharedComponents/requestForms/TestEntryForm'; +import TestContainer from '../sharedComponents/stressTest/TestContainer'; + // Import MUI components import { Box, FormControlLabel, Switch } from '@mui/material'; -import { CookieOrHeader, ReqRes, MainContainerProps, ValidationMessage } from '../../../../types'; -import TestContainer from '../sharedComponents/stressTest/TestContainer'; -import Store from '../../../toolkit-refactor/store'; import { type } from 'os'; // Translated from RestContainer.jsx -export default function Http2Composer(props: MainContainerProps) { - const dispatch = useDispatch(); +export default function Http2Composer(props: ConnectRouterProps) { + const dispatch = useAppDispatch(); + // Destructuring store props. const { - // currentTab, newRequestFields, newRequestHeaders, newRequestBody, @@ -40,6 +46,7 @@ export default function Http2Composer(props: MainContainerProps) { newRequestStreams, newRequestSSE, warningMessage, + currentTab, } = props; const { @@ -159,7 +166,7 @@ export default function Http2Composer(props: MainContainerProps) { }, checked: false, minimized: false, - // tab: currentTab, + tab: currentTab, }; }; @@ -168,7 +175,7 @@ export default function Http2Composer(props: MainContainerProps) { saves the reqRes information, clears current inputs, and finally dispatches the reqRes object. */ - const sendNewRequest = () : void => { + const sendNewRequest = (): void => { // checks to see if input URL is empty or only contains http. If that is the case, return. const warnings = requestValidationCheck(); if (Object.keys(warnings).length > 0) { @@ -211,7 +218,7 @@ export default function Http2Composer(props: MainContainerProps) { // add request to history historyController.addHistoryToIndexedDb(reqRes); - + // dispatches reqRes object reqResItemAdded(reqRes); diff --git a/src/client/components/main/http2-composer/Http2EndpointForm.tsx b/src/client/components/main/http2-composer/Http2EndpointForm.tsx index 023c58800..963652ef1 100644 --- a/src/client/components/main/http2-composer/Http2EndpointForm.tsx +++ b/src/client/components/main/http2-composer/Http2EndpointForm.tsx @@ -1,8 +1,22 @@ import React from 'react'; + // Import MUI components -import { Box, Button, MenuItem, FormControl, Select, SelectChangeEvent, TextField } from '@mui/material' +import { + Box, + Button, + MenuItem, + FormControl, + Select, + SelectChangeEvent, + TextField, +} from '@mui/material'; -export default function Http2EndpointForm({ http2Method, setHttp2Method, http2Uri, setHttp2Uri }) { +export default function Http2EndpointForm({ + http2Method, + setHttp2Method, + http2Uri, + setHttp2Uri, +}) { // const [method, setMethod] = React.useState('get'); const handleMethodSelect = (event: SelectChangeEvent) => { setHttp2Method(event.target.value as string); @@ -11,15 +25,16 @@ export default function Http2EndpointForm({ http2Method, setHttp2Method, http2Ur const handleUriInput = (event: React.ChangeEvent) => { setHttp2Uri(event.target.value as string); }; - return( - @@ -38,17 +53,14 @@ export default function Http2EndpointForm({ http2Method, setHttp2Method, http2Ur - - ) + ); } diff --git a/src/client/components/main/http2-composer/Http2MetaData.tsx b/src/client/components/main/http2-composer/Http2MetaData.tsx index 87d0aecf4..7fe541843 100644 --- a/src/client/components/main/http2-composer/Http2MetaData.tsx +++ b/src/client/components/main/http2-composer/Http2MetaData.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; + // Import local components import KeyValueTable from './KeyValueTable'; import Http2Body from './Http2Body'; + // Import MUI components import { Box, Tabs, Tab } from '@mui/material'; diff --git a/src/client/components/main/http2-composer/KeyValueForm.tsx b/src/client/components/main/http2-composer/KeyValueForm.tsx index 003ab1770..e7604cf96 100644 --- a/src/client/components/main/http2-composer/KeyValueForm.tsx +++ b/src/client/components/main/http2-composer/KeyValueForm.tsx @@ -1,35 +1,36 @@ import React, { useState } from 'react'; + // Import MUI components import { Box, Button, TextField, Checkbox } from '@mui/material'; import ClearRoundedIcon from '@mui/icons-material/ClearRounded'; export default function KeyValueForm({ type, index, state, setState }) { - const [key, setKey] = useState(state[index].key) - const [value, setValue] = useState(state[index].value) + const [key, setKey] = useState(state[index].key); + const [value, setValue] = useState(state[index].value); const handleKeyInput = (event: React.ChangeEvent) => { state[index].key = event.target.value; - setKey(state[index].key) + setKey(state[index].key); setState([...state]); - } + }; const handleValueInput = (event: React.ChangeEvent) => { state[index].value = event.target.value; - setValue(state[index].value) + setValue(state[index].value); setState([...state]); - } + }; const handleDelete = () => { - state.splice(index, 1) + state.splice(index, 1); setState([...state]); - } + }; const handleToggle = () => { state[index].toggle = !state[index].toggle; setState([...state]); - } + }; - return( + return ( + color="success" + /> + sx={{ width: '25%', pr: 1 }} + /> + sx={{ width: '75%', pr: 1 }} + /> - ) + ); } diff --git a/src/client/components/main/http2-composer/KeyValueTable.tsx b/src/client/components/main/http2-composer/KeyValueTable.tsx index 735487e9a..2820b1b5f 100644 --- a/src/client/components/main/http2-composer/KeyValueTable.tsx +++ b/src/client/components/main/http2-composer/KeyValueTable.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { v4 as uuid } from 'uuid'; + // Import local components import KeyValueForm from './KeyValueForm'; + // Import MUI components import { Box, Button } from '@mui/material'; export default function KeyValueTable({ type, state, setState }) { - console.log(`${type} state`, state) + console.log(`${type} state`, state); const keyValueForms = []; for (let i = 0; i < state.length; i += 1) { @@ -22,11 +24,11 @@ export default function KeyValueTable({ type, state, setState }) { } const handleAddNewForm = () => { - state.push({ id: uuid(), key: '', value: '', toggle: false }) - setState([...state]) - } + state.push({ id: uuid(), key: '', value: '', toggle: false }); + setState([...state]); + }; - return( + return ( {keyValueForms} - - ) + ); } diff --git a/src/client/components/main/http2-composer/RestMethodAndEndpointEntryForm.jsx b/src/client/components/main/http2-composer/RestMethodAndEndpointEntryForm.jsx index e7753d908..a0f81dd90 100644 --- a/src/client/components/main/http2-composer/RestMethodAndEndpointEntryForm.jsx +++ b/src/client/components/main/http2-composer/RestMethodAndEndpointEntryForm.jsx @@ -1,6 +1,7 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import dropDownArrow from '../../../../assets/icons/arrow_drop_down_white_192x192.png'; +import React from 'react'; +import { useAppSelector } from '../../../rtk/store'; +import useDropdownState from '~/hooks/useDropdownState'; +import dropDownArrow from '~/assets/icons/arrow_drop_down_white_192x192.png'; const RestMethodAndEndpointEntryForm = ({ warningMessage, @@ -10,22 +11,12 @@ const RestMethodAndEndpointEntryForm = ({ newRequestBodySet, newRequestBody, newTestContentSet, - placeholder='Enter URL or paste text here', + placeholder = 'Enter URL or paste text here', value, }) => { - const isDark = useSelector((state) => state.ui.isDark); - const [dropdownIsActive, setDropdownIsActive] = useState(false); - const dropdownEl = useRef(); - - useEffect(() => { - const closeDropdown = (event) => { - if (!dropdownEl.current.contains(event.target)) { - setDropdownIsActive(false); - } - }; - document.addEventListener('click', closeDropdown); - return () => document.removeEventListener('click', closeDropdown); - }, []); + const isDark = useAppSelector((state) => state.ui.isDark); + const { dropdownIsOpen, dropdownRef, toggleDropdown, closeDropdown } = + useDropdownState(); const clearWarningIfApplicable = () => { if (warningMessage.uri) setWarningMessage({}); @@ -63,9 +54,9 @@ const RestMethodAndEndpointEntryForm = ({ return (
@@ -75,7 +66,7 @@ const RestMethodAndEndpointEntryForm = ({ id="rest-method" aria-haspopup="true" aria-controls="dropdown-menu" - onClick={() => setDropdownIsActive(!dropdownIsActive)} + onClick={toggleDropdown} > {newRequestFields.method} @@ -94,7 +85,7 @@ const RestMethodAndEndpointEntryForm = ({ {newRequestFields.method !== 'GET' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('GET'); }} className="dropdown-item" @@ -106,7 +97,7 @@ const RestMethodAndEndpointEntryForm = ({ {newRequestFields.method !== 'POST' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('POST'); }} className="dropdown-item" @@ -117,7 +108,7 @@ const RestMethodAndEndpointEntryForm = ({ {newRequestFields.method !== 'PUT' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('PUT'); }} className="dropdown-item" @@ -128,7 +119,7 @@ const RestMethodAndEndpointEntryForm = ({ {newRequestFields.method !== 'PATCH' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('PATCH'); }} className="dropdown-item" @@ -139,7 +130,7 @@ const RestMethodAndEndpointEntryForm = ({ {newRequestFields.method !== 'DELETE' && ( { - setDropdownIsActive(false); + closeDropdown(); methodChangeHandler('DELETE'); }} className="dropdown-item" diff --git a/src/client/components/main/response-composer/CookieContainer.tsx b/src/client/components/main/response-composer/CookieContainer.tsx index 9f0a4c9e9..5db93c321 100644 --- a/src/client/components/main/response-composer/CookieContainer.tsx +++ b/src/client/components/main/response-composer/CookieContainer.tsx @@ -2,22 +2,20 @@ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useAppSelector } from '../../../rtk/store'; interface CookieProps { className?: string; - cookie: { - [key: string]: any; - }; + cookie: Record; } export default function CookieContainer({ className, cookie }: CookieProps) { const [showCookie, setShowCookie] = useState(false); - const isDark = useSelector((state: any) => state.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); const cookies = Object.entries(cookie).map(([key, value], index) => { if (!key || !value) return null; - if ((showCookie === true && index > 1) || (index <= 1)) { + if ((showCookie === true && index > 1) || index <= 1) { return ( {key} @@ -30,7 +28,9 @@ export default function CookieContainer({ className, cookie }: CookieProps) { return ( { setShowCookie(showCookie === false); }} @@ -45,4 +45,3 @@ export default function CookieContainer({ className, cookie }: CookieProps) {
); } - diff --git a/src/client/components/main/response-composer/CookiesContainer.tsx b/src/client/components/main/response-composer/CookiesContainer.tsx index 0d7e88dc0..30eed7d9f 100644 --- a/src/client/components/main/response-composer/CookiesContainer.tsx +++ b/src/client/components/main/response-composer/CookiesContainer.tsx @@ -12,7 +12,9 @@ interface CookiesContainerProps { }; } -export default function CookiesContainer({ currentResponse }: CookiesContainerProps) { +export default function CookiesContainer({ + currentResponse, +}: CookiesContainerProps) { if ( !currentResponse.response || !currentResponse.response.cookies || diff --git a/src/client/components/main/response-composer/EmptyState.tsx b/src/client/components/main/response-composer/EmptyState.tsx index df963c86b..fe65cddf7 100644 --- a/src/client/components/main/response-composer/EmptyState.tsx +++ b/src/client/components/main/response-composer/EmptyState.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import logofaded from '../../../../assets/img/swell-logo-faded.avif'; +import logofaded from '~/assets/img/swell-logo-faded.png'; interface EmptyStateProps { connection?: any; diff --git a/src/client/components/main/response-composer/EventPreview.tsx b/src/client/components/main/response-composer/EventPreview.tsx index 8f8f3efda..7fbc2918e 100644 --- a/src/client/components/main/response-composer/EventPreview.tsx +++ b/src/client/components/main/response-composer/EventPreview.tsx @@ -1,9 +1,9 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; -import dropDownArrow from '../../../../assets/icons/caret-down-tests.svg'; -import dropDownArrowUp from '../../../../assets/icons/caret-up-tests.svg'; +import { useAppSelector } from '../../../rtk/store'; +import dropDownArrow from '~/assets/icons/caret-down-tests.svg'; +import dropDownArrowUp from '~/assets/icons/caret-up-tests.svg'; interface Props { contents: string; @@ -13,12 +13,14 @@ interface Props { const EventPreview: React.FC = ({ contents }) => { const [showPreview, setShowPreview] = useState(false); const handleShowPreview = () => setShowPreview(!showPreview); - const isDark = useSelector((state: any) => state.ui.isDark); + const isDark = useAppSelector((state) => state.ui.isDark); return (
{ handleShowPreview(); }} @@ -51,7 +53,10 @@ const EventPreview: React.FC = ({ contents }) => { )}
{showPreview === true && ( -
+