fixes ContentTypeDropdown and AuthDropdown (#4918)

This commit is contained in:
Dimitri Mitropoulos 2022-06-29 17:34:28 -04:00 committed by GitHub
parent f00dc50da0
commit 9bea9e06f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 110 deletions

View File

@ -146,9 +146,9 @@ interface State {
}
const isComponent = (match: string) => (child: ReactNode) => any(equals(match), [
// @ts-expect-error not sure
// @ts-expect-error this is required by our API for Dropdown
child.type.name,
// @ts-expect-error not sure
// @ts-expect-error this is required by our API for Dropdown
child.type.displayName,
]);

View File

@ -1,5 +1,6 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames';
import { any, equals } from 'ramda';
import React, { CSSProperties, Fragment, PureComponent, ReactNode } from 'react';
import ReactDOM from 'react-dom';
@ -36,6 +37,17 @@ interface State {
uniquenessKey: number;
}
const isComponent = (match: string) => (child: ReactNode) => any(equals(match), [
// @ts-expect-error this is required by our API for Dropdown
child.type.name,
// @ts-expect-error this is required by our API for Dropdown
child.type.displayName,
]);
const isDropdownItem = isComponent(DropdownItem.name);
const isDropdownButton = isComponent(DropdownButton.name);
const isDropdownDivider = isComponent(DropdownDivider.name);
@autoBindMethodsForReact(AUTOBIND_CFG)
export class Dropdown extends PureComponent<DropdownProps, State> {
private _node: HTMLDivElement | null = null;
@ -356,8 +368,7 @@ export class Dropdown extends PureComponent<DropdownProps, State> {
const allChildren = this._getFlattenedChildren(children);
const visibleChildren = allChildren.filter((child, i) => {
// @ts-expect-error -- TSCONVERSION this should cater for all types that ReactNode can be
if (child.type.name !== DropdownItem.name) {
if (isDropdownItem(child)) {
return true;
}
@ -368,11 +379,9 @@ export class Dropdown extends PureComponent<DropdownProps, State> {
for (let i = 0; i < allChildren.length; i++) {
const child = allChildren[i];
// @ts-expect-error -- TSCONVERSION this should cater for all types that ReactNode can be
if (child.type.name === DropdownButton.name) {
if (isDropdownButton(child)) {
dropdownButtons.push(child);
// @ts-expect-error -- TSCONVERSION this should cater for all types that ReactNode can be
} else if (child.type.name === DropdownItem.name) {
} else if (isDropdownItem(child)) {
const active = i === filterActiveIndex;
const hide = !visibleChildren.includes(child);
dropdownItems.push(
@ -387,14 +396,12 @@ export class Dropdown extends PureComponent<DropdownProps, State> {
{child}
</li>,
);
// @ts-expect-error -- TSCONVERSION this should cater for all types that ReactNode can be
} else if (child.type.name === DropdownDivider.name) {
} else if (isDropdownDivider(child)) {
const currentIndex = visibleChildren.indexOf(child);
const nextChild = visibleChildren[currentIndex + 1];
// Only show the divider if the next child is a DropdownItem
// @ts-expect-error -- TSCONVERSION this should cater for all types that ReactNode can be
if (nextChild && nextChild.type.name === DropdownItem.name) {
if (nextChild && isDropdownItem(nextChild)) {
dropdownItems.push(<li key={i}>{child}</li>);
}
}

View File

@ -1,4 +1,5 @@
import React, { FC, useCallback } from 'react';
import { useSelector } from 'react-redux';
import {
AUTH_ASAP,
@ -16,6 +17,7 @@ import {
} from '../../../common/constants';
import * as models from '../../../models';
import type { Request, RequestAuthentication } from '../../../models/request';
import { selectActiveRequest } from '../../redux/selectors';
import { Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button';
import { DropdownDivider } from '../base/dropdown/dropdown-divider';
@ -34,17 +36,27 @@ const AuthItem: FC<{
{nameOverride || getAuthTypeName(type, true)}
</DropdownItem>
);
AuthItem.displayName = DropdownItem.name;
interface Props {
className?: string;
onChange: (request: Request, arg1: RequestAuthentication) => Promise<Request>;
request: Request;
}
export const AuthDropdown: FC<Props> = ({ children, className, onChange, request }) => {
const { authentication } = request;
export const AuthDropdown: FC<Props> = ({ onChange }) => {
const activeRequest = useSelector(selectActiveRequest);
const onClick = useCallback((type: string) => {
if (!activeRequest) {
return;
}
if (!('authentication' in activeRequest)) {
// gRPC Requests don't have `authentication`
return;
}
const { authentication } = activeRequest;
const fn = async () => {
if (type === authentication.type) {
// Type didn't change
@ -73,21 +85,34 @@ export const AuthDropdown: FC<Props> = ({ children, className, onChange, request
break;
}
}
onChange(request, newAuthentication);
onChange(activeRequest, newAuthentication);
};
fn();
}, [authentication, onChange, request]);
}, [onChange, activeRequest]);
const isCurrent = useCallback((type: string) => (
type === (authentication.type || AUTH_NONE)
), [authentication.type]);
const isCurrent = useCallback((type: string) => {
if (!activeRequest) {
return false;
}
if (!('authentication' in activeRequest)) {
return false;
}
return type === (activeRequest.authentication.type || AUTH_NONE);
}, [activeRequest]);
if (!activeRequest) {
return null;
}
const itemProps = { onClick, isCurrent };
return (
<Dropdown beside>
<DropdownDivider>Auth Types</DropdownDivider>
<DropdownButton className={className}>{children}</DropdownButton>
<DropdownButton className="tall">
{'authentication' in activeRequest ? getAuthTypeName(activeRequest.authentication.type) || 'Auth' : 'Auth'}
<i className="fa fa-caret-down space-left" />
</DropdownButton>
<AuthItem type={AUTH_BASIC} {...itemProps} />
<AuthItem type={AUTH_DIGEST} {...itemProps} />
<AuthItem type={AUTH_OAUTH_1} {...itemProps} />

View File

@ -16,16 +16,15 @@ import {
getContentTypeName,
} from '../../../common/constants';
import { selectActiveRequest } from '../../redux/selectors';
import { Dropdown, DropdownProps } from '../base/dropdown/dropdown';
import { Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button';
import { DropdownDivider } from '../base/dropdown/dropdown-divider';
import { DropdownItem } from '../base/dropdown/dropdown-item';
import { AlertModal } from '../modals/alert-modal';
import { showModal } from '../modals/index';
interface Props extends DropdownProps {
interface Props {
onChange: (mimeType: string | null) => void;
className?: string;
}
const EMPTY_MIME_TYPE = null;
@ -41,36 +40,38 @@ const MimeTypeItem: FC<{
}) => {
const activeRequest = useSelector(selectActiveRequest);
const handleChangeMimeType = useCallback(async (mimeType: string | null) => {
if (activeRequest) {
const { body } = activeRequest;
const hasMimeType = 'mimeType' in body;
if (hasMimeType && body.mimeType === mimeType) {
// Nothing to do since the mimeType hasn't changed
return;
}
if (!activeRequest) {
return;
}
const hasParams = 'params' in body && body.params && body.params.length;
const hasText = body.text && body.text.length;
const hasFile = 'fileName' in body && body.fileName && body.fileName.length;
const isEmpty = !hasParams && !hasText && !hasFile;
const isFile = hasMimeType && body.mimeType === CONTENT_TYPE_FILE;
const isMultipart = hasMimeType && body.mimeType === CONTENT_TYPE_FORM_DATA;
const isFormUrlEncoded = hasMimeType && body.mimeType === CONTENT_TYPE_FORM_URLENCODED;
const isText = !isFile && !isMultipart;
const willBeFile = mimeType === CONTENT_TYPE_FILE;
const willBeMultipart = mimeType === CONTENT_TYPE_FORM_DATA;
const willBeGraphQL = mimeType === CONTENT_TYPE_GRAPHQL;
const willConvertToText = !willBeGraphQL && !willBeFile && !willBeMultipart;
const willPreserveText = willConvertToText && isText;
const willPreserveForm = isFormUrlEncoded && willBeMultipart;
const { body } = activeRequest;
const hasMimeType = 'mimeType' in body;
if (hasMimeType && body.mimeType === mimeType) {
// Nothing to do since the mimeType hasn't changed
return;
}
if (!isEmpty && !willPreserveText && !willPreserveForm) {
await showModal(AlertModal, {
title: 'Switch Body Type?',
message: 'Current body will be lost. Are you sure you want to continue?',
addCancel: true,
});
}
const hasParams = 'params' in body && body.params && body.params.length;
const hasText = body.text && body.text.length;
const hasFile = 'fileName' in body && body.fileName && body.fileName.length;
const isEmpty = !hasParams && !hasText && !hasFile;
const isFile = hasMimeType && body.mimeType === CONTENT_TYPE_FILE;
const isMultipart = hasMimeType && body.mimeType === CONTENT_TYPE_FORM_DATA;
const isFormUrlEncoded = hasMimeType && body.mimeType === CONTENT_TYPE_FORM_URLENCODED;
const isText = !isFile && !isMultipart;
const willBeFile = mimeType === CONTENT_TYPE_FILE;
const willBeMultipart = mimeType === CONTENT_TYPE_FORM_DATA;
const willBeGraphQL = mimeType === CONTENT_TYPE_GRAPHQL;
const willConvertToText = !willBeGraphQL && !willBeFile && !willBeMultipart;
const willPreserveText = willConvertToText && isText;
const willPreserveForm = isFormUrlEncoded && willBeMultipart;
if (!isEmpty && !willPreserveText && !willPreserveForm) {
await showModal(AlertModal, {
title: 'Switch Body Type?',
message: 'Current body will be lost. Are you sure you want to continue?',
addCancel: true,
});
}
onChange(mimeType);
@ -87,40 +88,52 @@ const MimeTypeItem: FC<{
</DropdownItem>
);
};
MimeTypeItem.displayName = DropdownItem.name;
export const ContentTypeDropdown: FC<Props> = ({
children,
className,
onChange,
...extraProps
}) => (
<Dropdown beside {...extraProps}>
<DropdownButton className={className}>{children}</DropdownButton>
<DropdownDivider>
<span>
<i className="fa fa-bars" /> Structured
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_FORM_DATA} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_FORM_URLENCODED} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_GRAPHQL} onChange={onChange} />
<DropdownDivider>
<span>
<i className="fa fa-code" /> Text
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_JSON} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_XML} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_YAML} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_EDN} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_PLAINTEXT} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_OTHER} onChange={onChange} />
<DropdownDivider>
<span>
<i className="fa fa-ellipsis-h" /> Other
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_FILE} onChange={onChange} />
<MimeTypeItem mimeType={EMPTY_MIME_TYPE} forcedName="No Body" onChange={onChange} />
</Dropdown>
);
export const ContentTypeDropdown: FC<Props> = ({ onChange }) => {
const activeRequest = useSelector(selectActiveRequest);
if (!activeRequest) {
return null;
}
const { body } = activeRequest;
const hasMimeType = 'mimeType' in body;
const hasParams = body && 'params' in body && body.params;
const numBodyParams = hasParams ? body.params?.filter(({ disabled }) => !disabled).length : 0;
return (
<Dropdown>
<DropdownButton className="tall">
{hasMimeType ? getContentTypeName(body.mimeType) : 'Body'}
{numBodyParams ? <span className="bubble space-left">{numBodyParams}</span> : null}
<i className="fa fa-caret-down space-left" />
</DropdownButton>
<DropdownDivider>
<span>
<i className="fa fa-bars" /> Structured
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_FORM_DATA} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_FORM_URLENCODED} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_GRAPHQL} onChange={onChange} />
<DropdownDivider>
<span>
<i className="fa fa-code" /> Text
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_JSON} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_XML} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_YAML} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_EDN} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_PLAINTEXT} onChange={onChange} />
<MimeTypeItem mimeType={CONTENT_TYPE_OTHER} onChange={onChange} />
<DropdownDivider>
<span>
<i className="fa fa-ellipsis-h" /> Other
</span>
</DropdownDivider>
<MimeTypeItem mimeType={CONTENT_TYPE_FILE} onChange={onChange} />
<MimeTypeItem mimeType={EMPTY_MIME_TYPE} forcedName="No Body" onChange={onChange} />
</Dropdown>
);
};

View File

@ -4,7 +4,6 @@ import React, { FC, useCallback, useEffect, useRef } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { useMount } from 'react-use';
import { getAuthTypeName, getContentTypeName } from '../../../common/constants';
import * as models from '../../../models';
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
import type {
@ -147,12 +146,6 @@ export const RequestPane: FC<Props> = ({
);
}
let numBodyParams = 0;
if (request.body && request.body.params) {
numBodyParams = request.body.params.filter(p => !p.disabled).length;
}
const numParameters = request.parameters.filter(p => !p.disabled).length;
const numHeaders = request.headers.filter(h => !h.disabled).length;
const urlHasQueryParameters = request.url.indexOf('?') >= 0;
@ -185,24 +178,12 @@ export const RequestPane: FC<Props> = ({
<Tab tabIndex="=1">
<ContentTypeDropdown
onChange={updateRequestMimeType}
className="tall"
>
{typeof request.body.mimeType === 'string'
? getContentTypeName(request.body.mimeType)
: 'Body'}
{numBodyParams ? <span className="bubble space-left">{numBodyParams}</span> : null}
<i className="fa fa-caret-down space-left" />
</ContentTypeDropdown>
/>
</Tab>
<Tab tabIndex="=1">
<AuthDropdown
onChange={updateRequestAuthentication}
request={request}
className="tall"
>
{getAuthTypeName(request.authentication.type) || 'Auth'}
<i className="fa fa-caret-down space-left" />
</AuthDropdown>
/>
</Tab>
<Tab tabIndex="=1">
<button>