From 8bc2173c1bc35e79c638991a69e629818e4e93e9 Mon Sep 17 00:00:00 2001 From: Rohini Senthil Date: Fri, 11 Jun 2021 20:10:57 +0530 Subject: [PATCH] (feat) add header tab for response (#20) * (feat) add tabs for response window * (fix) fix editor height * (improv) add teable to headers * (improv) map response headers * (improv) remove response cookies * (improv) use height % instead of flex * (improv) minor css change * (improv) change size of icon * (improv) minor editor css change --- src/extension.ts | 1 + .../RequestOptionsWindow/styles.css | 1 + webview/components/Response/index.tsx | 50 +++++++ .../Response/styles.css | 85 ++++-------- webview/constants/response-options.ts | 10 ++ webview/features/requestBody/Raw/styles.css | 2 +- webview/features/response/Default/index.tsx | 12 -- webview/features/response/Default/styles.css | 19 --- .../features/response/ErrorResponse/index.tsx | 16 --- .../response/ErrorResponse/styles.css | 20 --- webview/features/response/Response/index.tsx | 80 ----------- .../features/response/ResponseBody/index.tsx | 31 +++++ .../features/response/ResponseBody/styles.css | 7 + .../response/ResponseHeaders/index.tsx | 15 ++ .../response/ResponseHeaders/styles.css | 4 + .../features/response/ResponseTab/index.tsx | 56 ++++++++ .../features/response/ResponseTab/styles.css | 67 +++++++++ .../response/ResponseWindow/index.tsx | 23 ++++ .../response/ResponseWindow/styles.css | 4 + webview/features/response/responseSlice.ts | 11 +- webview/pages/Postcode/index.tsx | 2 +- webview/pages/Postcode/styles.css | 4 +- webview/shared/KeyValueTable/index.tsx | 129 ++++++++++-------- 23 files changed, 385 insertions(+), 264 deletions(-) create mode 100644 webview/components/Response/index.tsx rename webview/{features/response => components}/Response/styles.css (58%) create mode 100644 webview/constants/response-options.ts delete mode 100644 webview/features/response/Default/index.tsx delete mode 100644 webview/features/response/Default/styles.css delete mode 100644 webview/features/response/ErrorResponse/index.tsx delete mode 100644 webview/features/response/ErrorResponse/styles.css delete mode 100644 webview/features/response/Response/index.tsx create mode 100644 webview/features/response/ResponseBody/index.tsx create mode 100644 webview/features/response/ResponseBody/styles.css create mode 100644 webview/features/response/ResponseHeaders/index.tsx create mode 100644 webview/features/response/ResponseHeaders/styles.css create mode 100644 webview/features/response/ResponseTab/index.tsx create mode 100644 webview/features/response/ResponseTab/styles.css create mode 100644 webview/features/response/ResponseWindow/index.tsx create mode 100644 webview/features/response/ResponseWindow/styles.css diff --git a/src/extension.ts b/src/extension.ts index 325bb37..55c0f0c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -133,6 +133,7 @@ export function activate(context: vscode.ExtensionContext) { data: resp.data, status: resp.status, statusText: resp.statusText, + headers: resp.headers, }) ) .catch((err) => { diff --git a/webview/components/RequestOptionsWindow/styles.css b/webview/components/RequestOptionsWindow/styles.css index 13c6725..f3770f6 100644 --- a/webview/components/RequestOptionsWindow/styles.css +++ b/webview/components/RequestOptionsWindow/styles.css @@ -1,4 +1,5 @@ .request-options-window-wrapper { flex: 1; padding: 5px 20px 10px 20px; + overflow: scroll; } diff --git a/webview/components/Response/index.tsx b/webview/components/Response/index.tsx new file mode 100644 index 0000000..b0ebc33 --- /dev/null +++ b/webview/components/Response/index.tsx @@ -0,0 +1,50 @@ +import * as React from "react"; +import { ResponseTab } from "../../features/response/ResponseTab"; +import { ResponseWindow } from "../../features/response/ResponseWindow"; +import { ReactComponent as PackageIcon } from "../../icons/package.svg"; +import "./styles.css"; +import { useAppSelector } from "../../redux/hooks"; +import { selectResponse } from "../../features/response/responseSlice"; +import { supportedLangs } from "../../constants/supported-langs"; + +export const Response = () => { + const response = useAppSelector(selectResponse); + const [selected, setSelected] = React.useState("body"); + const [language, setLanguage] = React.useState(supportedLangs[0].value); + + if (response.loading) { + return ( +
+
Sending request ...
+
+
+ ); + } else if (response.initial) { + return ( +
+
Hit Send to get a response
+ +
+ ); + } else if (response.error) { + return ( +
+
Could not send request
+
{`Error: ${response.error.message}`}
+ +
+ ); + } else { + return ( +
+ + +
+ ); + } +}; diff --git a/webview/features/response/Response/styles.css b/webview/components/Response/styles.css similarity index 58% rename from webview/features/response/Response/styles.css rename to webview/components/Response/styles.css index 8a288c8..bbe7594 100644 --- a/webview/features/response/Response/styles.css +++ b/webview/components/Response/styles.css @@ -1,76 +1,51 @@ -.response-window { - border-top: var(--default-border-size) solid var(--border); - flex: 1; - padding: 5px 20px 10px 20px; -} - -.response-header { - margin: 10px 0; - margin-bottom: 15px; +.response-body-wrapper { display: flex; - align-items: center; - justify-content: space-between; + flex-direction: column; + flex: 1; + font-size: var(--default-font-size); + border-top: var(--default-border-size) solid var(--border); } -.response-view-options { +.error-response-wrapper { display: flex; + flex-direction: column; + justify-content: center; align-items: center; -} - -.response-status { font-size: var(--default-font-size); - display: flex; - align-items: center; + border-top: var(--default-border-size) solid var(--border); } -.response-editor { - height: 85%; +.img-error-response { + margin: 15px; + fill: var(--logo-color); + width: 20%; + height: 20%; } -.text-response-status { - color: var(--tab-info); - margin-left: 5px; +.error-message { + margin: 10px; + color: var(--error-message); } -.button-response-view { +.initial-response-wrapper { + border-top: var(--default-border-size) solid var(--border); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; color: var(--default-text-light); font-size: var(--default-font-size); - padding: 5px; - cursor: pointer; - background-color: var(--input-field); - border: 0; - outline: none; -} -.button-response-view:hover { - color: var(--default-text); - outline: none; -} -.button-response-view:focus { - outline: none; + flex: 1; } -.button-response-view-selected { - color: var(--default-text); +.img-initial-response { + fill: var(--logo-color); + width: 20%; + height: 20%; } -.select-res-lang { - margin-left: 10px; - padding: 4px; - display: inline-block; - font-weight: 600; - outline: none; - border: var(--default-border-size) solid var(--border); - border-radius: 2px; - background-color: var(--background); - color: var(--default-text-light); - cursor: pointer; - font-size: var(--small-font-size); -} -.select-res-lang:hover { - color: var(--default-text); -} -.select-res-lang:focus { - outline: none; +.initial-text { + padding: 10px; } .loader-wrapper { diff --git a/webview/constants/response-options.ts b/webview/constants/response-options.ts new file mode 100644 index 0000000..604e05e --- /dev/null +++ b/webview/constants/response-options.ts @@ -0,0 +1,10 @@ +export const responseOptions = [ + { + name: "Body", + value: "body", + }, + { + name: "Headers", + value: "headers", + }, +]; diff --git a/webview/features/requestBody/Raw/styles.css b/webview/features/requestBody/Raw/styles.css index e93ecb0..74f2c1e 100644 --- a/webview/features/requestBody/Raw/styles.css +++ b/webview/features/requestBody/Raw/styles.css @@ -4,5 +4,5 @@ } .raw-editor { - height: 100%; + height: 95%; } diff --git a/webview/features/response/Default/index.tsx b/webview/features/response/Default/index.tsx deleted file mode 100644 index ab361d4..0000000 --- a/webview/features/response/Default/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { ReactComponent as PackageIcon } from "../../../icons/package.svg"; - -export const Default = () => { - return ( -
-
Hit Send to get a response
- -
- ); -}; diff --git a/webview/features/response/Default/styles.css b/webview/features/response/Default/styles.css deleted file mode 100644 index 2e8a2ac..0000000 --- a/webview/features/response/Default/styles.css +++ /dev/null @@ -1,19 +0,0 @@ -.initial-response-wrapper { - border-top: var(--default-border-size) solid var(--border); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - color: var(--default-text-light); - font-size: var(--default-font-size); -} - -.img-initial-response { - fill: var(--logo-color); - width: 30%; - height: 30%; -} - -.initial-text { - padding: 10px; -} diff --git a/webview/features/response/ErrorResponse/index.tsx b/webview/features/response/ErrorResponse/index.tsx deleted file mode 100644 index b9eb3c0..0000000 --- a/webview/features/response/ErrorResponse/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { ReactComponent as PackageIcon } from "../../../icons/package.svg"; -import { useAppSelector } from "../../../redux/hooks"; -import { selectResponse } from "../responseSlice"; - -export const ErrorResponse = () => { - const response = useAppSelector(selectResponse); - return ( -
-
Could not send request
-
{`Error: ${response.error.message}`}
- -
- ); -}; diff --git a/webview/features/response/ErrorResponse/styles.css b/webview/features/response/ErrorResponse/styles.css deleted file mode 100644 index 00929cb..0000000 --- a/webview/features/response/ErrorResponse/styles.css +++ /dev/null @@ -1,20 +0,0 @@ -.error-response-wrapper { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - font-size: var(--default-font-size); - border-top: var(--default-border-size) solid var(--border); -} - -.img-error-response { - margin: 15px; - fill: var(--logo-color); - width: 30%; - height: 30%; -} - -.error-message { - margin: 10px; - color: var(--error-message); -} diff --git a/webview/features/response/Response/index.tsx b/webview/features/response/Response/index.tsx deleted file mode 100644 index 3058375..0000000 --- a/webview/features/response/Response/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from "react"; -import "./styles.css"; -import { responseViews } from "../../../constants/response-views"; -import { supportedLangs } from "../../../constants/supported-langs"; -import { Default } from "../Default"; -import { ErrorResponse } from "../ErrorResponse"; -import { useAppSelector } from "../../../redux/hooks"; -import { selectResponse } from "../responseSlice"; - -const Editor = React.lazy(() => import("../../../shared/Editor")); - -export const Response = () => { - const response = useAppSelector(selectResponse); - const [view, setView] = React.useState(responseViews[0].value); - const [language, setLanguage] = React.useState(supportedLangs[0].value); - - if (response.loading) { - return ( -
-
Sending request ...
-
-
- ); - } else if (response.initial) { - return ; - } else if (response.error) { - return ; - } else { - return ( -
-
-
-
- {responseViews.map((type) => ( - - ))} -
- {view === "pretty" && ( - - )} -
-
-
Status:
-
{`${response.status} ${response.statusText}`}
-
-
- loading
}> - - -
- ); - } -}; diff --git a/webview/features/response/ResponseBody/index.tsx b/webview/features/response/ResponseBody/index.tsx new file mode 100644 index 0000000..1b3f268 --- /dev/null +++ b/webview/features/response/ResponseBody/index.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import "./styles.css"; +import { useAppSelector } from "../../../redux/hooks"; +import { selectResponse } from "../responseSlice"; +import * as propTypes from "prop-types"; + +const Editor = React.lazy(() => import("../../../shared/Editor")); + +export const ResponseBody = (props) => { + const { language } = props; + const response = useAppSelector(selectResponse); + + return ( +
+ loading
}> + + +
+ ); +}; + +ResponseBody.propTypes = { + language: propTypes.string.isRequired, +}; diff --git a/webview/features/response/ResponseBody/styles.css b/webview/features/response/ResponseBody/styles.css new file mode 100644 index 0000000..b44dd85 --- /dev/null +++ b/webview/features/response/ResponseBody/styles.css @@ -0,0 +1,7 @@ +.response-window { + height: 100%; +} + +.response-editor { + height: 95%; +} diff --git a/webview/features/response/ResponseHeaders/index.tsx b/webview/features/response/ResponseHeaders/index.tsx new file mode 100644 index 0000000..4108faf --- /dev/null +++ b/webview/features/response/ResponseHeaders/index.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; +import { useAppSelector } from "../../../redux/hooks"; +import { KeyValueTable } from "../../../shared/KeyValueTable"; +import { selectResponseHeaders } from "../responseSlice"; +import "./styles.css"; + +export const ResponseHeaders = () => { + const headers = useAppSelector(selectResponseHeaders); + + return ( +
+ +
+ ); +}; diff --git a/webview/features/response/ResponseHeaders/styles.css b/webview/features/response/ResponseHeaders/styles.css new file mode 100644 index 0000000..022b598 --- /dev/null +++ b/webview/features/response/ResponseHeaders/styles.css @@ -0,0 +1,4 @@ +.response-headers { + display: flex; + height: 80%; +} \ No newline at end of file diff --git a/webview/features/response/ResponseTab/index.tsx b/webview/features/response/ResponseTab/index.tsx new file mode 100644 index 0000000..b7727bf --- /dev/null +++ b/webview/features/response/ResponseTab/index.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import "./styles.css"; +import * as propTypes from "prop-types"; +import { responseOptions } from "../../../constants/response-options"; +import { supportedLangs } from "../../../constants/supported-langs"; +import { useAppSelector } from "../../../redux/hooks"; +import { selectResponse } from "../responseSlice"; + +export const ResponseTab = (props) => { + const { selected, setSelected, language, setLanguage } = props; + const response = useAppSelector(selectResponse); + + return ( +
+
+ {responseOptions.map((option) => ( + + ))} + {selected === "body" ? ( + + ) : null} +
+
+
Status:
+
{`${response.status} ${response.statusText}`}
+
+
+ ); +}; + +ResponseTab.propTypes = { + selected: propTypes.string.isRequired, + setSelected: propTypes.func.isRequired, + language: propTypes.string.isRequired, + setLanguage: propTypes.func.isRequired, +}; diff --git a/webview/features/response/ResponseTab/styles.css b/webview/features/response/ResponseTab/styles.css new file mode 100644 index 0000000..6b030d7 --- /dev/null +++ b/webview/features/response/ResponseTab/styles.css @@ -0,0 +1,67 @@ +.response-options-tab-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px 5px 20px; +} + +.response-options { + display: flex; +} + +.response-option { + padding: 5px 5px 5px 0; + margin: 0 10px 0 0; + display: flex; + cursor: pointer; + background-color: transparent; + color: var(--default-text-light); + outline: none; + border: none; + border-radius: 0; + font-weight: 500; + font-size: var(--default-font-size); +} +.response-option:hover { + color: var(--default-text); +} + +.response-option-selected { + color: var(--default-text); +} + +.response-options-header-length { + margin-left: 4px; + color: var(--tab-info); +} + +.response-status { + font-size: var(--default-font-size); + display: flex; + align-items: center; +} + +.text-response-status { + color: var(--tab-info); + margin-left: 5px; +} + +.select-res-lang { + margin-left: 10px; + padding: 5px; + display: inline-block; + font-weight: 600; + outline: none; + border: var(--default-border-size) solid var(--border); + border-radius: 2px; + background-color: var(--background); + color: var(--default-text-light); + cursor: pointer; + font-size: var(--small-font-size); +} +.select-res-lang:hover { + color: var(--default-text); +} +.select-res-lang:focus { + outline: none; +} \ No newline at end of file diff --git a/webview/features/response/ResponseWindow/index.tsx b/webview/features/response/ResponseWindow/index.tsx new file mode 100644 index 0000000..85b94a7 --- /dev/null +++ b/webview/features/response/ResponseWindow/index.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import * as propTypes from "prop-types"; +import "./styles.css"; +import { ResponseBody } from "../ResponseBody"; +import { ResponseHeaders } from "../ResponseHeaders"; + +export const ResponseWindow = (props) => { + const { selected, language } = props; + return ( +
+ {selected === "body" ? ( + + ) : selected === "headers" ? ( + + ) : null} +
+ ); +}; + +ResponseWindow.propTypes = { + selected: propTypes.string.isRequired, + language: propTypes.string.isRequired, +}; diff --git a/webview/features/response/ResponseWindow/styles.css b/webview/features/response/ResponseWindow/styles.css new file mode 100644 index 0000000..2a87c4e --- /dev/null +++ b/webview/features/response/ResponseWindow/styles.css @@ -0,0 +1,4 @@ +.response-options-window-wrapper { + flex: 1; + padding: 5px 20px 10px 20px; +} diff --git a/webview/features/response/responseSlice.ts b/webview/features/response/responseSlice.ts index ca3abc0..73fa05f 100644 --- a/webview/features/response/responseSlice.ts +++ b/webview/features/response/responseSlice.ts @@ -8,6 +8,7 @@ export interface ResponseState { initial: boolean; error?: Error; loading?: boolean; + headers?: { key: string; value: string }[]; } const initialState: ResponseState = { initial: true }; @@ -16,9 +17,15 @@ const responseSlice = createSlice({ name: "response", initialState, reducers: { - responseUpdated(state, action: PayloadAction) { + responseUpdated(state, action: PayloadAction) { return { ...action.payload, + headers: + action.payload.headers && + Object.entries(action.payload.headers).map(([key, value]) => ({ + key, + value, + })), initial: false, loading: false, }; @@ -33,5 +40,7 @@ export const { responseUpdated, responseLoadingStarted } = responseSlice.actions; export const selectResponse = (state: RootState) => state.response; +export const selectResponseHeaders = (state: RootState) => + state.response.headers; export default responseSlice.reducer; diff --git a/webview/pages/Postcode/index.tsx b/webview/pages/Postcode/index.tsx index 443780e..c5c322b 100644 --- a/webview/pages/Postcode/index.tsx +++ b/webview/pages/Postcode/index.tsx @@ -4,7 +4,7 @@ import "./styles.css"; import { RequestBar } from "../../components/RequestBar"; import { RequestOptionsTab } from "../../components/RequestOptionsBar"; import { RequestOptionsWindow } from "../../components/RequestOptionsWindow"; -import { Response } from "../../features/response/Response"; +import { Response } from "../../components/Response"; import { requestOptions } from "../../constants/request-options"; export const Postcode = () => { diff --git a/webview/pages/Postcode/styles.css b/webview/pages/Postcode/styles.css index 43e02d4..6489a9f 100644 --- a/webview/pages/Postcode/styles.css +++ b/webview/pages/Postcode/styles.css @@ -7,11 +7,11 @@ .request-options-wrapper { display: flex; flex-direction: column; - flex: 1; + min-height: 50%; } .response-wrapper { display: flex; flex-direction: column; - flex: 1; + height: 50%; } diff --git a/webview/shared/KeyValueTable/index.tsx b/webview/shared/KeyValueTable/index.tsx index 9bfbd21..92ca8fa 100644 --- a/webview/shared/KeyValueTable/index.tsx +++ b/webview/shared/KeyValueTable/index.tsx @@ -12,31 +12,35 @@ export const KeyValueRow = (props) => { actions, onDelete, onChange, + fixed, } = props; return ( - - {actions && ( - - onChange({ - key: itemKey, - value: itemValue, - description: itemDescription, - disabled: !e.target.checked, - }) - } - /> - )} - + {!fixed && ( + + {actions && ( + + onChange({ + key: itemKey, + value: itemValue, + description: itemDescription, + disabled: !e.target.checked, + }) + } + /> + )} + + )} onChange({ key: e.target.value, @@ -52,6 +56,7 @@ export const KeyValueRow = (props) => { className="kv-input" placeholder="Value" value={itemValue} + disabled={fixed} onChange={(e) => onChange({ key: itemKey, @@ -62,59 +67,67 @@ export const KeyValueRow = (props) => { } /> - - - onChange({ - key: itemKey, - value: itemValue, - description: e.target.value, - disabled: itemDisabled, - }) - } - /> - - - {actions && ( - - )} - + {!fixed && ( + + + onChange({ + key: itemKey, + value: itemValue, + description: e.target.value, + disabled: itemDisabled, + }) + } + /> + + )} + {!fixed && ( + + {actions && ( + + )} + + )} ); }; export const KeyValueTable = (props) => { - const { data, onRowUpdate, onRowAdd, onRowDelete } = props; + const { data, fixed, onRowUpdate, onRowAdd, onRowDelete } = props; return ( - + {!fixed && } - - + {!fixed && } + {!fixed && } - {[...data, {}].map(({ key, value, description, disabled }, idx) => ( - onRowDelete(idx)} - onChange={(item) => - idx === data.length ? onRowAdd(item) : onRowUpdate(idx, item) - } - key={idx} - actions={idx !== data.length} - /> - ))} + {(fixed ? data : [...data, {}]).map( + ({ key, value, description, disabled }, idx) => ( + onRowDelete(idx)} + onChange={(item) => + idx === data.length ? onRowAdd(item) : onRowUpdate(idx, item) + } + key={idx} + actions={idx !== data.length} + /> + ) + )}
KEY VALUEDESCRIPTIONDESCRIPTION
); @@ -122,12 +135,14 @@ export const KeyValueTable = (props) => { KeyValueTable.propTypes = { data: propTypes.array.isRequired, - onRowDelete: propTypes.func.isRequired, - onRowAdd: propTypes.func.isRequired, - onRowUpdate: propTypes.func.isRequired, + fixed: propTypes.bool, + onRowDelete: propTypes.func, + onRowAdd: propTypes.func, + onRowUpdate: propTypes.func, }; KeyValueRow.propTypes = { + fixed: propTypes.bool, itemKey: propTypes.string.isRequired, itemValue: propTypes.string.isRequired, itemDescription: propTypes.string.isRequired,