Skip to content

Commit

Permalink
move logic to common function
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-white committed May 16, 2024
1 parent b99cd35 commit 1362807
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 138 deletions.
246 changes: 116 additions & 130 deletions packages/elements-core/src/components/RequestSamples/RequestSamples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,9 @@ import React, { useMemo } from 'react';

import { persistAtom } from '../../utils/jotai/persistAtom';
import { convertRequestToSample } from './convertRequestToSample';
import { CodeExampleOverride } from './extractCodeExampleOverrides';
import { getConfigFor, requestSampleConfigs } from './requestSampleConfigs';

export type CodeExampleOverride = {
/**
* Code sample language.
*/
lang: string;
/**
* Code sample label, for example Node or Python2.7, optional, lang is used by default
*/
label?: string;
/**
* Code sample source code
*/
source: string;
};


export interface RequestSamplesProps {
/**
* The HTTP request to generate code for.
Expand All @@ -32,7 +17,7 @@ export interface RequestSamplesProps {
/**
* The list of code examples to override the generated ones.
*/
codeExampleOverrides: CodeExampleOverride[];
codeExampleOverrides?: CodeExampleOverride[];
/**
* True when embedded in TryIt
*/
Expand All @@ -49,125 +34,126 @@ const fallbackText = 'Unable to generate code example';
*
* The programming language can be selected by the user and is remembered across instances and remounts.
*/
export const RequestSamples = React.memo<RequestSamplesProps>(({ request, embeddedInMd = false, codeExampleOverrides = [] }) => {
const [selectedLanguage, setSelectedLanguage] = useAtom(selectedLanguageAtom);
const [selectedLibrary, setSelectedLibrary] = useAtom(selectedLibraryAtom);

const { httpSnippetLanguage, httpSnippetLibrary, mosaicCodeViewerLanguage } = getConfigFor(
selectedLanguage,
selectedLibrary,
);

const [requestSample, setRequestSample] = React.useState<string | null>(null);
React.useEffect(() => {
let isStale = false;
let selectedCodeExampleOverride: string | undefined;

if (codeExampleOverrides.length > 0) {
const codeExampleOverride = codeExampleOverrides.find(override => {
if (override.label) {
return (
override.lang.toLowerCase() === httpSnippetLanguage && override.label.toLowerCase() === httpSnippetLibrary
);
export const RequestSamples = React.memo<RequestSamplesProps>(
({ request, embeddedInMd = false, codeExampleOverrides = [] }) => {
const [selectedLanguage, setSelectedLanguage] = useAtom(selectedLanguageAtom);
const [selectedLibrary, setSelectedLibrary] = useAtom(selectedLibraryAtom);

const { httpSnippetLanguage, httpSnippetLibrary, mosaicCodeViewerLanguage } = getConfigFor(
selectedLanguage,
selectedLibrary,
);

const [requestSample, setRequestSample] = React.useState<string | null>(null);
React.useEffect(() => {
let isStale = false;
let selectedCodeExampleOverride: string | undefined;

if (codeExampleOverrides.length > 0) {
const codeExampleOverride = codeExampleOverrides.find(override => {
if (override.label) {
return (
override.lang.toLowerCase() === httpSnippetLanguage && override.label.toLowerCase() === httpSnippetLibrary
);
}
return override.lang.toLowerCase() === httpSnippetLanguage;
});
if (codeExampleOverride) {
selectedCodeExampleOverride = codeExampleOverride.source;
}
return override.lang.toLowerCase() === httpSnippetLanguage;
});
if (codeExampleOverride) {
selectedCodeExampleOverride = codeExampleOverride.source;
}
}

if (selectedCodeExampleOverride) {
if (!isStale) {
if (selectedCodeExampleOverride) {
if (!isStale) {
setRequestSample(selectedCodeExampleOverride);
}
} else {
convertRequestToSample(httpSnippetLanguage, httpSnippetLibrary, request)
.then(example => {
if (!isStale) {
setRequestSample(example);
}
})
.catch(() => {
if (!isStale) {
setRequestSample(fallbackText);
}
});
}
} else {
convertRequestToSample(httpSnippetLanguage, httpSnippetLibrary, request)
.then(example => {
if (!isStale) {
setRequestSample(example);
}
})
.catch(() => {
if (!isStale) {
setRequestSample(fallbackText);
}
});
}


return () => {
isStale = true;
};
}, [request, httpSnippetLanguage, httpSnippetLibrary, codeExampleOverrides]);

const menuItems = useMemo(() => {
const items: MenuItems = Object.entries(requestSampleConfigs).map(([language, config]) => {
const hasLibraries = config.libraries && Object.keys(config.libraries).length > 0;
return {
id: language,
title: language,
isChecked: selectedLanguage === language,
onPress: hasLibraries
? undefined
: () => {
setSelectedLanguage(language);
setSelectedLibrary('');
},
children: config.libraries
? Object.keys(config.libraries).map(library => ({
id: `${language}-${library}`,
title: library,
isChecked: selectedLanguage === language && selectedLibrary === library,
onPress: () => {
return () => {
isStale = true;
};
}, [request, httpSnippetLanguage, httpSnippetLibrary, codeExampleOverrides]);

const menuItems = useMemo(() => {
const items: MenuItems = Object.entries(requestSampleConfigs).map(([language, config]) => {
const hasLibraries = config.libraries && Object.keys(config.libraries).length > 0;
return {
id: language,
title: language,
isChecked: selectedLanguage === language,
onPress: hasLibraries
? undefined
: () => {
setSelectedLanguage(language);
setSelectedLibrary(library);
setSelectedLibrary('');
},
}))
: undefined,
};
});

return items;
}, [selectedLanguage, selectedLibrary, setSelectedLanguage, setSelectedLibrary]);

return (
<Panel rounded={embeddedInMd ? undefined : true} isCollapsible={embeddedInMd}>
<Panel.Titlebar rightComponent={<CopyButton size="sm" copyValue={requestSample || ''} />}>
<Box ml={-2}>
<Menu
aria-label="Request Sample Language"
closeOnPress
items={menuItems}
renderTrigger={({ isOpen }) => (
<Button size="sm" iconRight="chevron-down" appearance="minimal" active={isOpen}>
Request Sample: {selectedLanguage} {selectedLibrary ? ` / ${selectedLibrary}` : ''}
</Button>
)}
/>
</Box>
</Panel.Titlebar>
children: config.libraries
? Object.keys(config.libraries).map(library => ({
id: `${language}-${library}`,
title: library,
isChecked: selectedLanguage === language && selectedLibrary === library,
onPress: () => {
setSelectedLanguage(language);
setSelectedLibrary(library);
},
}))
: undefined,
};
});

<Panel.Content p={0}>
{requestSample !== null && (
<CodeViewer
aria-label={requestSample}
noCopyButton
maxHeight="400px"
language={mosaicCodeViewerLanguage}
value={requestSample}
style={
embeddedInMd
? undefined
: // when not rendering in prose (markdown), reduce font size to be consistent with base UI
{
// @ts-expect-error react css typings do not allow for css variables...
'--fs-code': 12,
}
}
/>
)}
</Panel.Content>
</Panel>
);
});
return items;
}, [selectedLanguage, selectedLibrary, setSelectedLanguage, setSelectedLibrary]);

return (
<Panel rounded={embeddedInMd ? undefined : true} isCollapsible={embeddedInMd}>
<Panel.Titlebar rightComponent={<CopyButton size="sm" copyValue={requestSample || ''} />}>
<Box ml={-2}>
<Menu
aria-label="Request Sample Language"
closeOnPress
items={menuItems}
renderTrigger={({ isOpen }) => (
<Button size="sm" iconRight="chevron-down" appearance="minimal" active={isOpen}>
Request Sample: {selectedLanguage} {selectedLibrary ? ` / ${selectedLibrary}` : ''}
</Button>
)}
/>
</Box>
</Panel.Titlebar>

<Panel.Content p={0}>
{requestSample !== null && (
<CodeViewer
aria-label={requestSample}
noCopyButton
maxHeight="400px"
language={mosaicCodeViewerLanguage}
value={requestSample}
style={
embeddedInMd
? undefined
: // when not rendering in prose (markdown), reduce font size to be consistent with base UI
{
// @ts-expect-error react css typings do not allow for css variables...
'--fs-code': 12,
}
}
/>
)}
</Panel.Content>
</Panel>
);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { isHttpOperation } from '@stoplight/elements-core/utils/guards';
import { isPlainObject } from '@stoplight/json';
import { isString } from 'lodash';

export type CodeExampleOverride = {
/**
* Code sample language.
*/
lang: string;
/**
* Code sample label, for example Node or Python2.7, optional, lang is used by default
*/
label?: string;
/**
* Code sample source code
*/
source: string;
};

export const extractCodeExampleOverrides = (obj: unknown): CodeExampleOverride[] => {
if (!isHttpOperation(obj)) {
return [];
}

const codeExamples = obj['x-codeExamples'];
if (!Array.isArray(codeExamples)) {
return [];
}

return codeExamples.reduce((extracted, item) => {
if (isPlainObject(item) && isString(item['lang']) && isString(item['source'])) {
return {
lang: item['lang'],
label: isString(item['label']) ? item['label'] : undefined,
source: item['source'],
};
}

return extracted;
}, []);
};
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './RequestSamples';
export * from './extractCodeExampleOverrides';
13 changes: 5 additions & 8 deletions packages/elements-core/src/components/TryIt/TryIt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from 'react';
import { HttpMethodColors } from '../../constants';
import { isHttpOperation, isHttpWebhookOperation } from '../../utils/guards';
import { getServersToDisplay, getServerVariables } from '../../utils/http-spec/IServer';
import { RequestSamples } from '../RequestSamples';
import { extractCodeExampleOverrides, RequestSamples } from '../RequestSamples';
import { TryItAuth } from './Auth/Auth';
import { usePersistedSecuritySchemeWithValues } from './Auth/authentication-utils';
import { FormDataBody } from './Body/FormDataBody';
Expand Down Expand Up @@ -115,12 +115,7 @@ export const TryIt: React.FC<TryItProps> = ({
parameter => parameter.required && !parameterValuesWithDefaults[parameter.name],
);

let codeExampleOverrides = [];
if (isHttpOperation(httpOperation)) {
if ('x-codeExamples' in httpOperation) {
codeExampleOverrides = httpOperation['x-codeExamples'] as any[];
}
}
const codeExampleOverrides = extractCodeExampleOverrides(httpOperation);

const getValues = () =>
Object.keys(bodyParameterValues)
Expand Down Expand Up @@ -347,7 +342,9 @@ export const TryIt: React.FC<TryItProps> = ({
return (
<Box rounded="lg" overflowY="hidden">
{tryItPanelElem}
{requestData && embeddedInMd && <RequestSamples request={requestData} codeExampleOverrides={codeExampleOverrides} embeddedInMd />}
{requestData && embeddedInMd && (
<RequestSamples request={requestData} codeExampleOverrides={codeExampleOverrides} embeddedInMd />
)}
{response && !('error' in response) && <TryItResponse response={response} />}
{response && 'error' in response && <ResponseError state={response} />}
</Box>
Expand Down

0 comments on commit 1362807

Please sign in to comment.