import { ReactWidget , UseSignal } from '@jupyterlab/apputils' ; import { JupyterFrontEnd } from '@jupyterlab/application' ; import { IEditorServices } from '@jupyterlab/codeeditor' ; import { Widget } from '@lumino/widgets' ; import { Message } from '@lumino/messaging' ; import { Signal } from '@lumino/signaling' ; import { IDragEvent } from '@lumino/dragdrop' ; import { MimeData } from '@lumino/coreutils' ; import { CodeSnippetService , ICodeSnippet } from './CodeSnippetService' ; import { CodeSnippetDisplay } from './CodeSnippetDisplay' ; import { CodeSnippetInputDialog } from './CodeSnippetInputDialog' ; import React from 'react' ; import { Notebook } from '@jupyterlab/notebook' ; /**
 * A class used to indicate a snippet item.
 */ const CODE_SNIPPET_ITEM = 'jp-codeSnippet-item' ; /**
 * The mimetype used for Jupyter cell data.
 */ const JUPYTER_CELL_MIME = 'application/vnd.jupyter.cells' ; /**
 * A class used to indicate a drop target.
 */ const DROP_TARGET_CLASS = 'jp-codeSnippet-dropTarget' ; const CODE_SNIPPET_EDITOR = 'jp-codeSnippet-editor' ; const CODE_SNIPPET_DRAG_HOVER = 'jp-codeSnippet-drag-hover' ; const commands = { OPEN_CODE_SNIPPET_EDITOR : `${ CODE_SNIPPET_EDITOR }:open` , } ; /**
 * A widget for Code Snippets.
 */ export class CodeSnippetWidget extends ReactWidget { getCurrentWidget : ( ) => Widget ; renderCodeSnippetsSignal : Signal < this , ICodeSnippet [ ] > ; app : JupyterFrontEnd ; codeSnippetManager : CodeSnippetService ; private editorServices : IEditorServices ; constructor ( getCurrentWidget , app , editorServices ) { super ( ) ; this . app = app ; this . editorServices = editorServices ; this . getCurrentWidget = getCurrentWidget ; this . renderCodeSnippetsSignal = new Signal < this , ICodeSnippet [ ] > ( this ) ; this . codeSnippetManager = CodeSnippetService . getCodeSnippetService ( ) ; this . moveCodeSnippet = this . moveCodeSnippet . bind ( this ) ; this . openCodeSnippetEditor = this . openCodeSnippetEditor . bind ( this ) ; this . updateCodeSnippetWidget = this . updateCodeSnippetWidget . bind ( this ) ; this . node . setAttribute ( 'data-lm-dragscroll' , 'true' ) ; } updateCodeSnippetWidget ( ) { const newSnippets = this . codeSnippetManager . snippets ; this . renderCodeSnippetsSignal . emit ( newSnippets ) ; } onAfterShow ( msg ) { this . updateCodeSnippetWidget ( ) ; } openCodeSnippetEditor ( args ) { this . app . commands . execute ( commands . OPEN_CODE_SNIPPET_EDITOR , args ) ; } param event handleEvent ( event ) { switch ( event . type ) { case 'lm-dragenter' : this . _evtDragEnter ( event as IDragEvent ) ; break ; case 'lm-dragleave' : this . _evtDragLeave ( event as IDragEvent ) ; break ; case 'lm-dragover' : this . _evtDragOver ( event as IDragEvent ) ; break ; case 'lm-drop' : this . _evtDrop ( event as IDragEvent ) ; break ; default : break ; } } param msg protected onAfterAttach ( msg ) { super . onAfterAttach ( msg ) ; const node = this . node ; node . addEventListener ( 'lm-dragenter' , this ) ; node . addEventListener ( 'lm-dragleave' , this ) ; node . addEventListener ( 'lm-dragover' , this ) ; node . addEventListener ( 'lm-drop' , this ) ; } param msg protected onBeforeDetach ( msg ) { const node = this . node ; node . removeEventListener ( 'lm-dragenter' , this ) ; node . removeEventListener ( 'lm-dragleave' , this ) ; node . removeEventListener ( 'lm-dragover' , this ) ; node . removeEventListener ( 'lm-drop' , this ) ; } /**
     * Find the snippet containing the target html element.
     *
     * #### Notes
     * Returns undefined if the cell is not found.
     */ private _findSnippet ( node ) { let n = node ; while ( n && n !== this . node ) { if ( n . classList . contains ( CODE_SNIPPET_ITEM ) ) { return n ; } n = n . parentElement ; } return undefined ; } /**
     * Handle the `'lm-dragenter'` event for the widget.
     */ private _evtDragEnter ( event ) { if ( ! event . mimeData . hasData ( JUPYTER_CELL_MIME ) ) { return ; } event . preventDefault ( ) ; event . stopPropagation ( ) ; const target = event . target as HTMLElement ; if ( ! event . mimeData . hasData ( 'snippet/id' ) ) { const snippetId = target . id . slice ( CODE_SNIPPET_DRAG_HOVER . length ) ; event . mimeData . setData ( 'snippet/id' , parseInt ( snippetId ) ) ; } const snippet = this . _findSnippet ( target ) ; if ( snippet === undefined ) { return ; } const snippetNode = snippet as HTMLElement ; snippetNode . classList . add ( DROP_TARGET_CLASS ) ; } /**
     * Handle the `'lm-dragleave'` event for the widget.
     */ private _evtDragLeave ( event ) { if ( ! event . mimeData . hasData ( JUPYTER_CELL_MIME ) ) { return ; } event . preventDefault ( ) ; event . stopPropagation ( ) ; const elements = this . node . getElementsByClassName ( DROP_TARGET_CLASS ) ; if ( elements . length ) { ( elements [ 0 ] as HTMLElement ) . classList . remove ( DROP_TARGET_CLASS ) ; } } /**
     * Handle the `'lm-dragover'` event for the widget.
     */ private _evtDragOver ( event ) { const data = this . findCellData ( event . mimeData ) ; if ( data === undefined ) { return ; } event . preventDefault ( ) ; event . stopPropagation ( ) ; event . dropAction = event . proposedAction ; const elements = this . node . getElementsByClassName ( DROP_TARGET_CLASS ) ; if ( elements . length ) { ( elements [ 0 ] as HTMLElement ) . classList . remove ( DROP_TARGET_CLASS ) ; } const target = event . target as HTMLElement ; const snippet = this . _findSnippet ( target ) ; if ( snippet === undefined ) { return ; } const snippetNode = snippet as HTMLElement ; snippetNode . classList . add ( DROP_TARGET_CLASS ) ; } private findCellData ( mime ) { const code = mime . getData ( 'text/plain' ) ; return code . split ( '\n' ) ; } /**
     * Handle the `'lm-drop'` event for the widget.
     */ private async _evtDrop ( event ) { const data = this . findCellData ( event . mimeData ) ; if ( data === undefined ) { return ; } event . preventDefault ( ) ; event . stopPropagation ( ) ; if ( event . proposedAction === 'none' ) { event . dropAction = 'none' ; return ; } let target = event . target as HTMLElement ; while ( target && target . parentElement ) { if ( target . classList . contains ( DROP_TARGET_CLASS ) ) { target . classList . remove ( DROP_TARGET_CLASS ) ; break ; } target = target . parentElement ; } const snippet = this . _findSnippet ( target ) ; let idx ; if ( snippet !== undefined ) { idx = parseInt ( snippet . id . slice ( CODE_SNIPPET_ITEM . length ) ) ; } else { idx = this . codeSnippetManager . snippets . length ; } /**
         * moving snippets inside the snippet panel
         */ const source = event . source ; if ( source instanceof CodeSnippetDisplay ) { event . dropAction = 'move' ; if ( event . mimeData . hasData ( 'snippet/id' ) ) { const srcIdx = event . mimeData . getData ( 'snippet/id' ) as number ; this . moveCodeSnippet ( srcIdx , idx ) ; } } else { const notebook = event . mimeData . getData ( 'internal:cells' ) [ 0 ] . parent ; const language = notebook . model . defaultKernelLanguage ; event . dropAction = 'copy' ; CodeSnippetInputDialog ( this , data , language , idx ) ; } this . codeSnippetManager . orderSnippets ( ) . then ( ( res ) => { if ( ! res ) { console . log ( 'Error in ordering snippets' ) ; return ; } } ) ; } private moveCodeSnippet ( srcIdx , targetIdx ) { this . codeSnippetManager . moveSnippet ( srcIdx , targetIdx ) . then ( ( res ) => { if ( ! res ) { console . log ( 'Error in moving snippet' ) ; return ; } } ) ; const newSnippets = this . codeSnippetManager . snippets ; this . renderCodeSnippetsSignal . emit ( newSnippets ) ; } render ( ) { return ( < UseSignal signal = { this . renderCodeSnippetsSignal } initialArgs = { [ ] } > { ( _ , codeSnippets ) : React . ReactElement => ( < div > < CodeSnippetDisplay codeSnippets = { codeSnippets } codeSnippetManager = { this . codeSnippetManager } app = { this . app } getCurrentWidget = { this . getCurrentWidget } openCodeSnippetEditor = { this . openCodeSnippetEditor } editorServices = { this . editorServices } updateCodeSnippetWidget = { this . updateCodeSnippetWidget } / > < /div>)} < /UseSignal>); } }