insomnia/packages/insomnia-app/app/ui/components/wrapper-design.tsx

286 lines
8.2 KiB
TypeScript
Raw Normal View History

import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { Button, NoticeTable } from 'insomnia-components';
2021-07-22 23:04:56 +00:00
import React, { Fragment, PureComponent, ReactNode } from 'react';
2020-04-26 20:33:39 +00:00
import SwaggerUI from 'swagger-ui-react';
2021-07-22 23:04:56 +00:00
fixes 'previewHidden' of undefined error (#3409) * readability improvements and reduced indirection * adds type for handleShowModifyCookieModal * correctly types wrapperProps property, thereby fixing bug. if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined. Also, there's no reason to use `await` on `this.setState`. I didn't find any more of these in the codebase, I just found this one. * adds type for swaggerUiSpec * undoes lifting props to state almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it. I went ahead and measured it before and after this commit (using performance.now): before = [ 1.93500000750646, 1.149999996414408, 0.9499999869149178, 0.9950000094249845, 0.8650000090710819, 1.560000004246831, 1.5699999930802733, 0.8450000023003668, 1.4550000196322799, 1.3299999991431832, 1.3050000125076622, 1.4099999971222132, 1.3099999923724681, 1.3100000214762986, 1.1999999987892807, 1.0099999781232327, 0.830000004498288, 1.2449999921955168, 1.2500000011641532, 1.4349999837577343, ] after = [ 2.9400000057648867, 2.449999999953434, 2.33499999740161, 2.2849999950267375, 1.7700000025797635, 1.8149999959859997, 2.1249999990686774, 1.9150000007357448, 2.074999996693805, 1.9899999897461385, 2.0200000144541264, 2.869999996619299, 2.1450000058393925, 2.33499999740161, 2.130000008037314, 2.119999990100041, 2.144999976735562, 2.130000008037314, 2.380000009201467, 2.8999999922234565, ] > R.mean(before) > 1.2480000004870817 > R.mean(after) > 2.243749999080319 > R.median(before) > 1.2775000068359077 > R.median(after) > 2.137499992386438 So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance. This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 14:14:00 +00:00
import { parseApiSpec, ParsedApiSpec } from '../../common/api-specs';
import type { GlobalActivity } from '../../common/constants';
import { ACTIVITY_HOME, AUTOBIND_CFG } from '../../common/constants';
import { initializeSpectral, isLintError } from '../../common/spectral';
2021-07-22 23:04:56 +00:00
import type { ApiSpec } from '../../models/api-spec';
import * as models from '../../models/index';
import previewIcon from '../images/icn-eye.svg';
import { CodeEditor, UnconnectedCodeEditor } from './codemirror/code-editor';
import { ErrorBoundary } from './error-boundary';
import { PageLayout } from './page-layout';
import { SpecEditorSidebar } from './spec-editor/spec-editor-sidebar';
import { WorkspacePageHeader } from './workspace-page-header';
2021-07-22 23:04:56 +00:00
import type { WrapperProps } from './wrapper';
2020-04-26 20:33:39 +00:00
const spectral = initializeSpectral();
2020-04-26 20:33:39 +00:00
interface Props {
gitSyncDropdown: ReactNode;
handleActivityChange: (options: {workspaceId?: string; nextActivity: GlobalActivity}) => Promise<void>;
handleUpdateApiSpec: (s: ApiSpec) => Promise<void>;
wrapperProps: WrapperProps;
}
2020-04-26 20:33:39 +00:00
interface State {
lintMessages: {
message: string;
line: number;
type: 'error' | 'warning';
}[];
}
2020-04-26 20:33:39 +00:00
@autoBindMethodsForReact(AUTOBIND_CFG)
export class WrapperDesign extends PureComponent<Props, State> {
editor: UnconnectedCodeEditor | null = null;
debounceTimeout: NodeJS.Timeout | null = null;
2020-04-26 20:33:39 +00:00
constructor(props: Props) {
super(props);
this.state = {
lintMessages: [],
};
}
// Defining it here instead of in render() so it won't act as a changed prop
// when being passed to <CodeEditor> again
static lintOptions = {
delay: 1000,
};
_setEditorRef(n: UnconnectedCodeEditor) {
2020-04-26 20:33:39 +00:00
this.editor = n;
}
async _handleTogglePreview() {
const { activeWorkspace } = this.props.wrapperProps;
if (!activeWorkspace) {
return;
}
const workspaceId = activeWorkspace._id;
fixes 'previewHidden' of undefined error (#3409) * readability improvements and reduced indirection * adds type for handleShowModifyCookieModal * correctly types wrapperProps property, thereby fixing bug. if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined. Also, there's no reason to use `await` on `this.setState`. I didn't find any more of these in the codebase, I just found this one. * adds type for swaggerUiSpec * undoes lifting props to state almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it. I went ahead and measured it before and after this commit (using performance.now): before = [ 1.93500000750646, 1.149999996414408, 0.9499999869149178, 0.9950000094249845, 0.8650000090710819, 1.560000004246831, 1.5699999930802733, 0.8450000023003668, 1.4550000196322799, 1.3299999991431832, 1.3050000125076622, 1.4099999971222132, 1.3099999923724681, 1.3100000214762986, 1.1999999987892807, 1.0099999781232327, 0.830000004498288, 1.2449999921955168, 1.2500000011641532, 1.4349999837577343, ] after = [ 2.9400000057648867, 2.449999999953434, 2.33499999740161, 2.2849999950267375, 1.7700000025797635, 1.8149999959859997, 2.1249999990686774, 1.9150000007357448, 2.074999996693805, 1.9899999897461385, 2.0200000144541264, 2.869999996619299, 2.1450000058393925, 2.33499999740161, 2.130000008037314, 2.119999990100041, 2.144999976735562, 2.130000008037314, 2.380000009201467, 2.8999999922234565, ] > R.mean(before) > 1.2480000004870817 > R.mean(after) > 2.243749999080319 > R.median(before) > 1.2775000068359077 > R.median(after) > 2.137499992386438 So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance. This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 14:14:00 +00:00
const previewHidden = Boolean(this.props.wrapperProps.activeWorkspaceMeta?.previewHidden);
await models.workspaceMeta.updateByParentId(workspaceId, { previewHidden: !previewHidden });
2020-04-26 20:33:39 +00:00
}
_handleOnChange(v: string) {
const {
wrapperProps: { activeApiSpec },
handleUpdateApiSpec,
} = this.props;
if (!activeApiSpec) {
return;
}
// TODO: this seems strange, should the timeout be set and cleared on every change??
2020-04-26 20:33:39 +00:00
// Debounce the update because these specs can get pretty large
if (this.debounceTimeout !== null) {
clearTimeout(this.debounceTimeout);
}
2020-04-26 20:33:39 +00:00
this.debounceTimeout = setTimeout(async () => {
await handleUpdateApiSpec({ ...activeApiSpec, contents: v });
}, 500);
}
_handleSetSelection(chStart: number, chEnd: number, lineStart: number, lineEnd: number) {
const editor = this.editor;
if (!editor) {
return;
}
editor.scrollToSelection(chStart, chEnd, lineStart, lineEnd);
2020-04-26 20:33:39 +00:00
}
_handleLintClick(notice) {
// TODO: Export Notice from insomnia-components and use here, instead of {}
2020-04-26 20:33:39 +00:00
const { start, end } = notice._range;
2020-04-26 20:33:39 +00:00
this._handleSetSelection(start.character, end.character, start.line, end.line);
}
async _reLint() {
const { activeApiSpec } = this.props.wrapperProps;
// Lint only if spec has content
if (activeApiSpec && activeApiSpec.contents.length !== 0) {
const results = (await spectral.run(activeApiSpec.contents)).filter(isLintError);
2020-04-26 20:33:39 +00:00
this.setState({
lintMessages: results.map(r => ({
type: r.severity === 0 ? 'error' : 'warning',
message: `${r.code} ${r.message}`,
line: r.range.start.line,
// Attach range that will be returned to our click handler
_range: r.range,
})),
});
} else {
this.setState({
lintMessages: [],
});
}
}
_handleBreadcrumb() {
2020-04-26 20:33:39 +00:00
this.props.wrapperProps.handleSetActiveActivity(ACTIVITY_HOME);
}
async componentDidMount() {
await this._reLint();
}
componentDidUpdate(prevProps: Props) {
const { activeApiSpec } = this.props.wrapperProps;
// Re-lint if content changed
if (activeApiSpec?.contents !== prevProps.wrapperProps.activeApiSpec?.contents) {
2020-04-26 20:33:39 +00:00
this._reLint();
}
}
_renderEditor() {
const { activeApiSpec, settings } = this.props.wrapperProps;
const { lintMessages } = this.state;
if (!activeApiSpec) {
return null;
}
return (
<div className="column tall theme--pane__body">
<div className="tall">
<CodeEditor
manualPrettify
ref={this._setEditorRef}
fontSize={settings.editorFontSize}
indentSize={settings.editorIndentSize}
lineWrapping={settings.lineWrapping}
keyMap={settings.editorKeyMap}
lintOptions={WrapperDesign.lintOptions}
mode="openapi"
defaultValue={activeApiSpec.contents}
onChange={this._handleOnChange}
uniquenessKey={activeApiSpec._id}
/>
</div>
{lintMessages.length > 0 && (
<NoticeTable notices={lintMessages} onClick={this._handleLintClick} />
)}
</div>
);
}
2020-04-26 20:33:39 +00:00
_renderPreview() {
fixes 'previewHidden' of undefined error (#3409) * readability improvements and reduced indirection * adds type for handleShowModifyCookieModal * correctly types wrapperProps property, thereby fixing bug. if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined. Also, there's no reason to use `await` on `this.setState`. I didn't find any more of these in the codebase, I just found this one. * adds type for swaggerUiSpec * undoes lifting props to state almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it. I went ahead and measured it before and after this commit (using performance.now): before = [ 1.93500000750646, 1.149999996414408, 0.9499999869149178, 0.9950000094249845, 0.8650000090710819, 1.560000004246831, 1.5699999930802733, 0.8450000023003668, 1.4550000196322799, 1.3299999991431832, 1.3050000125076622, 1.4099999971222132, 1.3099999923724681, 1.3100000214762986, 1.1999999987892807, 1.0099999781232327, 0.830000004498288, 1.2449999921955168, 1.2500000011641532, 1.4349999837577343, ] after = [ 2.9400000057648867, 2.449999999953434, 2.33499999740161, 2.2849999950267375, 1.7700000025797635, 1.8149999959859997, 2.1249999990686774, 1.9150000007357448, 2.074999996693805, 1.9899999897461385, 2.0200000144541264, 2.869999996619299, 2.1450000058393925, 2.33499999740161, 2.130000008037314, 2.119999990100041, 2.144999976735562, 2.130000008037314, 2.380000009201467, 2.8999999922234565, ] > R.mean(before) > 1.2480000004870817 > R.mean(after) > 2.243749999080319 > R.median(before) > 1.2775000068359077 > R.median(after) > 2.137499992386438 So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance. This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 14:14:00 +00:00
const { activeApiSpec, activeWorkspaceMeta } = this.props.wrapperProps;
2020-04-26 20:33:39 +00:00
if (!activeApiSpec || activeWorkspaceMeta?.previewHidden) {
return null;
}
2020-04-26 20:33:39 +00:00
fixes 'previewHidden' of undefined error (#3409) * readability improvements and reduced indirection * adds type for handleShowModifyCookieModal * correctly types wrapperProps property, thereby fixing bug. if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined. Also, there's no reason to use `await` on `this.setState`. I didn't find any more of these in the codebase, I just found this one. * adds type for swaggerUiSpec * undoes lifting props to state almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it. I went ahead and measured it before and after this commit (using performance.now): before = [ 1.93500000750646, 1.149999996414408, 0.9499999869149178, 0.9950000094249845, 0.8650000090710819, 1.560000004246831, 1.5699999930802733, 0.8450000023003668, 1.4550000196322799, 1.3299999991431832, 1.3050000125076622, 1.4099999971222132, 1.3099999923724681, 1.3100000214762986, 1.1999999987892807, 1.0099999781232327, 0.830000004498288, 1.2449999921955168, 1.2500000011641532, 1.4349999837577343, ] after = [ 2.9400000057648867, 2.449999999953434, 2.33499999740161, 2.2849999950267375, 1.7700000025797635, 1.8149999959859997, 2.1249999990686774, 1.9150000007357448, 2.074999996693805, 1.9899999897461385, 2.0200000144541264, 2.869999996619299, 2.1450000058393925, 2.33499999740161, 2.130000008037314, 2.119999990100041, 2.144999976735562, 2.130000008037314, 2.380000009201467, 2.8999999922234565, ] > R.mean(before) > 1.2480000004870817 > R.mean(after) > 2.243749999080319 > R.median(before) > 1.2775000068359077 > R.median(after) > 2.137499992386438 So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance. This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 14:14:00 +00:00
let swaggerUiSpec: ParsedApiSpec['contents'] | null = null;
2020-04-26 20:33:39 +00:00
try {
swaggerUiSpec = parseApiSpec(activeApiSpec.contents).contents;
} catch (err) {}
if (!swaggerUiSpec) {
2020-04-26 20:33:39 +00:00
swaggerUiSpec = {};
}
return (
<div id="swagger-ui-wrapper">
<ErrorBoundary
invalidationKey={activeApiSpec.contents}
renderError={() => (
<div className="text-left margin pad">
<h3>An error occurred while trying to render Swagger UI 😢</h3>
<p>
This preview will automatically refresh, once you have a valid specification that
can be previewed.
</p>
</div>
2021-08-07 08:03:56 +00:00
)}
>
<SwaggerUI
spec={swaggerUiSpec}
supportedSubmitMethods={[
'get',
'put',
'post',
'delete',
'options',
'head',
'patch',
'trace',
]}
/>
</ErrorBoundary>
</div>
);
}
_renderPageHeader() {
const { wrapperProps, gitSyncDropdown, handleActivityChange } = this.props;
fixes 'previewHidden' of undefined error (#3409) * readability improvements and reduced indirection * adds type for handleShowModifyCookieModal * correctly types wrapperProps property, thereby fixing bug. if you add `console.log({ previewHidden, propsOne: this.props.wrapperProps.activeWorkspaceMeta });` to the third line of `_renderPreview()` you'll see that `activeWorkspaceMeta` is indeed, sometimes, `undefined. Also, there's no reason to use `await` on `this.setState`. I didn't find any more of these in the codebase, I just found this one. * adds type for swaggerUiSpec * undoes lifting props to state almost always, this is done for performance reasons, but I removed it the app is working pretty quick-and-snappy for me without needing to introduced duplicated application state and keep track of it. I went ahead and measured it before and after this commit (using performance.now): before = [ 1.93500000750646, 1.149999996414408, 0.9499999869149178, 0.9950000094249845, 0.8650000090710819, 1.560000004246831, 1.5699999930802733, 0.8450000023003668, 1.4550000196322799, 1.3299999991431832, 1.3050000125076622, 1.4099999971222132, 1.3099999923724681, 1.3100000214762986, 1.1999999987892807, 1.0099999781232327, 0.830000004498288, 1.2449999921955168, 1.2500000011641532, 1.4349999837577343, ] after = [ 2.9400000057648867, 2.449999999953434, 2.33499999740161, 2.2849999950267375, 1.7700000025797635, 1.8149999959859997, 2.1249999990686774, 1.9150000007357448, 2.074999996693805, 1.9899999897461385, 2.0200000144541264, 2.869999996619299, 2.1450000058393925, 2.33499999740161, 2.130000008037314, 2.119999990100041, 2.144999976735562, 2.130000008037314, 2.380000009201467, 2.8999999922234565, ] > R.mean(before) > 1.2480000004870817 > R.mean(after) > 2.243749999080319 > R.median(before) > 1.2775000068359077 > R.median(after) > 2.137499992386438 So basically, considering a 16ms render rate (i.e. 60hz), 1ms saved by lifting props to state makes no difference in application performance. This is committed separately so that if there's any reason we want to keep the prior implementation, we can just still do so.
2021-05-24 14:14:00 +00:00
const previewHidden = Boolean(wrapperProps.activeWorkspaceMeta?.previewHidden);
return (
<WorkspacePageHeader
wrapperProps={wrapperProps}
handleActivityChange={handleActivityChange}
gridRight={
<Fragment>
<Button variant="contained" onClick={this._handleTogglePreview}>
<img src={previewIcon} alt="Preview" width="15" />
&nbsp; {previewHidden ? 'Preview: Off' : 'Preview: On'}
</Button>
{gitSyncDropdown}
</Fragment>
}
/>
);
}
_renderPageSidebar() {
const { activeApiSpec } = this.props.wrapperProps;
if (!activeApiSpec) {
return null;
}
return (
<ErrorBoundary
invalidationKey={activeApiSpec.contents}
renderError={() => (
<div className="text-left margin pad">
<h4>An error occurred while trying to render your spec's navigation. 😢</h4>
<p>
This navigation will automatically refresh, once you have a valid specification that
can be rendered.
</p>
</div>
2021-08-07 08:03:56 +00:00
)}
>
<SpecEditorSidebar apiSpec={activeApiSpec} handleSetSelection={this._handleSetSelection} />
</ErrorBoundary>
);
}
render() {
2020-04-26 20:33:39 +00:00
return (
<PageLayout
wrapperProps={this.props.wrapperProps}
renderPageHeader={this._renderPageHeader}
renderPaneOne={this._renderEditor}
renderPaneTwo={this._renderPreview}
renderPageSidebar={this._renderPageSidebar}
2020-04-26 20:33:39 +00:00
/>
);
}
}