1- import * as _ from 'lodash' ;
21import React from "react" ;
3- import { action , computed } from "mobx" ;
2+ import { action , computed , observable } from "mobx" ;
43import { inject , observer } from "mobx-react" ;
54import dedent from 'dedent' ;
65
7- import { HttpExchangeView } from '../../../types' ;
6+ import { CollectedEvent , HttpExchangeView } from '../../../types' ;
87import { styled } from '../../../styles' ;
98import { Icon } from '../../../icons' ;
109import { logError } from '../../../errors' ;
@@ -16,13 +15,11 @@ import {
1615 generateCodeSnippet ,
1716 getCodeSnippetFormatKey ,
1817 getCodeSnippetFormatName ,
19- getCodeSnippetOptionFromKey ,
18+ getSafeCodeSnippetOptionFromKey ,
2019 DEFAULT_SNIPPET_FORMAT_KEY ,
2120 snippetExportOptions ,
2221 SnippetOption
2322} from '../../../model/ui/export' ;
24- import { ZIP_ALL_FORMAT_KEY } from '../../../model/ui/snippet-formats' ;
25- import { ZipDownloadPanel } from '../zip-download-panel' ;
2623
2724import { ProHeaderPill , CardSalesPitch } from '../../account/pro-placeholders' ;
2825import {
@@ -34,6 +31,7 @@ import { PillSelector, PillButton } from '../../common/pill';
3431import { CopyButtonPill } from '../../common/copy-button' ;
3532import { DocsLink } from '../../common/docs-link' ;
3633import { SelfSizedEditor } from '../../editor/base-editor' ;
34+ import { ZipExportDialog } from '../zip-export-dialog' ;
3735
3836interface ExportCardProps extends CollapsibleCardProps {
3937 exchange : HttpExchangeView ;
@@ -138,116 +136,109 @@ const ExportHarPill = styled(observer((p: {
138136 margin-right: auto;
139137` ;
140138
141- // Virtual SnippetOption used as the PillSelector value when ZIP is selected.
142- // This is never passed to httpsnippet — it's only used for dropdown rendering.
143- const ZIP_SNIPPET_OPTION : SnippetOption = {
144- target : ZIP_ALL_FORMAT_KEY as any ,
145- client : '' as any ,
146- name : 'ZIP (Selected Formats)' ,
147- description : 'Download selected code snippet formats in a single ZIP archive' ,
148- link : ''
149- } ;
150-
151- // Build extended optGroups with ZIP at the top
152- const exportOptionsWithZip : _ . Dictionary < SnippetOption [ ] > = {
153- 'Archive' : [ ZIP_SNIPPET_OPTION ] ,
154- ...snippetExportOptions
155- } ;
156-
157- const getExportFormatKey = ( option : SnippetOption ) : string => {
158- if ( option === ZIP_SNIPPET_OPTION ) return ZIP_ALL_FORMAT_KEY ;
159- return getCodeSnippetFormatKey ( option ) ;
160- } ;
161-
162- const getExportFormatName = ( option : SnippetOption ) : string => {
163- if ( option === ZIP_SNIPPET_OPTION ) return ZIP_SNIPPET_OPTION . name ;
164- return getCodeSnippetFormatName ( option ) ;
165- } ;
166-
167139@inject ( 'accountStore' )
168140@inject ( 'uiStore' )
169141@observer
170142export class HttpExportCard extends React . Component < ExportCardProps > {
171143
144+ @observable
145+ private zipDialogOpen = false ;
146+
147+ @action . bound
148+ private openZipDialog ( ) { this . zipDialogOpen = true ; }
149+
150+ @action . bound
151+ private closeZipDialog ( ) { this . zipDialogOpen = false ; }
152+
172153 render ( ) {
173154 const { exchange, accountStore } = this . props ;
174155 const isPaidUser = accountStore ! . user . isPaidUser ( ) ;
175- const isZipSelected = this . isZipSelected ;
176156
177- return < CollapsibleCard { ...this . props } >
178- < header >
179- { isPaidUser
180- ? < ExportHarPill exchange = { exchange } />
181- : < ProHeaderPill />
182- }
157+ return < >
158+ < CollapsibleCard { ...this . props } >
159+ < header >
160+ { isPaidUser
161+ ? < >
162+ < ExportHarPill exchange = { exchange } />
163+ { /*
164+ * ZIP PillButton is active immediately (even when
165+ * the card is collapsed). The click stops propagation
166+ * so a header click underneath does not inadvertently
167+ * toggle the card.
168+ */ }
169+ < PillButton
170+ onClick = { ( e ) => {
171+ e . stopPropagation ( ) ;
172+ this . openZipDialog ( ) ;
173+ } }
174+ >
175+ < Icon icon = { [ 'fas' , 'file-archive' ] } /> ZIP
176+ </ PillButton >
177+ </ >
178+ : < ProHeaderPill />
179+ }
183180
184- < PillSelector < SnippetOption >
185- onChange = { this . setSnippetOption }
186- value = { this . currentDropdownValue }
187- optGroups = { exportOptionsWithZip }
188- keyFormatter = { getExportFormatKey }
189- nameFormatter = { getExportFormatName }
190- />
191-
192- < CollapsibleCardHeading onCollapseToggled = { this . props . onCollapseToggled } >
193- Export
194- </ CollapsibleCardHeading >
195- </ header >
196-
197- { isPaidUser ?
198- < div >
199- { isZipSelected
200- ? < ZipDownloadPanel exchanges = { [ exchange ] } />
201- : < ExportSnippetEditor
181+ < PillSelector < SnippetOption >
182+ onChange = { this . setSnippetOption }
183+ value = { this . snippetOption }
184+ optGroups = { snippetExportOptions }
185+ keyFormatter = { getCodeSnippetFormatKey }
186+ nameFormatter = { getCodeSnippetFormatName }
187+ />
188+
189+ < CollapsibleCardHeading onCollapseToggled = { this . props . onCollapseToggled } >
190+ Export
191+ </ CollapsibleCardHeading >
192+ </ header >
193+
194+ { isPaidUser ?
195+ < div >
196+ < ExportSnippetEditor
202197 exchange = { exchange }
203198 exportOption = { this . snippetOption }
204199 />
205- }
206- </ div >
207- :
208- < CardSalesPitch source = 'export' >
209- < p >
210- Instantly export requests as code, for languages and tools including cURL, wget, JS
211- (XHR, Node HTTP, Request, ...), Python (native or Requests), Ruby, Java (OkHttp
212- or Unirest), Go, PHP, Swift, HTTPie, and a whole lot more.
213- </ p >
214- < p >
215- Want to save the exchange itself? Export one or all requests as HAR (the{ ' ' }
216- < a href = "https://en.wikipedia.org/wiki/.har" > HTTP Archive Format</ a > ), to import
217- and examine elsewhere, share with your team, or store for future reference.
218- </ p >
219- </ CardSalesPitch >
220- }
221- </ CollapsibleCard > ;
222- }
223-
224- @computed
225- private get isZipSelected ( ) : boolean {
226- return ( this . props . uiStore ! . exportSnippetFormat || '' ) === ZIP_ALL_FORMAT_KEY ;
227- }
228-
229- @computed
230- private get currentDropdownValue ( ) : SnippetOption {
231- if ( this . isZipSelected ) return ZIP_SNIPPET_OPTION ;
232- return this . snippetOption ;
200+ </ div >
201+ :
202+ < CardSalesPitch source = 'export' >
203+ < p >
204+ Instantly export requests as code, for languages and tools including cURL, wget, JS
205+ (XHR, Node HTTP, Request, ...), Python (native or Requests), Ruby, Java (OkHttp
206+ or Unirest), Go, PHP, Swift, HTTPie, and a whole lot more.
207+ </ p >
208+ < p >
209+ Want to save the exchange itself? Export one or all requests as HAR (the{ ' ' }
210+ < a href = "https://en.wikipedia.org/wiki/.har" > HTTP Archive Format</ a > ), to import
211+ and examine elsewhere, share with your team, or store for future reference.
212+ </ p >
213+ </ CardSalesPitch >
214+ }
215+ </ CollapsibleCard >
216+ { /*
217+ * Dialog intentionally rendered OUTSIDE the CollapsibleCard.
218+ * `CollapsibleCard.renderChildren()` discards all children
219+ * after child 0 when the card is collapsed; placed there the
220+ * dialog JSX would never appear in the DOM when the card is
221+ * closed. The modal component uses a portal internally anyway,
222+ * so its position in the React tree does not matter.
223+ */ }
224+ { this . zipDialogOpen && < ZipExportDialog
225+ events = { [ exchange as unknown as CollectedEvent ] }
226+ onClose = { this . closeZipDialog }
227+ titleSuffix = '1 request'
228+ /> }
229+ </ > ;
233230 }
234231
235232 @computed
236233 private get snippetOption ( ) : SnippetOption {
237234 let exportSnippetFormat = this . props . uiStore ! . exportSnippetFormat ||
238235 DEFAULT_SNIPPET_FORMAT_KEY ;
239- // If ZIP is selected, fall back to default for the snippet option
240- if ( exportSnippetFormat === ZIP_ALL_FORMAT_KEY ) {
241- exportSnippetFormat = DEFAULT_SNIPPET_FORMAT_KEY ;
242- }
243- // Guard: if the format key doesn't resolve (e.g. deleted/invalid key),
244- // fall back to the default cURL option
245- return getCodeSnippetOptionFromKey ( exportSnippetFormat )
246- ?? getCodeSnippetOptionFromKey ( DEFAULT_SNIPPET_FORMAT_KEY ) ;
236+ return getSafeCodeSnippetOptionFromKey ( exportSnippetFormat ) ;
247237 }
248238
249239 @action . bound
250240 setSnippetOption ( optionKey : string ) {
251241 this . props . uiStore ! . exportSnippetFormat = optionKey ;
252242 }
253- } ;
243+ } ;
244+