Skip to content

Commit 5afc5a9

Browse files
author
Frederic Collonval
committed
Switch menu on the flight
1 parent e1220f6 commit 5afc5a9

File tree

2 files changed

+130
-66
lines changed

2 files changed

+130
-66
lines changed

jupyter_archive/handlers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
# The delay in ms at which we send the chunk of data
1313
# to the client.
1414
ARCHIVE_DOWNLOAD_FLUSH_DELAY = 100
15+
SUPPORTED_FORMAT = [
16+
"zip",
17+
"tgz",
18+
"tar.gz",
19+
"tbz",
20+
"tbz2",
21+
"tar.bz",
22+
"tar.bz2",
23+
"txz",
24+
"tar.xz"
25+
]
1526

1627

1728
class ArchiveStream():
@@ -84,6 +95,9 @@ def get(self, archive_path, include_body=False):
8495

8596
archive_token = self.get_argument('archiveToken')
8697
archive_format = self.get_argument('archiveFormat', 'zip')
98+
if archive_format not in SUPPORTED_FORMAT:
99+
self.log.error("Unsupported format {}.".format(archive_format))
100+
raise web.HTTPError(404)
87101

88102
archive_path = os.path.join(cm.root_dir, url2path(archive_path))
89103

src/index.ts

Lines changed: 116 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@ import {
22
JupyterFrontEnd,
33
JupyterFrontEndPlugin
44
} from "@jupyterlab/application";
5-
6-
import { each } from "@phosphor/algorithm";
5+
import { showErrorMessage } from "@jupyterlab/apputils";
6+
import { ISettingRegistry, URLExt } from "@jupyterlab/coreutils";
77
import { IFileBrowserFactory } from "@jupyterlab/filebrowser";
88
import { ServerConnection } from "@jupyterlab/services";
9-
import { URLExt, ISettingRegistry } from "@jupyterlab/coreutils";
10-
import { showErrorMessage, showDialog, Dialog } from "@jupyterlab/apputils";
9+
import { each } from "@phosphor/algorithm";
10+
import { IDisposable } from "@phosphor/disposable";
1111
import { Menu } from "@phosphor/widgets";
1212

1313
const DIRECTORIES_URL = "directories";
1414
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";
1526

1627
namespace CommandIDs {
1728
export const downloadArchive = "filebrowser:download-archive";
@@ -22,7 +33,7 @@ namespace CommandIDs {
2233

2334
function downloadArchiveRequest(
2435
path: string,
25-
archiveFormat: string
36+
archiveFormat: ArchiveFormat
2637
): Promise<void> {
2738
const settings = ServerConnection.makeSettings();
2839

@@ -120,59 +131,97 @@ const extension: JupyterFrontEndPlugin<void> = {
120131
".txz",
121132
".tar.xz"
122133
];
123-
let archiveFormat: string = "zip";
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"]';
124141

125-
// Create submenu
126-
const archives = new Menu({
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({
127149
commands
128150
});
129-
archives.title.label = "Download As ";
130-
archives.title.iconClass = "jp-MaterialIcon jp-DownloadIcon";
151+
archiveCurrentFolder.title.label = "Download Current Folder As";
152+
archiveCurrentFolder.title.iconClass = "jp-MaterialIcon jp-DownloadIcon";
131153

132-
["zip", "tar.bz2", "tar.gz", "tar.xz"].forEach(format =>
133-
archives.addItem({
154+
["zip", "tar.bz2", "tar.gz", "tar.xz"].forEach(format => {
155+
archiveFolder.addItem({
134156
command: CommandIDs.downloadArchive,
135157
args: { format }
136-
})
137-
);
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+
}
138213

139214
// Load the settings
140215
settingRegistry
141216
.load("@hadim/jupyter-archive:archive")
142217
.then(settings => {
143218
settings.changed.connect(settings => {
144-
const newFormat = settings.get("format").composite as string;
145-
if (
146-
newFormat !== archiveFormat &&
147-
(newFormat === null || archiveFormat === null)
148-
) {
149-
showDialog({
150-
title: "Information",
151-
body:
152-
"You will need to reload the page to apply the new default archive format.",
153-
buttons: [Dialog.okButton()]
154-
});
155-
} else {
156-
archiveFormat = newFormat;
157-
}
219+
const newFormat = settings.get("format").composite as ArchiveFormat;
220+
updateFormat(newFormat, archiveFormat);
158221
});
159-
archiveFormat = settings.get("format").composite as string;
160-
})
161-
.then(() => {
162-
if (archiveFormat === null) {
163-
app.contextMenu.addItem({
164-
selector: selectorOnlyDir,
165-
rank: 10,
166-
type: "submenu",
167-
submenu: archives
168-
});
169-
} else {
170-
app.contextMenu.addItem({
171-
command: CommandIDs.downloadArchive,
172-
selector: selectorOnlyDir,
173-
rank: 10
174-
});
175-
}
222+
223+
const newFormat = settings.get("format").composite as ArchiveFormat;
224+
updateFormat(newFormat, archiveFormat);
176225
})
177226
.catch(reason => {
178227
console.error(reason);
@@ -182,20 +231,14 @@ const extension: JupyterFrontEndPlugin<void> = {
182231
);
183232
});
184233

185-
// matches anywhere on filebrowser
186-
const selectorContent = ".jp-DirListing-content";
187-
188-
// matches all filebrowser items
189-
const selectorOnlyDir = '.jp-DirListing-item[data-isdir="true"]';
190-
191234
// Add the 'downloadArchive' command to the file's menu.
192235
commands.addCommand(CommandIDs.downloadArchive, {
193236
execute: args => {
194237
const widget = tracker.currentWidget;
195238
if (widget) {
196239
each(widget.selectedItems(), item => {
197240
if (item.type == "directory") {
198-
const format = args["format"] as string;
241+
const format = args["format"] as ArchiveFormat;
199242
downloadArchiveRequest(
200243
item.path,
201244
allowedArchiveExtensions.indexOf("." + format) >= 0
@@ -209,9 +252,9 @@ const extension: JupyterFrontEndPlugin<void> = {
209252
iconClass: args =>
210253
"format" in args ? "" : "jp-MaterialIcon jp-DownloadIcon",
211254
label: args => {
212-
const format = (args["format"] as string) || "";
213-
const label = format.replace(".", " ");
214-
return label ? `${label} Archive` : "Download as an archive";
255+
const format = (args["format"] as ArchiveFormat) || "";
256+
const label = format.replace(".", " ").toLocaleUpperCase();
257+
return label ? `${label} Archive` : "Download as an Archive";
215258
}
216259
});
217260

@@ -226,7 +269,7 @@ const extension: JupyterFrontEndPlugin<void> = {
226269
}
227270
},
228271
iconClass: "jp-MaterialIcon jp-DownCaretIcon",
229-
label: "Extract archive"
272+
label: "Extract Archive"
230273
});
231274

232275
// Add a command for each archive extensions
@@ -242,20 +285,27 @@ const extension: JupyterFrontEndPlugin<void> = {
242285

243286
// Add the 'downloadArchiveCurrentFolder' command to file browser content.
244287
commands.addCommand(CommandIDs.downloadArchiveCurrentFolder, {
245-
execute: () => {
288+
execute: args => {
246289
const widget = tracker.currentWidget;
247290
if (widget) {
248-
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+
);
249298
}
250299
},
251-
iconClass: "jp-MaterialIcon jp-DownloadIcon",
252-
label: "Download current folder as an archive"
253-
});
254-
255-
app.contextMenu.addItem({
256-
command: CommandIDs.downloadArchiveCurrentFolder,
257-
selector: selectorContent,
258-
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+
}
259309
});
260310
}
261311
};

0 commit comments

Comments
 (0)