Skip to content

Commit

Permalink
feat: port "copy to clipboard" / "expand/collapse all" functionality
Browse files Browse the repository at this point in the history
closes #410
  • Loading branch information
RomanHotsiy committed Jan 23, 2018
1 parent 199f240 commit 5bb0bdf
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 15 deletions.
2 changes: 1 addition & 1 deletion demo/playground/hmr-playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const swagger = window.location.search.indexOf('swagger') > -1; // compatibility
const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';

let store;
const options: RedocRawOptions = {};
const options: RedocRawOptions = { nativeScrollbars: true };

async function init() {
const spec = await loadAndBundleSpec(specUrl);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@types/react-dom": "^16.0.0",
"@types/react-hot-loader": "^3.0.3",
"@types/react-tabs": "^1.0.2",
"@types/react-tooltip": "^3.3.3",
"@types/webpack": "^3.0.5",
"@types/webpack-env": "^1.13.0",
"awesome-typescript-loader": "^3.2.2",
Expand Down Expand Up @@ -89,8 +90,8 @@
"prop-types": "^15.6.0",
"react-dropdown": "^1.3.0",
"react-hot-loader": "3.0.0-beta.6",
"react-perfect-scrollbar": "^0.2.2",
"react-tabs": "^2.0.0",
"react-tooltip": "^3.4.0",
"remarkable": "^1.7.1",
"slugify": "^1.2.1",
"stickyfill": "^1.1.1",
Expand Down
55 changes: 55 additions & 0 deletions src/common-elements/CopyButtonWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import * as ReactTooltip from 'react-tooltip';

import { ClipboardService } from '../services/ClipboardService';

export interface CopyButtonWrapperProps {
data: any;
children: (
props: {
renderCopyButton: (() => React.ReactNode);
},
) => React.ReactNode;
}

export class CopyButtonWrapper extends React.PureComponent<CopyButtonWrapperProps> {
render() {
return this.props.children({ renderCopyButton: this.renderCopyButton });
}

copy = () => {
const content =
typeof this.props.data === 'string'
? this.props.data
: JSON.stringify(this.props.data, null, 2);
ClipboardService.copyCustom(content);
};

renderCopyButton = () => {
return (
<>
<span
onClick={this.copy}
data-tip={true}
data-for="copy_tooltip"
data-event="click"
data-event-off="mouseleave"
>
Copy
</span>
<ReactTooltip
isCapture={true}
id="copy_tooltip"
place="top"
getContent={this.getTooltipContent}
type="light"
effect="solid"
/>
</>
);
};

getTooltipContent() {
return ClipboardService.isSupported() ? 'Copied' : 'Not supported in your browser';
}
}
1 change: 1 addition & 0 deletions src/common-elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './schema';
export * from './dropdown';
export * from './mixins';
export * from './tabs';
export * from './samples';
23 changes: 23 additions & 0 deletions src/common-elements/samples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from '../styled-components';

export const SampleControls = styled.div`
opacity: 0.4;
transition: opacity 0.3s ease;
text-align: right;
> span {
display: inline-block;
padding: 2px 10px;
cursor: pointer;
:hover {
background: rgba(255, 255, 255, 0.1);
}
}
`;

export const SampleControlsWrap = styled.div`
&:hover ${SampleControls} {
opacity: 1;
}
`;
47 changes: 41 additions & 6 deletions src/components/JsonViewer/JsonViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react';
import styled from '../../styled-components';

import { SampleControls } from '../../common-elements';
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
import { jsonToHTML } from '../../utils/jsonToHtml';
import { jsonStyles } from './style';

Expand All @@ -9,18 +11,51 @@ interface JsonProps {
className?: string;
}

const JsonViewerWrap = styled.div`
&:hover > ${SampleControls} {
opacity: 1;
}
`;

class Json extends React.PureComponent<JsonProps> {
node: HTMLElement | null;
node: HTMLDivElement;

render() {
return (
return <CopyButtonWrapper data={this.props.data}>{this.renderInner}</CopyButtonWrapper>;
}

renderInner = ({ renderCopyButton }) => (
<JsonViewerWrap>
<SampleControls>
{renderCopyButton()}
<span onClick={this.expandAll}> Expand all </span>
<span onClick={this.collapseAll}> Collapse all </span>
</SampleControls>
<div
className={this.props.className}
ref={node => (this.node = node)}
ref={node => (this.node = node!)}
dangerouslySetInnerHTML={{ __html: jsonToHTML(this.props.data) }}
/>
);
}
</JsonViewerWrap>
);

expandAll = () => {
const elements = this.node.getElementsByClassName('collapsible');
for (const collapsed of Array.prototype.slice.call(elements)) {
(collapsed.parentNode as Element)!.classList.remove('collapsed');
}
};

collapseAll = () => {
const elements = this.node.getElementsByClassName('collapsible');
for (const expanded of Array.prototype.slice.call(elements)) {
// const collapsed = elements[i];
if ((expanded.parentNode as Element)!.classList.contains('redoc-json')) {
continue;
}
(expanded.parentNode as Element)!.classList.add('collapsed');
}
};

clickListener = (event: MouseEvent) => {
let collapsed;
Expand All @@ -44,6 +79,6 @@ class Json extends React.PureComponent<JsonProps> {
}
}

export const StyledJson = styled(Json)`
export const JsonViewer = styled(Json)`
${jsonStyles};
`;
10 changes: 6 additions & 4 deletions src/components/PayloadSamples/MediaTypeSamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as React from 'react';

import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
import { MediaTypeModel } from '../../services/models';
import { StyledJson } from '../JsonViewer/JsonViewer';
import { SourceCode } from '../SourceCode/SourceCode';
import { JsonViewer } from '../JsonViewer/JsonViewer';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { NoSampleLabel } from './styled.elements';

import { isJsonLike, langFromMime } from '../../utils';
Expand All @@ -19,9 +19,11 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {

const noSample = <NoSampleLabel>No sample</NoSampleLabel>;
const sampleView = isJsonLike(mimeType)
? sample => <StyledJson data={sample} />
? sample => <JsonViewer data={sample} />
: sample =>
(sample && <SourceCode lang={langFromMime(mimeType)} source={sample} />) || { noSample };
(sample && <SourceCodeWithCopy lang={langFromMime(mimeType)} source={sample} />) || {
noSample,
};

const examplesNames = Object.keys(examples);
if (examplesNames.length === 0) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/RequestSamples/RequestSamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { OperationModel } from '../../services/models';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { SourceCode } from '../SourceCode/SourceCode';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';

import { Tab, TabList, TabPanel, Tabs } from '../../common-elements';

Expand Down Expand Up @@ -40,7 +40,7 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
)}
{samples.map(sample => (
<TabPanel key={sample.lang}>
<SourceCode lang={sample.lang} source={sample.source} />
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
</TabPanel>
))}
</Tabs>
Expand Down
19 changes: 19 additions & 0 deletions src/components/SourceCode/SourceCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import * as React from 'react';
import styled from '../../styled-components';
import { highlight } from '../../utils';

import { SampleControls, SampleControlsWrap } from '../../common-elements';
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';

const StyledPre = styled.pre`
font-family: ${props => props.theme.code.fontFamily};
font-size: ${props => props.theme.code.fontSize};
overflow-x: auto;
font-size: 0.9em;
margin: 0;
`;

export interface SourceCodeProps {
Expand All @@ -20,3 +24,18 @@ export class SourceCode extends React.PureComponent<SourceCodeProps> {
return <StyledPre dangerouslySetInnerHTML={{ __html: highlight(source, lang) }} />;
}
}

export class SourceCodeWithCopy extends React.PureComponent<SourceCodeProps> {
render() {
return (
<CopyButtonWrapper data={this.props.source}>
{({ renderCopyButton }) => (
<SampleControlsWrap>
<SampleControls>{renderCopyButton()}</SampleControls>
<SourceCode lang={this.props.lang} source={this.props.source} />
</SampleControlsWrap>
)}
</CopyButtonWrapper>
);
}
}
15 changes: 14 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@
dependencies:
"@types/react" "*"

"@types/react-tooltip@^3.3.3":
version "3.3.3"
resolved "https://registry.yarnpkg.com/@types/react-tooltip/-/react-tooltip-3.3.3.tgz#3b6dbb278fc8317ad04f0ce1972a0972f5450aa7"
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@^16.0.30":
version "16.0.34"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.34.tgz#7a8f795afd8a404a9c4af9539b24c75d3996914e"
Expand Down Expand Up @@ -1091,7 +1097,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"

classnames@^2.2.0, classnames@^2.2.3:
classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"

Expand Down Expand Up @@ -5848,6 +5854,13 @@ react-test-renderer@^16.0.0-0:
object-assign "^4.1.1"
prop-types "^15.6.0"

react-tooltip@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-3.4.0.tgz#037f38f797c3e6b1b58d2534ccc8c2c76af4f52d"
dependencies:
classnames "^2.2.5"
prop-types "^15.6.0"

react@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
Expand Down

0 comments on commit 5bb0bdf

Please sign in to comment.