@@ -2,25 +2,38 @@ import {
2
2
JupyterFrontEnd ,
3
3
JupyterFrontEndPlugin
4
4
} from "@jupyterlab/application" ;
5
-
6
- import { each } from "@phosphor/algorithm " ;
5
+ import { showErrorMessage } from "@jupyterlab/apputils" ;
6
+ import { ISettingRegistry , URLExt } from "@jupyterlab/coreutils " ;
7
7
import { IFileBrowserFactory } from "@jupyterlab/filebrowser" ;
8
8
import { ServerConnection } from "@jupyterlab/services" ;
9
- import { URLExt , ISettingRegistry } from "@jupyterlab/coreutils" ;
10
- import { showErrorMessage } from "@jupyterlab/apputils" ;
9
+ import { each } from "@phosphor/algorithm" ;
10
+ import { IDisposable } from "@phosphor/disposable" ;
11
+ import { Menu } from "@phosphor/widgets" ;
11
12
12
13
const DIRECTORIES_URL = "directories" ;
13
14
const EXTRACT_ARCHVE_URL = "extract-archive" ;
15
+ type ArchiveFormat =
16
+ | null
17
+ | "zip"
18
+ | "tgz"
19
+ | "tar.gz"
20
+ | "tbz"
21
+ | "tbz2"
22
+ | "tar.bz"
23
+ | "tar.bz2"
24
+ | "txz"
25
+ | "tar.xz" ;
14
26
15
27
namespace CommandIDs {
16
28
export const downloadArchive = "filebrowser:download-archive" ;
17
29
export const extractArchive = "filebrowser:extract-archive" ;
18
- export const downloadArchiveCurrentFolder = "filebrowser:download-archive-current-folder" ;
30
+ export const downloadArchiveCurrentFolder =
31
+ "filebrowser:download-archive-current-folder" ;
19
32
}
20
33
21
34
function downloadArchiveRequest (
22
35
path : string ,
23
- archiveFormat : string
36
+ archiveFormat : ArchiveFormat
24
37
) : Promise < void > {
25
38
const settings = ServerConnection . makeSettings ( ) ;
26
39
@@ -56,8 +69,8 @@ function downloadArchiveRequest(
56
69
} else {
57
70
let element = document . createElement ( "a" ) ;
58
71
document . body . appendChild ( element ) ;
59
- element . setAttribute ( ' href' , url ) ;
60
- element . setAttribute ( ' download' , '' ) ;
72
+ element . setAttribute ( " href" , url ) ;
73
+ element . setAttribute ( " download" , "" ) ;
61
74
element . click ( ) ;
62
75
document . body . removeChild ( element ) ;
63
76
}
@@ -107,54 +120,145 @@ const extension: JupyterFrontEndPlugin<void> = {
107
120
const { commands } = app ;
108
121
const { tracker } = factory ;
109
122
110
- let archiveFormat : string = "zip" ;
123
+ const allowedArchiveExtensions = [
124
+ ".zip" ,
125
+ ".tgz" ,
126
+ ".tar.gz" ,
127
+ ".tbz" ,
128
+ ".tbz2" ,
129
+ ".tar.bz" ,
130
+ ".tar.bz2" ,
131
+ ".txz" ,
132
+ ".tar.xz"
133
+ ] ;
134
+ let archiveFormat : ArchiveFormat ; // Default value read from settings
135
+
136
+ // matches anywhere on filebrowser
137
+ const selectorContent = ".jp-DirListing-content" ;
138
+
139
+ // matches all filebrowser items
140
+ const selectorOnlyDir = '.jp-DirListing-item[data-isdir="true"]' ;
141
+
142
+ // Create submenus
143
+ const archiveFolder = new Menu ( {
144
+ commands
145
+ } ) ;
146
+ archiveFolder . title . label = "Download As" ;
147
+ archiveFolder . title . iconClass = "jp-MaterialIcon jp-DownloadIcon" ;
148
+ const archiveCurrentFolder = new Menu ( {
149
+ commands
150
+ } ) ;
151
+ archiveCurrentFolder . title . label = "Download Current Folder As" ;
152
+ archiveCurrentFolder . title . iconClass = "jp-MaterialIcon jp-DownloadIcon" ;
153
+
154
+ [ "zip" , "tar.bz2" , "tar.gz" , "tar.xz" ] . forEach ( format => {
155
+ archiveFolder . addItem ( {
156
+ command : CommandIDs . downloadArchive ,
157
+ args : { format }
158
+ } ) ;
159
+ archiveCurrentFolder . addItem ( {
160
+ command : CommandIDs . downloadArchiveCurrentFolder ,
161
+ args : { format }
162
+ } ) ;
163
+ } ) ;
164
+
165
+ // Reference to menu items
166
+ let archiveFolderItem : IDisposable ;
167
+ let archiveCurrentFolderItem : IDisposable ;
168
+
169
+ function updateFormat ( newFormat : ArchiveFormat , oldFormat : ArchiveFormat ) {
170
+ if ( newFormat !== oldFormat ) {
171
+ if (
172
+ newFormat === null ||
173
+ oldFormat === null ||
174
+ oldFormat === undefined
175
+ ) {
176
+ if ( oldFormat !== undefined ) {
177
+ archiveFolderItem . dispose ( ) ;
178
+ archiveCurrentFolderItem . dispose ( ) ;
179
+ }
180
+
181
+ if ( newFormat === null ) {
182
+ archiveFolderItem = app . contextMenu . addItem ( {
183
+ selector : selectorOnlyDir ,
184
+ rank : 10 ,
185
+ type : "submenu" ,
186
+ submenu : archiveFolder
187
+ } ) ;
188
+
189
+ archiveCurrentFolderItem = app . contextMenu . addItem ( {
190
+ selector : selectorContent ,
191
+ rank : 3 ,
192
+ type : "submenu" ,
193
+ submenu : archiveCurrentFolder
194
+ } ) ;
195
+ } else {
196
+ archiveFolderItem = app . contextMenu . addItem ( {
197
+ command : CommandIDs . downloadArchive ,
198
+ selector : selectorOnlyDir ,
199
+ rank : 10
200
+ } ) ;
201
+
202
+ archiveCurrentFolderItem = app . contextMenu . addItem ( {
203
+ command : CommandIDs . downloadArchiveCurrentFolder ,
204
+ selector : selectorContent ,
205
+ rank : 3
206
+ } ) ;
207
+ }
208
+ }
209
+
210
+ archiveFormat = newFormat ;
211
+ }
212
+ }
111
213
112
214
// Load the settings
113
215
settingRegistry
114
216
. load ( "@hadim/jupyter-archive:archive" )
115
217
. then ( settings => {
116
218
settings . changed . connect ( settings => {
117
- archiveFormat = settings . get ( "format" ) . composite as string ;
219
+ const newFormat = settings . get ( "format" ) . composite as ArchiveFormat ;
220
+ updateFormat ( newFormat , archiveFormat ) ;
118
221
} ) ;
119
- archiveFormat = settings . get ( "format" ) . composite as string ;
222
+
223
+ const newFormat = settings . get ( "format" ) . composite as ArchiveFormat ;
224
+ updateFormat ( newFormat , archiveFormat ) ;
120
225
} )
121
226
. catch ( reason => {
122
227
console . error ( reason ) ;
123
228
showErrorMessage (
124
- "Fail to read settings for '@jupyterlab/ archive:archive'" ,
229
+ "Fail to read settings for '@hadim/jupyter- archive:archive'" ,
125
230
reason
126
231
) ;
127
232
} ) ;
128
233
129
- // matches anywhere on filebrowser
130
- const selectorContent = '.jp-DirListing-content' ;
131
-
132
- // matches all filebrowser items
133
- const selectorOnlyDir = '.jp-DirListing-item[data-isdir="true"]' ;
134
-
135
- // Add the 'download_archive' command to the file's menu.
234
+ // Add the 'downloadArchive' command to the file's menu.
136
235
commands . addCommand ( CommandIDs . downloadArchive , {
137
- execute : ( ) => {
236
+ execute : args => {
138
237
const widget = tracker . currentWidget ;
139
238
if ( widget ) {
140
239
each ( widget . selectedItems ( ) , item => {
141
240
if ( item . type == "directory" ) {
142
- downloadArchiveRequest ( item . path , archiveFormat ) ;
241
+ const format = args [ "format" ] as ArchiveFormat ;
242
+ downloadArchiveRequest (
243
+ item . path ,
244
+ allowedArchiveExtensions . indexOf ( "." + format ) >= 0
245
+ ? format
246
+ : archiveFormat
247
+ ) ;
143
248
}
144
249
} ) ;
145
250
}
146
251
} ,
147
- iconClass : "jp-MaterialIcon jp-DownloadIcon" ,
148
- label : "Download as an archive"
149
- } ) ;
150
-
151
- app . contextMenu . addItem ( {
152
- command : CommandIDs . downloadArchive ,
153
- selector : selectorOnlyDir ,
154
- rank : 10
252
+ iconClass : args =>
253
+ "format" in args ? "" : "jp-MaterialIcon jp-DownloadIcon" ,
254
+ label : args => {
255
+ const format = ( args [ "format" ] as ArchiveFormat ) || "" ;
256
+ const label = format . replace ( "." , " " ) . toLocaleUpperCase ( ) ;
257
+ return label ? `${ label } Archive` : "Download as an Archive" ;
258
+ }
155
259
} ) ;
156
260
157
- // Add the 'extract_archive ' command to the file's menu.
261
+ // Add the 'extractArchive ' command to the file's menu.
158
262
commands . addCommand ( CommandIDs . extractArchive , {
159
263
execute : ( ) => {
160
264
const widget = tracker . currentWidget ;
@@ -165,14 +269,11 @@ const extension: JupyterFrontEndPlugin<void> = {
165
269
}
166
270
} ,
167
271
iconClass : "jp-MaterialIcon jp-DownCaretIcon" ,
168
- label : "Extract archive "
272
+ label : "Extract Archive "
169
273
} ) ;
170
274
171
275
// Add a command for each archive extensions
172
276
// TODO: use only one command and accept multiple extensions.
173
- const allowedArchiveExtensions = [ ".zip" , ".tgz" , ".tar.gz" , ".tbz" , ".tbz2" ,
174
- ".tar.bz" , ".tar.bz2" , ".txz" , ".tar.xz" ]
175
-
176
277
allowedArchiveExtensions . forEach ( extension => {
177
278
const selector = '.jp-DirListing-item[title$="' + extension + '"]' ;
178
279
app . contextMenu . addItem ( {
@@ -182,24 +283,30 @@ const extension: JupyterFrontEndPlugin<void> = {
182
283
} ) ;
183
284
} ) ;
184
285
185
- // Add the 'download_archive ' command to fiel browser content.
286
+ // Add the 'downloadArchiveCurrentFolder ' command to file browser content.
186
287
commands . addCommand ( CommandIDs . downloadArchiveCurrentFolder , {
187
- execute : ( ) => {
288
+ execute : args => {
188
289
const widget = tracker . currentWidget ;
189
290
if ( widget ) {
190
- downloadArchiveRequest ( widget . model . path , archiveFormat ) ;
291
+ const format = args [ "format" ] as ArchiveFormat ;
292
+ downloadArchiveRequest (
293
+ widget . model . path ,
294
+ allowedArchiveExtensions . indexOf ( "." + format ) >= 0
295
+ ? format
296
+ : archiveFormat
297
+ ) ;
191
298
}
192
299
} ,
193
- iconClass : "jp-MaterialIcon jp-DownloadIcon" ,
194
- label : "Download current folder as an archive"
195
- } ) ;
196
-
197
- app . contextMenu . addItem ( {
198
- command : CommandIDs . downloadArchiveCurrentFolder ,
199
- selector : selectorContent ,
200
- rank : 3
300
+ iconClass : args =>
301
+ "format" in args ? "" : "jp-MaterialIcon jp-DownloadIcon" ,
302
+ label : args => {
303
+ const format = ( args [ "format" ] as ArchiveFormat ) || "" ;
304
+ const label = format . replace ( "." , " " ) . toLocaleUpperCase ( ) ;
305
+ return label
306
+ ? `${ label } Archive`
307
+ : "Download Current Folder as an Archive" ;
308
+ }
201
309
} ) ;
202
-
203
310
}
204
311
} ;
205
312
0 commit comments