mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
modals->fc transform remaining (#5278)
* remaining * inline * state => * tweaks * simplfy wrapper * extract prompt modal * fix hotkey * remove duplicate modal * isJSON as object arg * remove dispatch * tweaks * more tweaks * fix nunjucks modal * last tweaks
This commit is contained in:
parent
02c1c130ff
commit
92b8b3baf1
@ -1217,7 +1217,7 @@ export class UnconnectedCodeEditor extends Component<CodeEditorProps, State> {
|
||||
|
||||
_showFilterHelp() {
|
||||
const isJSON = UnconnectedCodeEditor._isJSON(this.props.mode);
|
||||
showModal(FilterHelpModal, isJSON);
|
||||
showModal(FilterHelpModal, { isJSON });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -89,7 +89,7 @@ export const RequestActionsDropdown = forwardRef<DropdownHandle, Props>(({
|
||||
}, [handleDuplicateRequest, request]);
|
||||
|
||||
const generateCode = useCallback(() => {
|
||||
showModal(GenerateCodeModal, request);
|
||||
showModal(GenerateCodeModal, { request });
|
||||
}, [request]);
|
||||
|
||||
const copyAsCurl = useCallback(async () => {
|
||||
|
@ -103,10 +103,6 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
models.requestGroup.remove(requestGroup);
|
||||
}, [requestGroup]);
|
||||
|
||||
const handleEditEnvironment = useCallback(() => {
|
||||
showModal(EnvironmentEditModal, requestGroup);
|
||||
}, [requestGroup]);
|
||||
|
||||
const handlePluginClick = useCallback(async ({ label, plugin, action }: RequestGroupAction) => {
|
||||
setLoadingActions({ ...loadingActions, [label]: true });
|
||||
|
||||
@ -174,7 +170,7 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
<i className="fa fa-copy" /> Duplicate
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownItem onClick={handleEditEnvironment}>
|
||||
<DropdownItem onClick={() => showModal(EnvironmentEditModal, { requestGroup })}>
|
||||
<i className="fa fa-code" /> Environment
|
||||
</DropdownItem>
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent, ReactNode } from 'react';
|
||||
import React, { forwardRef, ReactNode, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@ -14,90 +12,61 @@ export interface AlertModalOptions {
|
||||
okLabel?: string;
|
||||
onConfirm?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
type State = Omit<AlertModalOptions, 'onConfirm'>;
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class AlertModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
export interface AlertModalHandle {
|
||||
show: (options: AlertModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const AlertModal = forwardRef<AlertModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<AlertModalOptions>({
|
||||
title: '',
|
||||
message: '',
|
||||
addCancel: false,
|
||||
okLabel: '',
|
||||
};
|
||||
});
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
_cancel: HTMLButtonElement | null = null;
|
||||
_ok: HTMLButtonElement | null = null;
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: ({ title, message, addCancel, onConfirm, okLabel }) => {
|
||||
setState({
|
||||
title,
|
||||
message,
|
||||
addCancel,
|
||||
okLabel,
|
||||
onConfirm,
|
||||
});
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
_okCallback?: (value: void | PromiseLike<void>) => void;
|
||||
_okCallback2: AlertModalOptions['onConfirm'];
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_handleOk() {
|
||||
this.hide();
|
||||
|
||||
// TODO: unsound non-null assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this._okCallback!();
|
||||
|
||||
if (typeof this._okCallback2 === 'function') {
|
||||
this._okCallback2();
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
setCancelRef(cancel: HTMLButtonElement) {
|
||||
this._cancel = cancel;
|
||||
}
|
||||
|
||||
setOkRef(ok: HTMLButtonElement) {
|
||||
this._ok = ok;
|
||||
}
|
||||
|
||||
show({ title, message, addCancel, onConfirm, okLabel }: AlertModalOptions) {
|
||||
this.setState({
|
||||
title,
|
||||
message,
|
||||
addCancel,
|
||||
okLabel,
|
||||
});
|
||||
this.modal?.show();
|
||||
// Need to do this after render because modal focuses itself too
|
||||
setTimeout(() => {
|
||||
this._cancel?.focus();
|
||||
}, 100);
|
||||
this._okCallback2 = onConfirm;
|
||||
return new Promise<void>(resolve => {
|
||||
this._okCallback = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, title, addCancel, okLabel } = this.state;
|
||||
return (
|
||||
<Modal ref={this._setModalRef} skinny>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody className="wide pad">{message}</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
{addCancel ? (
|
||||
<button className="btn" ref={this.setCancelRef} onClick={this.hide}>
|
||||
Cancel
|
||||
</button>
|
||||
) : null}
|
||||
<button className="btn" ref={this.setOkRef} onClick={this._handleOk}>
|
||||
{okLabel || 'Ok'}
|
||||
const { message, title, addCancel, okLabel } = state;
|
||||
return (
|
||||
<Modal ref={modalRef} skinny>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody className="wide pad">{message}</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
{addCancel ? (
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
) : null}
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
modalRef.current?.hide();
|
||||
if (typeof state.onConfirm === 'function') {
|
||||
state.onConfirm();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{okLabel || 'Ok'}
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
AlertModal.displayName = 'AlertModal';
|
||||
|
@ -1,118 +1,80 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
|
||||
interface State {
|
||||
title: string;
|
||||
message: string;
|
||||
yesText: string;
|
||||
noText: string;
|
||||
loading: boolean;
|
||||
onDone?: (success: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
interface AskModalOptions {
|
||||
export interface AskModalOptions {
|
||||
title?: string;
|
||||
message?: string;
|
||||
onDone?: (success: boolean) => Promise<void>;
|
||||
yesText?: string;
|
||||
noText?: string;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class AskModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
export interface AskModalHandle {
|
||||
show: (options: AskModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const AskModal = forwardRef<AskModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<State>({
|
||||
title: '',
|
||||
message: '',
|
||||
yesText: 'Yes',
|
||||
noText: 'No',
|
||||
loading: false,
|
||||
};
|
||||
onDone: async () => { },
|
||||
});
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
yesButton: HTMLButtonElement | null = null;
|
||||
|
||||
_doneCallback: AskModalOptions['onDone'];
|
||||
_promiseCallback: (value: boolean | PromiseLike<boolean>) => void = () => {};
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_setYesButtonRef(yesButton: HTMLButtonElement) {
|
||||
this.yesButton = yesButton;
|
||||
}
|
||||
|
||||
async _handleYes() {
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
// Wait for the callback to finish before closing
|
||||
await this._doneCallback?.(true);
|
||||
|
||||
this._promiseCallback(true);
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
async _handleNo() {
|
||||
this.hide();
|
||||
await this._doneCallback?.(false);
|
||||
|
||||
this._promiseCallback(false);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
show({ title, message, onDone, yesText, noText }: AskModalOptions = {}) {
|
||||
this._doneCallback = onDone;
|
||||
this.setState({
|
||||
title: title || 'Confirm',
|
||||
message: message || 'No message provided',
|
||||
yesText: yesText || 'Yes',
|
||||
noText: noText || 'No',
|
||||
loading: false,
|
||||
});
|
||||
this.modal?.show();
|
||||
|
||||
setTimeout(() => {
|
||||
this.yesButton?.focus();
|
||||
}, 100);
|
||||
|
||||
return new Promise<boolean>(resolve => {
|
||||
this._promiseCallback = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, title, yesText, noText, loading } = this.state;
|
||||
return (
|
||||
<Modal noEscape ref={this._setModalRef}>
|
||||
<ModalHeader>{title || 'Confirm?'}</ModalHeader>
|
||||
<ModalBody className="wide pad">{message}</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
<button className="btn" onClick={this._handleNo}>
|
||||
{noText}
|
||||
</button>
|
||||
<button
|
||||
ref={this._setYesButtonRef}
|
||||
className="btn"
|
||||
onClick={this._handleYes}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && <i className="fa fa-refresh fa-spin" />} {yesText}
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: ({ title, message, onDone, yesText, noText }) => {
|
||||
setState({
|
||||
title: title || 'Confirm',
|
||||
message: message || 'No message provided',
|
||||
yesText: yesText || 'Yes',
|
||||
noText: noText || 'No',
|
||||
onDone,
|
||||
});
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
const { message, title, yesText, noText, onDone } = state;
|
||||
return (
|
||||
<Modal ref={modalRef} noEscape>
|
||||
<ModalHeader>{title || 'Confirm?'}</ModalHeader>
|
||||
<ModalBody className="wide pad">{message}</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
modalRef.current?.hide();
|
||||
onDone?.(false);
|
||||
}}
|
||||
>
|
||||
{noText}
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
modalRef.current?.hide();
|
||||
onDone?.(true);
|
||||
}}
|
||||
>
|
||||
{yesText}
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
AskModal.displayName = 'AskModal';
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { NunjucksEnabledProvider } from '../../context/nunjucks/nunjucks-enabled-context';
|
||||
import { CopyButton } from '../base/copy-button';
|
||||
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 { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@ -24,7 +22,7 @@ const MODES: Record<string, string> = {
|
||||
'text/html': 'HTML',
|
||||
};
|
||||
|
||||
interface State {
|
||||
interface CodePromptModalOptions {
|
||||
title: string;
|
||||
defaultValue: string;
|
||||
submitName: string;
|
||||
@ -34,11 +32,17 @@ interface State {
|
||||
hideMode: boolean;
|
||||
enableRender: boolean;
|
||||
showCopyButton: boolean;
|
||||
onChange: (value: string) => void;
|
||||
onModeChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class CodePromptModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
export interface CodePromptModalHandle {
|
||||
show: (options: CodePromptModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const CodePromptModal = forwardRef<CodePromptModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<CodePromptModalOptions>({
|
||||
title: 'Not Set',
|
||||
defaultValue: '',
|
||||
submitName: 'Not Set',
|
||||
@ -48,150 +52,117 @@ export class CodePromptModal extends PureComponent<{}, State> {
|
||||
hideMode: false,
|
||||
enableRender: false,
|
||||
showCopyButton: false,
|
||||
};
|
||||
onChange: () => { },
|
||||
onModeChange: () => { },
|
||||
});
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
_onModeChange: Function = () => {};
|
||||
_onChange: Function = () => {};
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
const realMode = typeof options.mode === 'string' ? options.mode : 'text/plain';
|
||||
setState(state => ({
|
||||
...options,
|
||||
mode: realMode || state.mode || 'text/plain',
|
||||
}));
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
const {
|
||||
submitName,
|
||||
title,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
hint,
|
||||
mode,
|
||||
hideMode,
|
||||
enableRender,
|
||||
showCopyButton,
|
||||
onChange,
|
||||
} = state;
|
||||
|
||||
_handleChange(value: any) {
|
||||
this._onChange(value);
|
||||
}
|
||||
|
||||
_handleChangeMode(mode: any) {
|
||||
this.setState({ mode });
|
||||
this._onModeChange?.(mode);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
show(options: any) {
|
||||
const {
|
||||
title,
|
||||
defaultValue,
|
||||
submitName,
|
||||
placeholder,
|
||||
hint,
|
||||
mode,
|
||||
hideMode,
|
||||
enableRender,
|
||||
onChange,
|
||||
onModeChange,
|
||||
showCopyButton,
|
||||
} = options;
|
||||
this._onChange = onChange;
|
||||
this._onModeChange = onModeChange;
|
||||
const realMode = typeof mode === 'string' ? mode : 'text/plain';
|
||||
this.setState({
|
||||
title,
|
||||
defaultValue,
|
||||
submitName,
|
||||
placeholder,
|
||||
hint,
|
||||
enableRender,
|
||||
hideMode,
|
||||
showCopyButton,
|
||||
mode: realMode || this.state.mode || 'text/plain',
|
||||
});
|
||||
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
} = this.props;
|
||||
const {
|
||||
submitName,
|
||||
title,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
hint,
|
||||
mode,
|
||||
hideMode,
|
||||
enableRender,
|
||||
showCopyButton,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalBody
|
||||
noScroll
|
||||
className="wide tall"
|
||||
style={
|
||||
showCopyButton
|
||||
? {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr)',
|
||||
gridTemplateRows: 'auto minmax(0, 1fr)',
|
||||
}
|
||||
: {
|
||||
minHeight: '10rem',
|
||||
}
|
||||
}
|
||||
>
|
||||
<NunjucksEnabledProvider disable={!enableRender}>
|
||||
{showCopyButton ? (
|
||||
<div className="pad-top-sm pad-right-sm">
|
||||
<CopyButton content={defaultValue} className="pull-right" />
|
||||
</div>
|
||||
) : null}
|
||||
{mode === 'text/x-markdown' ? (
|
||||
<div className="pad-sm tall">
|
||||
<MarkdownEditor
|
||||
tall
|
||||
return (
|
||||
<Modal ref={modalRef} tall>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalBody
|
||||
noScroll
|
||||
className="wide tall"
|
||||
style={
|
||||
showCopyButton
|
||||
? {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr)',
|
||||
gridTemplateRows: 'auto minmax(0, 1fr)',
|
||||
}
|
||||
: {
|
||||
minHeight: '10rem',
|
||||
}
|
||||
}
|
||||
>
|
||||
<NunjucksEnabledProvider disable={!enableRender}>
|
||||
{showCopyButton ? (
|
||||
<div className="pad-top-sm pad-right-sm">
|
||||
<CopyButton content={defaultValue} className="pull-right" />
|
||||
</div>
|
||||
) : null}
|
||||
{mode === 'text/x-markdown' ? (
|
||||
<div className="pad-sm tall">
|
||||
<MarkdownEditor
|
||||
tall
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="pad-sm pad-bottom tall">
|
||||
<div className="form-control form-control--outlined form-control--tall tall">
|
||||
<CodeEditor
|
||||
hideLineNumbers
|
||||
manualPrettify
|
||||
className="tall"
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={this._handleChange}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
enableNunjucks
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="pad-sm pad-bottom tall">
|
||||
<div className="form-control form-control--outlined form-control--tall tall">
|
||||
<CodeEditor
|
||||
hideLineNumbers
|
||||
manualPrettify
|
||||
className="tall"
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
onChange={this._handleChange}
|
||||
mode={mode}
|
||||
enableNunjucks
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</NunjucksEnabledProvider>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!hideMode ? (
|
||||
<Dropdown>
|
||||
<DropdownButton className="btn btn--clicky margin-left-sm">
|
||||
</div>
|
||||
)}
|
||||
</NunjucksEnabledProvider>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!hideMode ? (
|
||||
<Dropdown>
|
||||
<DropdownButton className="btn btn--clicky margin-left-sm">
|
||||
{MODES[mode]}
|
||||
<i className="fa fa-caret-down space-left" />
|
||||
</DropdownButton>
|
||||
<DropdownDivider>Editor Syntax</DropdownDivider>
|
||||
{Object.keys(MODES).map(mode => (
|
||||
<DropdownItem
|
||||
key={mode}
|
||||
onClick={() => {
|
||||
setState(state => ({ ...state, mode }));
|
||||
state.onModeChange?.(mode);
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-code" />
|
||||
{MODES[mode]}
|
||||
<i className="fa fa-caret-down space-left" />
|
||||
</DropdownButton>
|
||||
<DropdownDivider>Editor Syntax</DropdownDivider>
|
||||
{Object.keys(MODES).map(mode => (
|
||||
<DropdownItem key={mode} onClick={() => this._handleChangeMode(mode)}>
|
||||
<i className="fa fa-code" />
|
||||
{MODES[mode]}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
) : null}
|
||||
<div className="margin-left faint italic txt-sm">{hint ? `* ${hint}` : ''}</div>
|
||||
<button className="btn" onClick={this.hide}>
|
||||
{submitName || 'Submit'}
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
) : null}
|
||||
<div className="margin-left faint italic txt-sm">{hint ? `* ${hint}` : ''}</div>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
{submitName || 'Submit'}
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
CodePromptModal.displayName = 'CodePromptModal';
|
||||
|
@ -1,112 +1,86 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import * as models from '../../../models/index';
|
||||
import { RequestGroup } from '../../../models/request-group';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
import { EnvironmentEditor } from '../editors/environment-editor';
|
||||
|
||||
interface Props {
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
interface State {
|
||||
requestGroup: RequestGroup | null;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class EnvironmentEditModal extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
export interface EnvironmentEditModalOptions {
|
||||
requestGroup: RequestGroup;
|
||||
}
|
||||
export interface EnvironmentEditModalHandle {
|
||||
show: (options: EnvironmentEditModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const EnvironmentEditModal = forwardRef<EnvironmentEditModalHandle, ModalProps>((props, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const editorRef = useRef<EnvironmentEditor>(null);
|
||||
const [state, setState] = useState<State>({
|
||||
requestGroup: null,
|
||||
isValid: true,
|
||||
};
|
||||
});
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
_envEditor: EnvironmentEditor | null = null;
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_setEditorRef(envEditor: EnvironmentEditor) {
|
||||
this._envEditor = envEditor;
|
||||
}
|
||||
|
||||
_saveChanges() {
|
||||
if (!this._envEditor?.isValid()) {
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: ({ requestGroup }) => {
|
||||
setState(state => ({ ...state, requestGroup }));
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
const didChange = () => {
|
||||
const isValid = editorRef.current?.isValid() || false;
|
||||
setState({ isValid, requestGroup });
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
let patch;
|
||||
|
||||
try {
|
||||
const data = this._envEditor.getValue();
|
||||
|
||||
patch = {
|
||||
environment: data && data.object,
|
||||
environmentPropertyOrder: data && data.propertyOrder,
|
||||
};
|
||||
const data = editorRef.current?.getValue();
|
||||
if (state.requestGroup && data) {
|
||||
models.requestGroup.update(state.requestGroup, {
|
||||
environment: data.object,
|
||||
environmentPropertyOrder: data.propertyOrder,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// Invalid JSON probably
|
||||
return;
|
||||
}
|
||||
|
||||
const { requestGroup } = this.state;
|
||||
this.props.onChange(Object.assign({}, requestGroup, patch));
|
||||
}
|
||||
|
||||
_didChange() {
|
||||
this._saveChanges();
|
||||
|
||||
const isValid = Boolean(this._envEditor?.isValid());
|
||||
|
||||
if (this.state.isValid !== isValid) {
|
||||
this.setState({ isValid });
|
||||
}
|
||||
}
|
||||
|
||||
show(requestGroup: RequestGroup) {
|
||||
this.setState({ requestGroup });
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
...extraProps
|
||||
} = this.props;
|
||||
const { requestGroup, isValid } = this.state;
|
||||
const environmentInfo = {
|
||||
object: requestGroup ? requestGroup.environment : {},
|
||||
propertyOrder: requestGroup && requestGroup.environmentPropertyOrder,
|
||||
};
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall {...extraProps}>
|
||||
<ModalHeader>Environment Overrides (JSON Format)</ModalHeader>
|
||||
<ModalBody noScroll className="pad-top-sm">
|
||||
<EnvironmentEditor
|
||||
ref={this._setEditorRef}
|
||||
key={requestGroup ? requestGroup._id : 'n/a'}
|
||||
environmentInfo={environmentInfo}
|
||||
didChange={this._didChange}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left italic txt-sm">
|
||||
* Used to override data in the global environment
|
||||
</div>
|
||||
<button className="btn" disabled={!isValid} onClick={this.hide}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
const { requestGroup, isValid } = state;
|
||||
const environmentInfo = {
|
||||
object: requestGroup ? requestGroup.environment : {},
|
||||
propertyOrder: requestGroup && requestGroup.environmentPropertyOrder,
|
||||
};
|
||||
return (
|
||||
<Modal ref={modalRef} tall {...props}>
|
||||
<ModalHeader>Environment Overrides (JSON Format)</ModalHeader>
|
||||
<ModalBody noScroll className="pad-top-sm">
|
||||
<EnvironmentEditor
|
||||
ref={editorRef}
|
||||
key={requestGroup ? requestGroup._id : 'n/a'}
|
||||
environmentInfo={environmentInfo}
|
||||
didChange={didChange}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left italic txt-sm">
|
||||
* Used to override data in the global environment
|
||||
</div>
|
||||
<button className="btn" disabled={!isValid} onClick={() => modalRef.current?.hide()}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal >
|
||||
);
|
||||
});
|
||||
EnvironmentEditModal.displayName = 'EnvironmentEditModal';
|
||||
|
@ -1,92 +1,67 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
|
||||
// NOTE: this is only used by the plugin api
|
||||
export interface ErrorModalOptions {
|
||||
title?: string;
|
||||
error?: Error | null;
|
||||
addCancel?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class ErrorModal extends PureComponent<{}, ErrorModalOptions> {
|
||||
modal: ModalHandle | null = null;
|
||||
_okCallback: (value?: unknown) => void = () => {};
|
||||
|
||||
state: ErrorModalOptions = {
|
||||
export interface ErrorModalHandle {
|
||||
show: (options: ErrorModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const ErrorModal = forwardRef<ErrorModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<ErrorModalOptions>({
|
||||
title: '',
|
||||
error: null,
|
||||
message: '',
|
||||
addCancel: false,
|
||||
};
|
||||
});
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_handleOk() {
|
||||
this.hide();
|
||||
|
||||
this._okCallback();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
show(options: ErrorModalOptions = {}) {
|
||||
const { title, error, addCancel, message } = options;
|
||||
this.setState({
|
||||
title,
|
||||
error,
|
||||
addCancel,
|
||||
message,
|
||||
});
|
||||
|
||||
this.modal?.show();
|
||||
|
||||
console.log('[ErrorModal]', error);
|
||||
return new Promise(resolve => {
|
||||
this._okCallback = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error, title, addCancel } = this.state;
|
||||
const message = this.state.message || error?.message;
|
||||
return (
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
{message ? <div className="notice error pre">{message}</div> : null}
|
||||
{error && (
|
||||
<details>
|
||||
<summary>Stack trace</summary>
|
||||
<pre className="pad-top-sm force-wrap selectable">
|
||||
<code className="wide">{error.stack}</code>
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
{addCancel ? (
|
||||
<button className="btn" onClick={this.hide}>
|
||||
Cancel
|
||||
</button>
|
||||
) : null}
|
||||
<button className="btn" onClick={this._handleOk}>
|
||||
Ok
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
setState(options);
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
const { error, title, addCancel } = state;
|
||||
const message = state.message || error?.message;
|
||||
return (
|
||||
<Modal ref={modalRef}>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
{message ? <div className="notice error pre">{message}</div> : null}
|
||||
{error && (
|
||||
<details>
|
||||
<summary>Stack trace</summary>
|
||||
<pre className="pad-top-sm force-wrap selectable">
|
||||
<code className="wide">{error.stack}</code>
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
{addCancel ? (
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
) : null}
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Ok
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
ErrorModal.displayName = 'ErrorModal';
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import * as models from '../../../models';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
|
||||
import { isRequest, Request } from '../../../models/request';
|
||||
import { isRequestGroup, RequestGroup } from '../../../models/request-group';
|
||||
import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-request';
|
||||
import { RootState } from '../../redux/modules';
|
||||
import { exportRequestsToFile } from '../../redux/modules/global';
|
||||
import { selectSidebarChildren } from '../../redux/sidebar-selectors';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
@ -25,104 +22,26 @@ export interface Node {
|
||||
selectedRequests: number;
|
||||
}
|
||||
|
||||
type Props = ModalProps & ReturnType<typeof mapStateToProps>;
|
||||
|
||||
interface State {
|
||||
export interface State {
|
||||
treeRoot: Node | null;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class ExportRequestsModalClass extends PureComponent<Props, State> {
|
||||
modal: ModalHandle | null = null;
|
||||
|
||||
state: State = {
|
||||
treeRoot: null,
|
||||
};
|
||||
|
||||
setModalRef(modal: ModalHandle) {
|
||||
if (modal != null) {
|
||||
this.modal = modal;
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.modal?.show();
|
||||
this.createTree();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
handleExport() {
|
||||
const { treeRoot } = this.state;
|
||||
|
||||
if (treeRoot == null || treeRoot.selectedRequests === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exportedRequestIds = this.getSelectedRequestIds(treeRoot);
|
||||
exportRequestsToFile(exportedRequestIds);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
getSelectedRequestIds(node: Node): string[] {
|
||||
const docIsRequest = isRequest(node.doc) || isWebSocketRequest(node.doc) || isGrpcRequest(node.doc);
|
||||
|
||||
if (docIsRequest && node.selectedRequests === node.totalRequests) {
|
||||
return [node.doc._id];
|
||||
}
|
||||
|
||||
const requestIds: string[] = [];
|
||||
|
||||
for (const child of node.children) {
|
||||
const reqIds = this.getSelectedRequestIds(child);
|
||||
requestIds.push(...reqIds);
|
||||
}
|
||||
|
||||
return requestIds;
|
||||
}
|
||||
|
||||
createTree() {
|
||||
const { sidebarChildren } = this.props;
|
||||
const childObjects = sidebarChildren.all;
|
||||
const children: Node[] = childObjects.map(child => this.createNode(child));
|
||||
const totalRequests = children
|
||||
.map(child => child.totalRequests)
|
||||
.reduce((acc, totalRequests) => acc + totalRequests, 0);
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION missing property
|
||||
const rootFolder: RequestGroup = {
|
||||
...models.requestGroup.init(),
|
||||
_id: 'all',
|
||||
type: models.requestGroup.type,
|
||||
name: 'All requests',
|
||||
parentId: '',
|
||||
modified: 0,
|
||||
created: 0,
|
||||
};
|
||||
this.setState({
|
||||
treeRoot: {
|
||||
doc: rootFolder,
|
||||
collapsed: false,
|
||||
children: children,
|
||||
totalRequests: totalRequests,
|
||||
selectedRequests: totalRequests, // Default select all
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createNode(item: Record<string, any>): Node {
|
||||
const children: Node[] = item.children.map((child: Record<string, any>) => this.createNode(child));
|
||||
export interface ExportRequestsModalHandle {
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const ExportRequestsModal = forwardRef<ExportRequestsModalHandle, ModalProps>((props, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<State>({ treeRoot: null });
|
||||
const sidebarChildren = useSelector(selectSidebarChildren);
|
||||
const createNode = useCallback((item: Record<string, any>): Node => {
|
||||
const children: Node[] = item.children.map((child: Record<string, any>) => createNode(child));
|
||||
let totalRequests = children
|
||||
.map(child => child.totalRequests)
|
||||
.reduce((acc, totalRequests) => acc + totalRequests, 0);
|
||||
const docIsRequest = isRequest(item.doc) || isWebSocketRequest(item.doc) || isGrpcRequest(item.doc);
|
||||
|
||||
if (docIsRequest) {
|
||||
totalRequests++;
|
||||
}
|
||||
|
||||
return {
|
||||
doc: item.doc,
|
||||
collapsed: false,
|
||||
@ -130,79 +49,64 @@ export class ExportRequestsModalClass extends PureComponent<Props, State> {
|
||||
totalRequests: totalRequests,
|
||||
selectedRequests: totalRequests, // Default select all
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: () => {
|
||||
modalRef.current?.show();
|
||||
const childObjects = sidebarChildren.all;
|
||||
const children: Node[] = childObjects.map(child => createNode(child));
|
||||
const totalRequests = children
|
||||
.map(child => child.totalRequests)
|
||||
.reduce((acc, totalRequests) => acc + totalRequests, 0);
|
||||
|
||||
handleSetRequestGroupCollapsed(requestGroupId: string, isCollapsed: boolean) {
|
||||
const { treeRoot } = this.state;
|
||||
|
||||
if (treeRoot == null) {
|
||||
return;
|
||||
// @ts-expect-error -- TSCONVERSION missing property
|
||||
const rootFolder: RequestGroup = {
|
||||
...models.requestGroup.init(),
|
||||
_id: 'all',
|
||||
type: models.requestGroup.type,
|
||||
name: 'All requests',
|
||||
parentId: '',
|
||||
modified: 0,
|
||||
created: 0,
|
||||
};
|
||||
setState({
|
||||
treeRoot: {
|
||||
doc: rootFolder,
|
||||
collapsed: false,
|
||||
children: children,
|
||||
totalRequests: totalRequests,
|
||||
selectedRequests: totalRequests, // Default select all
|
||||
},
|
||||
});
|
||||
},
|
||||
}), [createNode, sidebarChildren.all]);
|
||||
const getSelectedRequestIds = (node: Node): string[] => {
|
||||
const docIsRequest = isRequest(node.doc) || isWebSocketRequest(node.doc) || isGrpcRequest(node.doc);
|
||||
if (docIsRequest && node.selectedRequests === node.totalRequests) {
|
||||
return [node.doc._id];
|
||||
}
|
||||
|
||||
const found = this.setRequestGroupCollapsed(treeRoot, isCollapsed, requestGroupId);
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
treeRoot: { ...treeRoot },
|
||||
});
|
||||
}
|
||||
|
||||
handleSetItemSelected(itemId: string, isSelected: boolean) {
|
||||
const { treeRoot } = this.state;
|
||||
|
||||
if (treeRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const found = this.setItemSelected(treeRoot, isSelected, itemId);
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
treeRoot: { ...treeRoot },
|
||||
});
|
||||
}
|
||||
|
||||
setRequestGroupCollapsed(node: Node, isCollapsed: boolean, requestGroupId: string) {
|
||||
if (!isRequestGroup(node.doc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.doc._id === requestGroupId) {
|
||||
node.collapsed = isCollapsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
const requestIds: string[] = [];
|
||||
for (const child of node.children) {
|
||||
const found = this.setRequestGroupCollapsed(child, isCollapsed, requestGroupId);
|
||||
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
const reqIds = getSelectedRequestIds(child);
|
||||
requestIds.push(...reqIds);
|
||||
}
|
||||
return requestIds;
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
setItemSelected(node: Node, isSelected: boolean, id?: string) {
|
||||
const setItemSelected = (node: Node, isSelected: boolean, id?: string) => {
|
||||
if (id == null || node.doc._id === id) {
|
||||
// Switch the flags of all children in this subtree.
|
||||
for (const child of node.children) {
|
||||
this.setItemSelected(child, isSelected);
|
||||
setItemSelected(child, isSelected);
|
||||
}
|
||||
|
||||
node.selectedRequests = isSelected ? node.totalRequests : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
const found = this.setItemSelected(child, isSelected, id);
|
||||
|
||||
const found = setItemSelected(child, isSelected, id);
|
||||
if (found) {
|
||||
node.selectedRequests = node.children
|
||||
.map(ch => ch.selectedRequests)
|
||||
@ -210,42 +114,86 @@ export class ExportRequestsModalClass extends PureComponent<Props, State> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const handleSetItemSelected = (itemId: string, isSelected: boolean) => {
|
||||
const { treeRoot } = state;
|
||||
if (treeRoot == null) {
|
||||
return;
|
||||
}
|
||||
const found = setItemSelected(treeRoot, isSelected, itemId);
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
setState({ treeRoot: { ...treeRoot } });
|
||||
};
|
||||
const handleSetRequestGroupCollapsed = (requestGroupId: string, isCollapsed: boolean) => {
|
||||
const { treeRoot } = state;
|
||||
if (treeRoot == null) {
|
||||
return;
|
||||
}
|
||||
const found = setRequestGroupCollapsed(treeRoot, isCollapsed, requestGroupId);
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
setState({ treeRoot: { ...treeRoot } });
|
||||
};
|
||||
const setRequestGroupCollapsed = (node: Node, isCollapsed: boolean, requestGroupId: string) => {
|
||||
if (!isRequestGroup(node.doc)) {
|
||||
return false;
|
||||
}
|
||||
if (node.doc._id === requestGroupId) {
|
||||
node.collapsed = isCollapsed;
|
||||
return true;
|
||||
}
|
||||
for (const child of node.children) {
|
||||
const found = setRequestGroupCollapsed(child, isCollapsed, requestGroupId);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const handleExport = () => {
|
||||
const { treeRoot } = state;
|
||||
if (treeRoot == null || treeRoot.selectedRequests === 0) {
|
||||
return;
|
||||
}
|
||||
const exportedRequestIds = getSelectedRequestIds(treeRoot);
|
||||
|
||||
render() {
|
||||
const { treeRoot } = this.state;
|
||||
const isExportDisabled = treeRoot != null ? treeRoot.selectedRequests === 0 : false;
|
||||
return (
|
||||
<Modal ref={this.setModalRef} tall {...this.props}>
|
||||
<ModalHeader>Select Requests to Export</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="requests-tree">
|
||||
<Tree
|
||||
root={treeRoot}
|
||||
handleSetRequestGroupCollapsed={this.handleSetRequestGroupCollapsed}
|
||||
handleSetItemSelected={this.handleSetItemSelected}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
<button className="btn" onClick={this.hide}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn" onClick={this.handleExport} disabled={isExportDisabled}>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
exportRequestsToFile(exportedRequestIds);
|
||||
modalRef.current?.hide();
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
sidebarChildren: selectSidebarChildren(state),
|
||||
const { treeRoot } = state;
|
||||
const isExportDisabled = treeRoot != null ? treeRoot.selectedRequests === 0 : false;
|
||||
return (
|
||||
<Modal ref={modalRef} tall {...props}>
|
||||
<ModalHeader>Select Requests to Export</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="requests-tree">
|
||||
<Tree
|
||||
root={treeRoot}
|
||||
handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed}
|
||||
handleSetItemSelected={handleSetItemSelected}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={handleExport}
|
||||
disabled={isExportDisabled}
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export const ExportRequestsModal = connect(mapStateToProps, null, null, { forwardRef:true })(ExportRequestsModalClass);
|
||||
ExportRequestsModal.displayName = 'ExportRequestsModal';
|
||||
|
@ -1,16 +1,10 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { FC, PureComponent } from 'react';
|
||||
import React, { FC, forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { Link } from '../base/link';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
|
||||
interface State {
|
||||
isJSON: boolean;
|
||||
}
|
||||
|
||||
interface HelpExample {
|
||||
code: string;
|
||||
description: string;
|
||||
@ -65,37 +59,38 @@ const XPathHelp: FC = () => (
|
||||
/>
|
||||
</ModalBody>
|
||||
);
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class FilterHelpModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
isJSON: true,
|
||||
};
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
show(isJSON: boolean) {
|
||||
this.setState({ isJSON });
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isJSON } = this.state;
|
||||
const isXPath = !isJSON;
|
||||
return (
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader>Response Filtering Help</ModalHeader>
|
||||
{isJSON ? <JSONPathHelp /> : null}
|
||||
{isXPath ? <XPathHelp /> : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
interface FilterHelpModalOptions {
|
||||
isJSON: boolean;
|
||||
}
|
||||
export interface FilterHelpModalHandle {
|
||||
show: (options: FilterHelpModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
export const FilterHelpModal = forwardRef<FilterHelpModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<FilterHelpModalOptions>({
|
||||
isJSON: true,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
const { isJSON } = options;
|
||||
setState({ isJSON });
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
const { isJSON } = state;
|
||||
const isXPath = !isJSON;
|
||||
return (
|
||||
<Modal ref={modalRef}>
|
||||
<ModalHeader>Response Filtering Help</ModalHeader>
|
||||
{isJSON ? <JSONPathHelp /> : null}
|
||||
{isXPath ? <XPathHelp /> : null}
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
FilterHelpModal.displayName = 'FilterHelpModal';
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import HTTPSnippet, { HTTPSnippetClient, HTTPSnippetTarget } from 'httpsnippet';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { exportHarRequest } from '../../../common/har';
|
||||
import { Request } from '../../../models/request';
|
||||
import { CopyButton } from '../base/copy-button';
|
||||
@ -10,7 +8,7 @@ import { Dropdown } from '../base/dropdown/dropdown';
|
||||
import { DropdownButton } from '../base/dropdown/dropdown-button';
|
||||
import { DropdownItem } from '../base/dropdown/dropdown-item';
|
||||
import { Link } from '../base/link';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@ -30,95 +28,53 @@ const TO_ADD_CONTENT_LENGTH: Record<string, string[]> = {
|
||||
node: ['native'],
|
||||
};
|
||||
|
||||
interface Props {
|
||||
type Props = ModalProps & {
|
||||
environmentId: string;
|
||||
};
|
||||
export interface GenerateCodeModalOptions {
|
||||
request?: Request;
|
||||
}
|
||||
|
||||
interface State {
|
||||
export interface State {
|
||||
cmd: string;
|
||||
request?: Request;
|
||||
target: HTTPSnippetTarget;
|
||||
client: HTTPSnippetClient;
|
||||
}
|
||||
export interface GenerateCodeModalHandle {
|
||||
show: (options: GenerateCodeModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const GenerateCodeModal = forwardRef<GenerateCodeModalHandle, Props>((props, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const editorRef = useRef<UnconnectedCodeEditor>(null);
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
let target: HTTPSnippetTarget | undefined;
|
||||
let client: HTTPSnippetClient | undefined;
|
||||
let storedTarget: HTTPSnippetTarget | undefined;
|
||||
let storedClient: HTTPSnippetClient | undefined;
|
||||
try {
|
||||
storedTarget = JSON.parse(window.localStorage.getItem('insomnia::generateCode::target') || '') as HTTPSnippetTarget;
|
||||
} catch (error) {}
|
||||
|
||||
// Load preferences from localStorage
|
||||
try {
|
||||
target = JSON.parse(window.localStorage.getItem('insomnia::generateCode::target') || '') as HTTPSnippetTarget;
|
||||
} catch (error) {}
|
||||
try {
|
||||
storedClient = JSON.parse(window.localStorage.getItem('insomnia::generateCode::client') || '') as HTTPSnippetClient;
|
||||
} catch (error) {}
|
||||
const [state, setState] = useState<State>({
|
||||
cmd: '',
|
||||
request: undefined,
|
||||
target: storedTarget || DEFAULT_TARGET,
|
||||
client: storedClient || DEFAULT_CLIENT,
|
||||
});
|
||||
|
||||
try {
|
||||
client = JSON.parse(window.localStorage.getItem('insomnia::generateCode::client') || '') as HTTPSnippetClient;
|
||||
} catch (error) {}
|
||||
|
||||
this.state = {
|
||||
cmd: '',
|
||||
request: undefined,
|
||||
target: target || DEFAULT_TARGET,
|
||||
client: client || DEFAULT_CLIENT,
|
||||
};
|
||||
}
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
_editor: UnconnectedCodeEditor | null = null;
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_setEditorRef(editor: UnconnectedCodeEditor) {
|
||||
this._editor = editor;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
_handleClientChange(client: HTTPSnippetClient) {
|
||||
const { target, request } = this.state;
|
||||
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
this._generateCode(request, target, client);
|
||||
}
|
||||
|
||||
_handleTargetChange(target: HTTPSnippetTarget) {
|
||||
const { target: currentTarget, request } = this.state;
|
||||
|
||||
if (currentTarget.key === target.key) {
|
||||
// No change
|
||||
return;
|
||||
}
|
||||
|
||||
const client = target.clients.find(c => c.key === target.default);
|
||||
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
// TODO: remove non-null assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this._generateCode(request, target, client!);
|
||||
}
|
||||
|
||||
async _generateCode(request: Request, target: HTTPSnippetTarget, client: HTTPSnippetClient) {
|
||||
const generateCode = useCallback(async (request: Request, target: HTTPSnippetTarget, client: HTTPSnippetClient) => {
|
||||
// Some clients need a content-length for the request to succeed
|
||||
const addContentLength = Boolean((TO_ADD_CONTENT_LENGTH[target.key] || []).find(c => c === client.key));
|
||||
const { environmentId } = this.props;
|
||||
const har = await exportHarRequest(request._id, environmentId, addContentLength);
|
||||
const har = await exportHarRequest(request._id, props.environmentId, addContentLength);
|
||||
// @TODO Should we throw instead?
|
||||
if (!har) {
|
||||
return;
|
||||
}
|
||||
const snippet = new HTTPSnippet(har);
|
||||
const cmd = snippet.convert(target.key, client.key) || '';
|
||||
this.setState({
|
||||
setState({
|
||||
request,
|
||||
cmd,
|
||||
client,
|
||||
@ -127,88 +83,96 @@ export class GenerateCodeModal extends PureComponent<Props, State> {
|
||||
// Save client/target for next time
|
||||
window.localStorage.setItem('insomnia::generateCode::client', JSON.stringify(client));
|
||||
window.localStorage.setItem('insomnia::generateCode::target', JSON.stringify(target));
|
||||
}, [props.environmentId]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
if (!options.request) {
|
||||
return;
|
||||
}
|
||||
generateCode(options.request, state.target, state.client);
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), [generateCode, state]);
|
||||
|
||||
const { cmd, target, client, request } = state;
|
||||
const targets = HTTPSnippet.availableTargets();
|
||||
// NOTE: Just some extra precautions in case the target is messed up
|
||||
let clients: HTTPSnippetClient[] = [];
|
||||
if (target && Array.isArray(target.clients)) {
|
||||
clients = target.clients;
|
||||
}
|
||||
|
||||
show(request: Request) {
|
||||
const { client, target } = this.state;
|
||||
|
||||
this._generateCode(request, target, client);
|
||||
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cmd, target, client } = this.state;
|
||||
const targets = HTTPSnippet.availableTargets();
|
||||
// NOTE: Just some extra precautions in case the target is messed up
|
||||
let clients: HTTPSnippetClient[] = [];
|
||||
|
||||
if (target && Array.isArray(target.clients)) {
|
||||
clients = target.clients;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall {...this.props}>
|
||||
<ModalHeader>Generate Client Code</ModalHeader>
|
||||
<ModalBody
|
||||
noScroll
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr)',
|
||||
gridTemplateRows: 'auto minmax(0, 1fr)',
|
||||
}}
|
||||
>
|
||||
<div className="pad">
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{
|
||||
target ? target.title : 'n/a'
|
||||
}
|
||||
<i className="fa fa-caret-down" />
|
||||
</DropdownButton>
|
||||
{targets.map(target => (
|
||||
<DropdownItem key={target.key} onClick={() => this._handleTargetChange(target)}>
|
||||
{target.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
return (
|
||||
<Modal ref={modalRef} tall {...props}>
|
||||
<ModalHeader>Generate Client Code</ModalHeader>
|
||||
<ModalBody
|
||||
noScroll
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(0, 1fr)',
|
||||
gridTemplateRows: 'auto minmax(0, 1fr)',
|
||||
}}
|
||||
>
|
||||
<div className="pad">
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{target ? target.title : 'n/a'}
|
||||
<i className="fa fa-caret-down" />
|
||||
</DropdownButton>
|
||||
{targets.map(target => (
|
||||
<DropdownItem
|
||||
key={target.key}
|
||||
onClick={() => {
|
||||
const client = target.clients.find(c => c.key === target.default);
|
||||
if (request && client) {
|
||||
generateCode(request, target, client);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{target.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{client ? client.title : 'n/a'}
|
||||
<i className="fa fa-caret-down" />
|
||||
</DropdownButton>
|
||||
{clients.map(client => (
|
||||
<DropdownItem
|
||||
key={client.key}
|
||||
onClick={() => this._handleClientChange(client)}
|
||||
>
|
||||
{client.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
<Dropdown outline>
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
{client ? client.title : 'n/a'}
|
||||
<i className="fa fa-caret-down" />
|
||||
</DropdownButton>
|
||||
{clients.map(client => (
|
||||
<DropdownItem
|
||||
key={client.key}
|
||||
onClick={() => request && generateCode(request, state.target, client)}
|
||||
>
|
||||
{client.title}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
<CopyButton content={cmd} className="pull-right" />
|
||||
</div>
|
||||
<CodeEditor
|
||||
placeholder="Generating code snippet..."
|
||||
className="border-top"
|
||||
key={Date.now()}
|
||||
mode={MODE_MAP[target.key] || target.key}
|
||||
ref={this._setEditorRef}
|
||||
defaultValue={cmd}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left italic txt-sm">
|
||||
* Code snippets generated by
|
||||
<Link href="https://github.com/Kong/httpsnippet">httpsnippet</Link>
|
||||
</div>
|
||||
<button className="btn" onClick={this.hide}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
<CopyButton content={cmd} className="pull-right" />
|
||||
</div>
|
||||
<CodeEditor
|
||||
placeholder="Generating code snippet..."
|
||||
className="border-top"
|
||||
key={Date.now()}
|
||||
mode={MODE_MAP[target.key] || target.key}
|
||||
ref={editorRef}
|
||||
defaultValue={cmd}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left italic txt-sm">
|
||||
* Code snippets generated by
|
||||
<Link href="https://github.com/Kong/httpsnippet">httpsnippet</Link>
|
||||
</div>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
GenerateCodeModal.displayName = 'GenerateCodeModal';
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import { parseApiSpec } from '../../../common/api-specs';
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import type { ApiSpec } from '../../../models/api-spec';
|
||||
import type { ConfigGenerator } from '../../../plugins';
|
||||
import * as plugins from '../../../plugins';
|
||||
import { CopyButton } from '../base/copy-button';
|
||||
import { Link } from '../base/link';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@ -30,133 +28,117 @@ interface State {
|
||||
activeTab: number;
|
||||
}
|
||||
|
||||
interface ShowOptions {
|
||||
interface GenerateConfigModalOptions {
|
||||
apiSpec: ApiSpec;
|
||||
activeTabLabel: string;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class GenerateConfigModal extends PureComponent<{}, State> {
|
||||
modal: ModalHandle | null = null;
|
||||
|
||||
state: State = {
|
||||
export interface GenerateConfigModalHandle {
|
||||
show: (options: GenerateConfigModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const GenerateConfigModal = forwardRef<GenerateConfigModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<State>({
|
||||
configs: [],
|
||||
activeTab: 0,
|
||||
};
|
||||
});
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: async options => {
|
||||
const configs: Config[] = [];
|
||||
for (const p of await plugins.getConfigGenerators()) {
|
||||
configs.push(await generateConfig(p, options.apiSpec));
|
||||
}
|
||||
const foundIndex = configs.findIndex(c => c.label === options.activeTabLabel);
|
||||
setState({
|
||||
configs,
|
||||
activeTab: foundIndex < 0 ? 0 : foundIndex,
|
||||
});
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
async _generate(generatePlugin: ConfigGenerator, apiSpec: ApiSpec) {
|
||||
const config: Config = {
|
||||
content: '',
|
||||
mimeType: 'text/yaml',
|
||||
label: generatePlugin.label,
|
||||
docsLink: generatePlugin.docsLink,
|
||||
error: null,
|
||||
};
|
||||
const generateConfig = async (generatePlugin: ConfigGenerator, apiSpec: ApiSpec): Promise<Config> => {
|
||||
try {
|
||||
const result = await generatePlugin.generate(parseApiSpec(apiSpec.contents));
|
||||
if (result.document) {
|
||||
config.content = result.document;
|
||||
}
|
||||
config.error = result.error || null;
|
||||
return config;
|
||||
return {
|
||||
content: result.document || '',
|
||||
mimeType: 'text/yaml',
|
||||
label: generatePlugin.label,
|
||||
docsLink: generatePlugin.docsLink,
|
||||
error: result.error || null,
|
||||
};
|
||||
} catch (err) {
|
||||
config.error = err.message;
|
||||
return config;
|
||||
return {
|
||||
content: '',
|
||||
mimeType: 'text/yaml',
|
||||
label: generatePlugin.label,
|
||||
docsLink: generatePlugin.docsLink,
|
||||
error: err.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async show({ activeTabLabel, apiSpec }: ShowOptions) {
|
||||
const configs: Config[] = [];
|
||||
|
||||
for (const p of await plugins.getConfigGenerators()) {
|
||||
configs.push(await this._generate(p, apiSpec));
|
||||
}
|
||||
|
||||
const foundIndex = configs.findIndex(c => c.label === activeTabLabel);
|
||||
this.setState({
|
||||
const onSelect = (index: number) => {
|
||||
setState({
|
||||
configs,
|
||||
activeTab: foundIndex < 0 ? 0 : foundIndex,
|
||||
});
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
renderConfigTabPanel(config: Config) {
|
||||
const linkIcon = <i className="fa fa-external-link-square" />;
|
||||
if (config.error) {
|
||||
return (
|
||||
<TabPanel key={config.label}>
|
||||
<p className="notice error margin-md">
|
||||
{config.error}
|
||||
{config.docsLink ? <><br /><Link href={config.docsLink}>Documentation {linkIcon}</Link></> : null}
|
||||
</p>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TabPanel key={config.label}>
|
||||
<CodeEditor
|
||||
className="tall pad-top-sm"
|
||||
defaultValue={config.content}
|
||||
mode={config.mimeType}
|
||||
readOnly
|
||||
/>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
|
||||
_handleTabSelect(index: number) {
|
||||
this.setState({
|
||||
activeTab: index,
|
||||
});
|
||||
}
|
||||
};
|
||||
const { configs, activeTab } = state;
|
||||
const activeConfig = configs[activeTab];
|
||||
return (
|
||||
<Modal ref={modalRef} tall>
|
||||
<ModalHeader>Generate Config</ModalHeader>
|
||||
<ModalBody className="wide">
|
||||
<Tabs forceRenderTabPanel defaultIndex={activeTab} onSelect={onSelect}>
|
||||
<TabList>{configs.map(config =>
|
||||
(<Tab key={config.label} tabIndex="-1">
|
||||
<button>
|
||||
{config.label}
|
||||
{config.docsLink ?
|
||||
<>
|
||||
{' '}
|
||||
<HelpTooltip>
|
||||
To learn more about {config.label}
|
||||
<br />
|
||||
<Link href={config.docsLink}>Documentation {<i className="fa fa-external-link-square" />}</Link>
|
||||
</HelpTooltip>
|
||||
</> : null}
|
||||
</button>
|
||||
</Tab>)
|
||||
)}
|
||||
</TabList>
|
||||
{configs.map(config =>
|
||||
(<TabPanel key={config.label}>
|
||||
{config.error ?
|
||||
<p className="notice error margin-md">
|
||||
{config.error}
|
||||
{config.docsLink ? <><br /><Link href={config.docsLink}>Documentation {<i className="fa fa-external-link-square" />}</Link></> : null}
|
||||
</p> :
|
||||
<CodeEditor
|
||||
className="tall pad-top-sm"
|
||||
defaultValue={config.content}
|
||||
mode={config.mimeType}
|
||||
readOnly
|
||||
/>}
|
||||
</TabPanel>)
|
||||
)}
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
{activeConfig && (
|
||||
<ModalFooter>
|
||||
<CopyButton className="btn" content={activeConfig.content}>
|
||||
Copy to Clipboard
|
||||
</CopyButton>
|
||||
</ModalFooter>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
GenerateConfigModal.displayName = 'GenerateConfigModal';
|
||||
|
||||
renderConfigTab(config: Config) {
|
||||
const linkIcon = <i className="fa fa-external-link-square" />;
|
||||
return (
|
||||
<Tab key={config.label} tabIndex="-1">
|
||||
<button>
|
||||
{config.label}
|
||||
{config.docsLink ?
|
||||
<>
|
||||
{' '}
|
||||
<HelpTooltip>
|
||||
To learn more about {config.label}
|
||||
<br />
|
||||
<Link href={config.docsLink}>Documentation {linkIcon}</Link>
|
||||
</HelpTooltip>
|
||||
</> : null}
|
||||
</button>
|
||||
</Tab>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { configs, activeTab } = this.state;
|
||||
const activeConfig = configs[activeTab];
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall>
|
||||
<ModalHeader>Generate Config</ModalHeader>
|
||||
<ModalBody className="wide">
|
||||
<Tabs forceRenderTabPanel defaultIndex={activeTab} onSelect={this._handleTabSelect}>
|
||||
<TabList>{configs.map(this.renderConfigTab)}</TabList>
|
||||
{configs.map(this.renderConfigTabPanel)}
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
{activeConfig && (
|
||||
<ModalFooter>
|
||||
<CopyButton className="btn" content={activeConfig.content}>
|
||||
Copy to Clipboard
|
||||
</CopyButton>
|
||||
</ModalFooter>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const showGenerateConfigModal = (opts: ShowOptions) => showModal(GenerateConfigModal, opts);
|
||||
export const showGenerateConfigModal = (opts: GenerateConfigModalOptions) => showModal(GenerateConfigModal, opts);
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { Workspace } from '../../../models/workspace';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@ -14,82 +12,80 @@ interface Props {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
interface State {
|
||||
defaultTemplate: string;
|
||||
interface NunjucksModalOptions {
|
||||
template: string;
|
||||
onDone: Function;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class NunjucksModal extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
defaultTemplate: '',
|
||||
export interface NunjucksModalHandle {
|
||||
show: (options: NunjucksModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const NunjucksModal = forwardRef<NunjucksModalHandle, ModalProps & Props>((props, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<NunjucksModalOptions>({
|
||||
template: '',
|
||||
onDone: () => { },
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: ({ onDone, template }) => {
|
||||
setState({
|
||||
template,
|
||||
onDone,
|
||||
});
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
const handleTemplateChange = (template: string) => {
|
||||
setState(state => ({
|
||||
template,
|
||||
onDone: state.onDone,
|
||||
}));
|
||||
};
|
||||
|
||||
_onDone: Function | null = null;
|
||||
_currentTemplate: string | null = null;
|
||||
modal: ModalHandle | null = null;
|
||||
const { workspace } = props;
|
||||
const { template } = state;
|
||||
let editor: JSX.Element | null = null;
|
||||
let title = '';
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
if (template.indexOf('{{') === 0) {
|
||||
title = 'Variable';
|
||||
editor = <VariableEditor onChange={handleTemplateChange} defaultValue={template} />;
|
||||
} else if (template.indexOf('{%') === 0) {
|
||||
title = 'Tag';
|
||||
editor = <TagEditor onChange={handleTemplateChange} defaultValue={template} workspace={workspace} />;
|
||||
}
|
||||
|
||||
_handleTemplateChange(template: string | null) {
|
||||
this._currentTemplate = template;
|
||||
}
|
||||
|
||||
_handleSubmit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
_handleModalHide() {
|
||||
if (this._onDone) {
|
||||
this._onDone(this._currentTemplate);
|
||||
|
||||
this.setState({
|
||||
defaultTemplate: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
show({ template, onDone }: any) {
|
||||
this._onDone = onDone;
|
||||
this._currentTemplate = template;
|
||||
this.setState({
|
||||
defaultTemplate: template,
|
||||
});
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { workspace } = this.props;
|
||||
const { defaultTemplate } = this.state;
|
||||
let editor: JSX.Element | null = null;
|
||||
let title = '';
|
||||
|
||||
if (defaultTemplate.indexOf('{{') === 0) {
|
||||
title = 'Variable';
|
||||
editor = <VariableEditor onChange={this._handleTemplateChange} defaultValue={defaultTemplate} />;
|
||||
} else if (defaultTemplate.indexOf('{%') === 0) {
|
||||
title = 'Tag';
|
||||
editor = <TagEditor onChange={this._handleTemplateChange} defaultValue={defaultTemplate} workspace={workspace} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} onHide={this._handleModalHide}>
|
||||
<ModalHeader>Edit {title}</ModalHeader>
|
||||
<ModalBody className="pad" key={defaultTemplate}>
|
||||
<form onSubmit={this._handleSubmit}>{editor}</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button className="btn" onClick={this.hide}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
ref={modalRef}
|
||||
onHide={() => {
|
||||
state.onDone(state.template);
|
||||
setState(state => ({
|
||||
template: '',
|
||||
onDone: state.onDone,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<ModalHeader>Edit {title}</ModalHeader>
|
||||
<ModalBody className="pad">
|
||||
<form
|
||||
onSubmit={event => {
|
||||
event.preventDefault();
|
||||
modalRef.current?.hide();
|
||||
}}
|
||||
>{editor}</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
NunjucksModal.displayName = 'NunjucksModal';
|
||||
|
@ -1,100 +1,88 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { docsTemplateTags } from '../../../common/documentation';
|
||||
import { Request } from '../../../models/request';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { RenderError } from '../../../templating';
|
||||
import { Link } from '../base/link';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
import { RequestSettingsModal } from '../modals/request-settings-modal';
|
||||
import { showModal } from './index';
|
||||
|
||||
interface State {
|
||||
error: Error | null;
|
||||
request: any | null;
|
||||
export interface RequestRenderErrorModalOptions {
|
||||
error: RenderError | null;
|
||||
request: Request | null;
|
||||
}
|
||||
export interface RequestRenderErrorModalHandle {
|
||||
show: (options: RequestRenderErrorModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class RequestRenderErrorModal extends PureComponent<{}, State> {
|
||||
state: State = {
|
||||
export const RequestRenderErrorModal = forwardRef<RequestRenderErrorModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<RequestRenderErrorModalOptions>({
|
||||
error: null,
|
||||
request: null,
|
||||
};
|
||||
});
|
||||
|
||||
modal: ModalHandle | null = null;
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
setState(options);
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
|
||||
_handleShowRequestSettings() {
|
||||
this.hide();
|
||||
showModal(RequestSettingsModal, {
|
||||
request: this.state.request,
|
||||
});
|
||||
}
|
||||
|
||||
show({ request, error }: Pick<State, 'request' | 'error'>) {
|
||||
this.setState({ request, error });
|
||||
this.modal?.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.hide();
|
||||
}
|
||||
|
||||
renderModalBody(request: any, error: any) {
|
||||
const fullPath = `Request.${error.path}`;
|
||||
const result = JSONPath({ json: request, path: `$.${error.path}` });
|
||||
const template = result && result.length ? result[0] : null;
|
||||
const locationLabel =
|
||||
template?.includes('\n') ? `line ${error.location.line} of` : null;
|
||||
return (
|
||||
<div className="pad">
|
||||
<div className="notice warning">
|
||||
<p>
|
||||
Failed to render <strong>{fullPath}</strong> prior to sending
|
||||
</p>
|
||||
<div className="pad-top-sm">
|
||||
{error.path.match(/^body/) && isRequest(request) && (
|
||||
<button
|
||||
className="btn btn--clicky margin-right-sm"
|
||||
onClick={this._handleShowRequestSettings}
|
||||
>
|
||||
Adjust Render Settings
|
||||
</button>
|
||||
)}
|
||||
<Link button href={docsTemplateTags} className="btn btn--clicky">
|
||||
Templating Documentation <i className="fa fa-external-link" />
|
||||
</Link>
|
||||
const { request, error } = state;
|
||||
const fullPath = `Request.${error?.path}`;
|
||||
const result = JSONPath({ json: request, path: `$.${error?.path}` });
|
||||
const template = result && result.length ? result[0] : null;
|
||||
const locationLabel = template?.includes('\n') ? `line ${error?.location.line} of` : null;
|
||||
return (
|
||||
<Modal ref={modalRef}>
|
||||
<ModalHeader>Failed to Render Request</ModalHeader>
|
||||
<ModalBody>{request && error && error ? (
|
||||
<div className="pad">
|
||||
<div className="notice warning">
|
||||
<p>
|
||||
Failed to render <strong>{fullPath}</strong> prior to sending
|
||||
</p>
|
||||
<div className="pad-top-sm">
|
||||
{error.path?.match(/^body/) && isRequest(request) && (
|
||||
<button
|
||||
className="btn btn--clicky margin-right-sm"
|
||||
onClick={() => {
|
||||
modalRef.current?.hide();
|
||||
showModal(RequestSettingsModal, { request: state.request });
|
||||
}}
|
||||
>
|
||||
Adjust Render Settings
|
||||
</button>
|
||||
)}
|
||||
<Link button href={docsTemplateTags} className="btn btn--clicky">
|
||||
Templating Documentation <i className="fa fa-external-link" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>Render error</strong>
|
||||
<code className="block selectable">{error.message}</code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Caused by the following field</strong>
|
||||
<code className="block">
|
||||
{locationLabel} {fullPath}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>Render error</strong>
|
||||
<code className="block selectable">{error.message}</code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Caused by the following field</strong>
|
||||
<code className="block">
|
||||
{locationLabel} {fullPath}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { request, error } = this.state;
|
||||
return (
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader>Failed to Render Request</ModalHeader>
|
||||
<ModalBody>{request && error ? this.renderModalBody(request, error) : null}</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
) : null}</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
RequestRenderErrorModal.displayName = 'RequestRenderErrorModal';
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { createRef, PureComponent } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
import { showModal } from '.';
|
||||
|
||||
export interface SelectModalShowOptions {
|
||||
export interface SelectModalOptions {
|
||||
message: string | null;
|
||||
onDone?: (selectedValue: string | null) => void | Promise<void>;
|
||||
options: {
|
||||
@ -19,77 +17,60 @@ export interface SelectModalShowOptions {
|
||||
value: string | null;
|
||||
noEscape?: boolean;
|
||||
}
|
||||
|
||||
const initialState: SelectModalShowOptions = {
|
||||
message: null,
|
||||
options: [],
|
||||
title: null,
|
||||
value: null,
|
||||
};
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class SelectModal extends PureComponent<{}, SelectModalShowOptions> {
|
||||
modal = createRef<ModalHandle>();
|
||||
doneButton = createRef<HTMLButtonElement>();
|
||||
state: SelectModalShowOptions = initialState;
|
||||
|
||||
async _onDone() {
|
||||
this.modal.current?.hide();
|
||||
await this.state.onDone?.(this.state.value);
|
||||
}
|
||||
|
||||
_handleSelectChange(event: React.SyntheticEvent<HTMLSelectElement>) {
|
||||
this.setState({ value: event.currentTarget.value });
|
||||
}
|
||||
|
||||
show({
|
||||
message,
|
||||
onDone,
|
||||
options,
|
||||
title,
|
||||
value,
|
||||
noEscape,
|
||||
}: SelectModalShowOptions = initialState) {
|
||||
this.setState({
|
||||
message,
|
||||
onDone,
|
||||
options,
|
||||
title,
|
||||
value,
|
||||
noEscape,
|
||||
});
|
||||
this.modal.current?.show();
|
||||
setTimeout(() => {
|
||||
this.doneButton.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, title, options, value, noEscape } = this.state;
|
||||
|
||||
return (
|
||||
<Modal ref={this.modal} noEscape={noEscape}>
|
||||
<ModalHeader>{title || 'Confirm?'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
<p>{message}</p>
|
||||
<div className="form-control form-control--outlined">
|
||||
<select onChange={this._handleSelectChange} value={value ?? undefined}>
|
||||
{options.map(({ name, value }) => (
|
||||
<option key={value} value={value}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button ref={this.doneButton} className="btn" onClick={this._onDone}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
export interface SelectModalHandle {
|
||||
show: (options: SelectModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
export const showSelectModal = (opts: SelectModalShowOptions) => showModal(SelectModal, opts);
|
||||
export const SelectModal = forwardRef<SelectModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<SelectModalOptions>({
|
||||
message: null,
|
||||
options: [],
|
||||
title: null,
|
||||
value: null,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
setState(options);
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
const { message, title, options, value, noEscape, onDone } = state;
|
||||
|
||||
return (
|
||||
<Modal ref={modalRef} noEscape={noEscape}>
|
||||
<ModalHeader>{title || 'Confirm?'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
<p>{message}</p>
|
||||
<div className="form-control form-control--outlined">
|
||||
<select onChange={event => setState(state => ({ ...state, value: event.target.value }))} value={value ?? undefined}>
|
||||
{options.map(({ name, value }) => (
|
||||
<option key={value} value={value}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() => {
|
||||
modalRef.current?.hide();
|
||||
onDone?.(value);
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
SelectModal.displayName = 'SelectModal';
|
||||
|
||||
export const showSelectModal = (opts: SelectModalOptions) => showModal(SelectModal, opts);
|
||||
|
@ -1,70 +1,48 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { PureComponent, ReactNode } from 'react';
|
||||
import React, { forwardRef, ReactNode, useImperativeHandle, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { type ModalHandle, Modal } from '../base/modal';
|
||||
import { type ModalProps, Modal, ModalHandle } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
|
||||
interface State {
|
||||
interface WrapperModalOptions {
|
||||
title: string;
|
||||
body: ReactNode;
|
||||
bodyHTML?: string | null;
|
||||
tall?: boolean | null;
|
||||
skinny?: boolean | null;
|
||||
wide?: boolean | null;
|
||||
tall?: boolean;
|
||||
skinny?: boolean;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class WrapperModal extends PureComponent<{}, State> {
|
||||
modal: ModalHandle | null = null;
|
||||
|
||||
state: State = {
|
||||
export interface WrapperModalHandle {
|
||||
show: (options: WrapperModalOptions) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const WrapperModal = forwardRef<WrapperModalHandle, ModalProps>((props, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [state, setState] = useState<WrapperModalOptions>({
|
||||
title: '',
|
||||
body: null,
|
||||
bodyHTML: null,
|
||||
tall: false,
|
||||
skinny: false,
|
||||
wide: false,
|
||||
};
|
||||
});
|
||||
|
||||
_setModalRef(modal: ModalHandle) {
|
||||
this.modal = modal;
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: options => {
|
||||
setState(options);
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), []);
|
||||
|
||||
show(options: Record<string, any> = {}) {
|
||||
const { title, body, bodyHTML, tall, skinny, wide } = options;
|
||||
this.setState({
|
||||
title,
|
||||
body,
|
||||
bodyHTML,
|
||||
tall: !!tall,
|
||||
skinny: !!skinny,
|
||||
wide: !!wide,
|
||||
});
|
||||
this.modal?.show();
|
||||
}
|
||||
const { title, body, tall, skinny, wide } = state;
|
||||
|
||||
render() {
|
||||
const { title, body, bodyHTML, tall, skinny, wide } = this.state;
|
||||
let finalBody = body;
|
||||
return (
|
||||
<Modal ref={modalRef} tall={tall} skinny={skinny} wide={wide} {...props}>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody>{body}</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
if (bodyHTML) {
|
||||
finalBody = (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: bodyHTML,
|
||||
}}
|
||||
className="tall wide pad"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall={tall ?? undefined} skinny={skinny ?? undefined} wide={wide ?? undefined}>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody>{finalBody}</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
WrapperModal.displayName = 'WrapperModal';
|
||||
|
@ -61,7 +61,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const handleGenerateCode = () => {
|
||||
showModal(GenerateCodeModal, request);
|
||||
showModal(GenerateCodeModal, { request });
|
||||
};
|
||||
const focusInput = useCallback(() => {
|
||||
if (inputRef.current) {
|
||||
|
@ -144,7 +144,7 @@ export const WrapperDebug: FC = () => {
|
||||
showCookiesEditor:
|
||||
() => showModal(CookiesModal),
|
||||
request_showGenerateCodeEditor:
|
||||
() => showModal(GenerateCodeModal, activeRequest),
|
||||
() => showModal(GenerateCodeModal, { request: activeRequest }),
|
||||
});
|
||||
// Close all websocket connections when the active environment changes
|
||||
useEffect(() => {
|
||||
|
@ -212,19 +212,19 @@ const App = () => {
|
||||
<div key="modals" className="modals">
|
||||
<ErrorBoundary showAlert>
|
||||
<AnalyticsModal />
|
||||
<AlertModal ref={registerModal} />
|
||||
<ErrorModal ref={registerModal} />
|
||||
<AlertModal ref={instance => registerModal(instance, 'AlertModal')} />
|
||||
<ErrorModal ref={instance => registerModal(instance, 'ErrorModal')} />
|
||||
<PromptModal ref={registerModal} />
|
||||
<WrapperModal ref={registerModal} />
|
||||
<WrapperModal ref={instance => registerModal(instance, 'WrapperModal')} />
|
||||
<LoginModal ref={registerModal} />
|
||||
<AskModal ref={registerModal} />
|
||||
<SelectModal ref={registerModal} />
|
||||
<FilterHelpModal ref={registerModal} />
|
||||
<RequestRenderErrorModal ref={registerModal} />
|
||||
<GenerateConfigModal ref={registerModal} />
|
||||
<AskModal ref={instance => registerModal(instance, 'AskModal')} />
|
||||
<SelectModal ref={instance => registerModal(instance, 'SelectModal')} />
|
||||
<FilterHelpModal ref={instance => registerModal(instance, 'FilterHelpModal')} />
|
||||
<RequestRenderErrorModal ref={instance => registerModal(instance, 'RequestRenderErrorModal')} />
|
||||
<GenerateConfigModal ref={instance => registerModal(instance, 'GenerateConfigModal')} />
|
||||
<ProjectSettingsModal ref={instance => registerModal(instance, 'ProjectSettingsModal')} />
|
||||
<WorkspaceDuplicateModal ref={registerModal} vcs={vcs || undefined} />
|
||||
<CodePromptModal ref={registerModal} />
|
||||
<CodePromptModal ref={instance => registerModal(instance, 'CodePromptModal')} />
|
||||
<RequestSettingsModal ref={instance => registerModal(instance, 'RequestSettingsModal')} />
|
||||
<RequestGroupSettingsModal ref={instance => registerModal(instance, 'RequestGroupSettingsModal')} />
|
||||
|
||||
@ -238,7 +238,7 @@ const App = () => {
|
||||
</> : null}
|
||||
|
||||
<NunjucksModal
|
||||
ref={registerModal}
|
||||
ref={instance => registerModal(instance, 'NunjucksModal')}
|
||||
workspace={activeWorkspace}
|
||||
/>
|
||||
|
||||
@ -250,7 +250,7 @@ const App = () => {
|
||||
</> : null}
|
||||
|
||||
<GenerateCodeModal
|
||||
ref={registerModal}
|
||||
ref={instance => registerModal(instance, 'GenerateCodeModal')}
|
||||
environmentId={activeEnvironment ? activeEnvironment._id : 'n/a'}
|
||||
/>
|
||||
|
||||
@ -259,10 +259,7 @@ const App = () => {
|
||||
|
||||
<RequestSwitcherModal ref={instance => registerModal(instance, 'RequestSwitcherModal')} />
|
||||
|
||||
<EnvironmentEditModal
|
||||
ref={registerModal}
|
||||
onChange={models.requestGroup.update}
|
||||
/>
|
||||
<EnvironmentEditModal ref={instance => registerModal(instance, 'EnvironmentEditModal')} />
|
||||
|
||||
<GitRepositorySettingsModal ref={registerModal} />
|
||||
|
||||
@ -297,7 +294,7 @@ const App = () => {
|
||||
/>
|
||||
|
||||
<AddKeyCombinationModal ref={instance => registerModal(instance, 'AddKeyCombinationModal')} />
|
||||
<ExportRequestsModal ref={registerModal} />
|
||||
<ExportRequestsModal ref={instance => registerModal(instance, 'ExportRequestsModal')} />
|
||||
|
||||
<GrpcDispatchModalWrapper>
|
||||
{dispatch => (
|
||||
|
Loading…
Reference in New Issue
Block a user