Skip to content

Commit

Permalink
Match rendering of callbacks to style of operation responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan-Bailey-Bose committed Jan 16, 2020
1 parent 06440a5 commit 60fa093
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 44 deletions.
87 changes: 87 additions & 0 deletions src/components/CallbackSamples/CallbackSamples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { isPayloadSample, RedocNormalizedOptions } from '../../services';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';

import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { OptionsContext } from '../OptionsProvider';
import { CallbackModel } from '../../services/models';
import { Endpoint } from '../Endpoint/Endpoint';

export interface CallbackSamplesProps {
callbacks: CallbackModel[];
}

@observer
export class CallbackSamples extends React.Component<CallbackSamplesProps> {
static contextType = OptionsContext;
context: RedocNormalizedOptions;

render() {
const { callbacks } = this.props;

// Sums number of code samples per operation per callback
const numSamples = callbacks.reduce(
(callbackSum, callback) =>
callbackSum +
callback.operations.reduce(
(sampleSum, operation) => sampleSum + operation.codeSamples.length,
0,
),
0,
);

const hasSamples = numSamples > 0;
const hideTabList = numSamples === 1 ? this.context.hideSingleRequestSampleTab : false;

const renderTabs = () => {
return callbacks.map(callback => {
return callback.operations.map(operation => {
return operation.codeSamples.map(sample => {
return (
<Tab key={operation.id + '_' + operation.name}>
{operation.name} {sample.label !== undefined ? sample.label : sample.lang}
</Tab>
);
});
});
});
};

const renderTabPanels = () => {
return callbacks.map(callback => {
return callback.operations.map(operation => {
return operation.codeSamples.map(sample => {
return (
<TabPanel key={sample.lang + '_' + (sample.label || '')}>
{isPayloadSample(sample) ? (
<div>
<Endpoint operation={operation} inverted={false} />
<PayloadSamples content={sample.requestBodyContent} />
</div>
) : (
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
)}
</TabPanel>
);
});
});
});
};

return (
(hasSamples && (
<div>
<RightPanelHeader> Callback samples </RightPanelHeader>

<Tabs defaultIndex={0}>
<TabList hidden={hideTabList}>{renderTabs()}</TabList>
{renderTabPanels()}
</Tabs>
</div>
)) ||
null
);
}
}
18 changes: 12 additions & 6 deletions src/components/Callbacks/Callback.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { CallbackModel } from '../../services/models';
import { OperationModel } from '../../services/models';
import { CallbackDetails } from './CallbackDetails';
import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.elements';

@observer
export class CallbackView extends React.Component<{ callback: CallbackModel }> {
export class CallbackView extends React.Component<{ callbackOperation: OperationModel }> {
toggle = () => {
this.props.callback.toggle();
this.props.callbackOperation.toggle();
};

render() {
const { name, expanded } = this.props.callback;
const { name, description, expanded } = this.props.callbackOperation;

return (
<div>
<StyledCallbackTitle onClick={this.toggle} name={name} opened={expanded} />
<StyledCallbackTitle
onClick={this.toggle}
name={name}
description={description}
opened={expanded}
/>
{expanded && (
<CallbackDetailsWrap>
<span>{name}</span>
<CallbackDetails callbackOperation={this.props.callbackOperation} />
</CallbackDetailsWrap>
)}
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/components/Callbacks/CallbackDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from 'react';
import { OperationModel } from '../../services/models';
import { OperationItem } from '../ContentItems/ContentItems';

export class CallbackDetails extends React.PureComponent<{ callbackOperation: OperationModel }> {
render() {
return <OperationItem item={this.props.callbackOperation} />;
}
}
7 changes: 5 additions & 2 deletions src/components/Callbacks/CallbackTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import * as React from 'react';

import { ShelfIcon } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown';

export interface CallbackTitleProps {
name: string;
description?: string;
opened?: boolean;
className?: string;
onClick?: () => void;
}

export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
render() {
const { name, opened, className, onClick } = this.props;
const { name, description, opened, className, onClick } = this.props;
return (
<div className={className} onClick={onClick || undefined}>
<ShelfIcon size={'1.5em'} direction={opened ? 'up' : 'down'} float={'left'} />
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
<strong>{name} </strong>
{description && <Markdown compact={true} inline={true} source={description} />}
</div>
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Callbacks/CallbacksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export class CallbacksList extends React.PureComponent<CallbacksListProps> {
<div>
<CallbacksHeader> Callbacks </CallbacksHeader>
{callbacks.map(callback => {
return <CallbackView key={callback.name} callback={callback} />;
return callback.operations.map(operation => {
return <CallbackView key={callback.name} callbackOperation={operation} />;
});
})}
</div>
);
Expand Down
21 changes: 0 additions & 21 deletions src/components/ContentItems/ContentItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ import { Operation } from '..';
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { ContentItemModel } from '../../services';
import { GroupModel, OperationModel } from '../../services/models';
import styled from '../../styled-components';

const CallbacksHeader = styled.h3`
font-size: 18px;
padding: 0 40px;
margin: 3em 0 1.1em;
color: #253137;
font-weight: normal;
`;

@observer
export class ContentItems extends React.Component<{
Expand All @@ -28,18 +19,6 @@ export class ContentItems extends React.Component<{
return null;
}
return items.map(item => {
if (item.type === 'operation' && item.callbacks.length > 0) {
return (
<React.Fragment key={item.id}>
<ContentItem item={item} />
<CallbacksHeader>Callbacks</CallbacksHeader>
{item.callbacks.map((callbackIndex, idx) => {
return <ContentItems key={idx} items={callbackIndex.operations} />;
})}
</React.Fragment>
);
}

return <ContentItem key={item.id} item={item} />;
});
}
Expand Down
44 changes: 31 additions & 13 deletions src/components/Operation/Operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ import { Parameters } from '../Parameters/Parameters';
import { RequestSamples } from '../RequestSamples/RequestSamples';
import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { CallbacksList } from '../Callbacks';
import { CallbackSamples } from '../CallbackSamples/CallbackSamples';

import { OperationModel as OperationType } from '../../services/models';
import styled from '../../styled-components';
import { Extensions } from '../Fields/Extensions';

const CallbackMiddlePanel = styled(MiddlePanel)`
width: 100%;
padding: 0;
`;

const OperationRow = styled(Row)`
backface-visibility: hidden;
contain: content;
Expand All @@ -42,18 +49,23 @@ export class Operation extends React.Component<OperationProps> {

const { name: summary, description, deprecated, externalDocs } = operation;
const hasDescription = !!(description || externalDocs);
const AdaptiveMiddlePanel = operation.isCallback ? CallbackMiddlePanel : MiddlePanel;

return (
<OptionsContext.Consumer>
{options => (
<OperationRow>
<MiddlePanel>
<H2>
<ShareLink to={operation.id} />
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
</H2>
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
{hasDescription && (
<AdaptiveMiddlePanel>
{!operation.isCallback && (
<H2>
<ShareLink to={operation.id} />
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
</H2>
)}
{!operation.isCallback && options.pathInMiddlePanel && (
<Endpoint operation={operation} inverted={true} />
)}
{!operation.isCallback && hasDescription && (
<Description>
{description !== undefined && <Markdown source={description} />}
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
Expand All @@ -63,12 +75,18 @@ export class Operation extends React.Component<OperationProps> {
<SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} />
</MiddlePanel>
<DarkRightPanel>
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
<RequestSamples operation={operation} />
<ResponseSamples operation={operation} />
</DarkRightPanel>
<CallbacksList callbacks={operation.callbacks} />
</AdaptiveMiddlePanel>
{!operation.isCallback && (
<DarkRightPanel>
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
<RequestSamples operation={operation} />
<ResponseSamples operation={operation} />
{operation.callbacks.length > 0 && (
<CallbackSamples callbacks={operation.callbacks} />
)}
</DarkRightPanel>
)}
</OperationRow>
)}
</OptionsContext.Consumer>
Expand Down
2 changes: 1 addition & 1 deletion src/components/__tests__/Callbacks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Components', () => {
options,
);
const callbackViewElement = shallow(
<CallbackView key={callback.name} callback={callback} />,
<CallbackView key={callback.name} callbackOperation={callback.operations[0]} />,
).getElement();
expect(callbackViewElement.props).toBeDefined();
expect(callbackViewElement.props.children).toBeDefined();
Expand Down
8 changes: 8 additions & 0 deletions src/services/models/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ export class OperationModel implements IMenuItem {
this.active = false;
}

/**
* Toggle expansion in middle panel (for callbacks, which are operations)
*/
@action
toggle() {
this.expanded = !this.expanded;
}

expand() {
if (this.parent) {
this.parent.expand();
Expand Down

0 comments on commit 60fa093

Please sign in to comment.