|
@@ -16,6 +16,8 @@ import {
|
|
|
|
|
|
import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
|
|
|
|
|
|
+import { DirListing } from '@jupyterlab/filebrowser';
|
|
|
+
|
|
|
import { IObservableMap } from '@jupyterlab/observables';
|
|
|
|
|
|
import {
|
|
@@ -34,9 +36,16 @@ import {
|
|
|
imageRendererFactory
|
|
|
} from '@jupyterlab/rendermime';
|
|
|
|
|
|
-import { KernelMessage, Kernel, Contents } from '@jupyterlab/services';
|
|
|
+import { KernelMessage, Kernel } from '@jupyterlab/services';
|
|
|
+
|
|
|
+import {
|
|
|
+ JSONValue,
|
|
|
+ PromiseDelegate,
|
|
|
+ JSONObject,
|
|
|
+ UUID
|
|
|
+} from '@phosphor/coreutils';
|
|
|
|
|
|
-import { JSONValue, PromiseDelegate, JSONObject } from '@phosphor/coreutils';
|
|
|
+import { some, filter, toArray } from '@phosphor/algorithm';
|
|
|
|
|
|
import { IDragEvent } from '@phosphor/dragdrop';
|
|
|
|
|
@@ -64,7 +73,6 @@ import {
|
|
|
} from './model';
|
|
|
|
|
|
import { InputPlaceholder, OutputPlaceholder } from './placeholder';
|
|
|
-import { toArray, filter, map, IIterator, find } from '@phosphor/algorithm';
|
|
|
|
|
|
/**
|
|
|
* The CSS class added to cell widgets.
|
|
@@ -154,9 +162,9 @@ const DEFAULT_MARKDOWN_TEXT = 'Type Markdown and LaTeX: $ α^2 $';
|
|
|
const RENDER_TIMEOUT = 1000;
|
|
|
|
|
|
/**
|
|
|
- * Type for attachment thunks.
|
|
|
+ * The mime type for a rich contents drag object.
|
|
|
*/
|
|
|
-type AttachmentThunk = () => Promise<Contents.IModel>;
|
|
|
+const CONTENTS_MIME_RICH = 'application/x-jupyter-icontentsrich';
|
|
|
|
|
|
/******************************************************************************
|
|
|
* Cell
|
|
@@ -1075,7 +1083,7 @@ export namespace CodeCell {
|
|
|
* `AttachmentsCell` - A base class for a cell widget that allows
|
|
|
* attachments to be drag/drop'd or pasted onto it
|
|
|
*/
|
|
|
-export class AttachmentsCell extends Cell {
|
|
|
+export abstract class AttachmentsCell extends Cell {
|
|
|
/**
|
|
|
* Handle the DOM events for the widget.
|
|
|
*
|
|
@@ -1111,10 +1119,51 @@ export class AttachmentsCell extends Cell {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Modify the cell source to include a reference to the attachment.
|
|
|
+ */
|
|
|
+ protected abstract updateCellSourceWithAttachment(
|
|
|
+ attachmentName: string
|
|
|
+ ): void;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handle `after-attach` messages for the widget.
|
|
|
+ */
|
|
|
+ protected onAfterAttach(msg: Message): void {
|
|
|
+ super.onAfterAttach(msg);
|
|
|
+ let node = this.node;
|
|
|
+ node.addEventListener('p-dragover', this);
|
|
|
+ node.addEventListener('p-drop', this);
|
|
|
+ node.addEventListener('dragenter', this);
|
|
|
+ node.addEventListener('dragover', this);
|
|
|
+ node.addEventListener('drop', this);
|
|
|
+ node.addEventListener('paste', this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A message handler invoked on a `'before-detach'`
|
|
|
+ * message
|
|
|
+ */
|
|
|
+ protected onBeforeDetach(msg: Message): void {
|
|
|
+ let node = this.node;
|
|
|
+ node.removeEventListener('drop', this);
|
|
|
+ node.removeEventListener('dragover', this);
|
|
|
+ node.removeEventListener('dragenter', this);
|
|
|
+ node.removeEventListener('paste', this);
|
|
|
+ node.removeEventListener('p-dragover', this);
|
|
|
+ node.removeEventListener('p-drop', this);
|
|
|
+ }
|
|
|
+
|
|
|
private _evtDragOver(event: IDragEvent) {
|
|
|
- const supportedMimeType = find(imageRendererFactory.mimeTypes, mimeType =>
|
|
|
- event.mimeData.hasData(`${mimeType};closure=true`)
|
|
|
- );
|
|
|
+ const supportedMimeType = some(imageRendererFactory.mimeTypes, mimeType => {
|
|
|
+ if (!event.mimeData.hasData(CONTENTS_MIME_RICH)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const data = event.mimeData.getData(
|
|
|
+ CONTENTS_MIME_RICH
|
|
|
+ ) as DirListing.IContentsThunk;
|
|
|
+ return data.model.mimetype === mimeType;
|
|
|
+ });
|
|
|
if (!supportedMimeType) {
|
|
|
return;
|
|
|
}
|
|
@@ -1128,6 +1177,7 @@ export class AttachmentsCell extends Cell {
|
|
|
*/
|
|
|
private _evtPaste(event: ClipboardEvent): void {
|
|
|
this._attachFiles(event.clipboardData.items);
|
|
|
+ event.preventDefault();
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1144,11 +1194,15 @@ export class AttachmentsCell extends Cell {
|
|
|
private _evtDrop(event: IDragEvent): void {
|
|
|
const supportedMimeTypes = toArray(
|
|
|
filter(event.mimeData.types(), mimeType => {
|
|
|
- const parsedMimeType = /(.*);closure=true/.exec(mimeType);
|
|
|
- return (
|
|
|
- parsedMimeType !== null &&
|
|
|
- imageRendererFactory.mimeTypes.indexOf(parsedMimeType[1]) !== -1
|
|
|
- );
|
|
|
+ if (mimeType === CONTENTS_MIME_RICH) {
|
|
|
+ const data = event.mimeData.getData(
|
|
|
+ CONTENTS_MIME_RICH
|
|
|
+ ) as DirListing.IContentsThunk;
|
|
|
+ return (
|
|
|
+ imageRendererFactory.mimeTypes.indexOf(data.model.mimetype) !== -1
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return imageRendererFactory.mimeTypes.indexOf(mimeType) !== -1;
|
|
|
})
|
|
|
);
|
|
|
if (supportedMimeTypes.length === 0) {
|
|
@@ -1160,50 +1214,30 @@ export class AttachmentsCell extends Cell {
|
|
|
event.dropAction = 'none';
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- const thunks: IIterator<AttachmentThunk> = map(
|
|
|
- supportedMimeTypes,
|
|
|
- mimeType => event.mimeData.getData(mimeType)
|
|
|
- );
|
|
|
- const promises = toArray(map(thunks, thunk => thunk()));
|
|
|
- void Promise.all(promises).then(models => {
|
|
|
- models.forEach(model => {
|
|
|
- if (model.type === 'file' && model.format === 'base64') {
|
|
|
- this.model.attachments.set(model.name, {
|
|
|
- [model.mimetype]: model.content
|
|
|
+ event.dropAction = 'copy';
|
|
|
+
|
|
|
+ for (const mimeType of supportedMimeTypes) {
|
|
|
+ if (mimeType === CONTENTS_MIME_RICH) {
|
|
|
+ const { model, withContent } = event.mimeData.getData(
|
|
|
+ CONTENTS_MIME_RICH
|
|
|
+ ) as DirListing.IContentsThunk;
|
|
|
+ if (model.type === 'file') {
|
|
|
+ this.updateCellSourceWithAttachment(model.name);
|
|
|
+ void withContent().then(fullModel => {
|
|
|
+ this.model.attachments.set(fullModel.name, {
|
|
|
+ [fullModel.mimetype]: fullModel.content
|
|
|
+ });
|
|
|
});
|
|
|
- this._updateCellSourceWithAttachment(model.name);
|
|
|
}
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Handle `after-attach` messages for the widget.
|
|
|
- */
|
|
|
- protected onAfterAttach(msg: Message): void {
|
|
|
- super.onAfterAttach(msg);
|
|
|
- let node = this.node;
|
|
|
- node.addEventListener('p-dragover', this);
|
|
|
- node.addEventListener('p-drop', this);
|
|
|
- node.addEventListener('dragenter', this);
|
|
|
- node.addEventListener('dragover', this);
|
|
|
- node.addEventListener('drop', this);
|
|
|
- node.addEventListener('paste', this);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * A message handler invoked on a `'before-detach'`
|
|
|
- * message
|
|
|
- */
|
|
|
- protected onBeforeDetach(msg: Message): void {
|
|
|
- let node = this.node;
|
|
|
- node.removeEventListener('drop', this);
|
|
|
- node.removeEventListener('dragover', this);
|
|
|
- node.removeEventListener('dragenter', this);
|
|
|
- node.removeEventListener('paste', this);
|
|
|
- node.removeEventListener('p-dragover', this);
|
|
|
- node.removeEventListener('p-drop', this);
|
|
|
+ } else {
|
|
|
+ // Pure mimetype, no useful name to infer
|
|
|
+ const name = UUID.uuid4();
|
|
|
+ this.model.attachments.set(name, {
|
|
|
+ [mimeType]: event.mimeData.getData(mimeType)
|
|
|
+ });
|
|
|
+ this.updateCellSourceWithAttachment(name);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1240,7 +1274,7 @@ export class AttachmentsCell extends Cell {
|
|
|
const encodedData = matches[3];
|
|
|
const bundle: nbformat.IMimeBundle = { [mimeType]: encodedData };
|
|
|
this.model.attachments.set(blob.name, bundle);
|
|
|
- this._updateCellSourceWithAttachment(blob.name);
|
|
|
+ this.updateCellSourceWithAttachment(blob.name);
|
|
|
};
|
|
|
reader.onerror = evt => {
|
|
|
console.error(`Failed to attach ${blob.name}` + evt);
|
|
@@ -1248,15 +1282,6 @@ export class AttachmentsCell extends Cell {
|
|
|
reader.readAsDataURL(blob);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Appends the text 
|
|
|
- * to the cell source
|
|
|
- */
|
|
|
- private _updateCellSourceWithAttachment(attachmentName: string) {
|
|
|
- const textToBeAppended = ``;
|
|
|
- this.model.value.insert(this.model.value.text.length, textToBeAppended);
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* The model used by the widget.
|
|
|
*/
|
|
@@ -1365,6 +1390,14 @@ export class MarkdownCell extends AttachmentsCell {
|
|
|
super.onUpdateRequest(msg);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Modify the cell source to include a reference to the attachment.
|
|
|
+ */
|
|
|
+ protected updateCellSourceWithAttachment(attachmentName: string) {
|
|
|
+ const textToBeAppended = ``;
|
|
|
+ this.model.value.insert(this.model.value.text.length, textToBeAppended);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Handle the rendered state.
|
|
|
*/
|