UNPKG

ckeditor5-image-upload-base64

Version:

The development environment of CKEditor 5 – the best browser-based rich text editor.

277 lines (232 loc) 9.95 kB
/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ /** * @module image/imageupload/imageuploadprogress */ /* globals setTimeout */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository'; import uploadingPlaceholder from '../../theme/icons/image_placeholder.svg'; import { getViewImgFromWidget } from '../image/utils'; import '../../theme/imageuploadprogress.css'; import '../../theme/imageuploadicon.css'; import '../../theme/imageuploadloader.css'; /** * The image upload progress plugin. * It shows a placeholder when the image is read from the disk and a progress bar while the image is uploading. * * @extends module:core/plugin~Plugin */ export default class ImageUploadProgress extends Plugin { /** * @inheritDoc */ constructor( editor ) { super( editor ); /** * The image placeholder that is displayed before real image data can be accessed. * * @protected * @member {String} #placeholder */ this.placeholder = 'data:image/svg+xml;utf8,' + encodeURIComponent( uploadingPlaceholder ); } /** * @inheritDoc */ init() { const editor = this.editor; // Upload status change - update image's view according to that status. editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) ); } /** * This method is called each time the image `uploadStatus` attribute is changed. * * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event. * @param {Object} data Additional information about the change. * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi */ uploadStatusChange( evt, data, conversionApi ) { const editor = this.editor; const modelImage = data.item; const uploadId = modelImage.getAttribute( 'uploadId' ); if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { return; } const fileRepository = editor.plugins.get( FileRepository ); const status = uploadId ? data.attributeNewValue : null; const placeholder = this.placeholder; const viewFigure = editor.editing.mapper.toViewElement( modelImage ); const viewWriter = conversionApi.writer; if ( status == 'reading' ) { // Start "appearing" effect and show placeholder with infinite progress bar on the top // while image is read from disk. _startAppearEffect( viewFigure, viewWriter ); _showPlaceholder( placeholder, viewFigure, viewWriter ); return; } // Show progress bar on the top of the image when image is uploading. if ( status == 'uploading' ) { const loader = fileRepository.loaders.get( uploadId ); // Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191. _startAppearEffect( viewFigure, viewWriter ); if ( !loader ) { // There is no loader associated with uploadId - this means that image came from external changes. // In such cases we still want to show the placeholder until image is fully uploaded. // Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191. _showPlaceholder( placeholder, viewFigure, viewWriter ); } else { // Hide placeholder and initialize progress bar showing upload progress. _hidePlaceholder( viewFigure, viewWriter ); _showProgressBar( viewFigure, viewWriter, loader, editor.editing.view ); _displayLocalImage( viewFigure, viewWriter, loader ); } return; } if ( status == 'complete' && fileRepository.loaders.get( uploadId ) ) { _showCompleteIcon( viewFigure, viewWriter, editor.editing.view ); } // Clean up. _hideProgressBar( viewFigure, viewWriter ); _hidePlaceholder( viewFigure, viewWriter ); _stopAppearEffect( viewFigure, viewWriter ); } } // Adds ck-appear class to the image figure if one is not already applied. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer function _startAppearEffect( viewFigure, writer ) { if ( !viewFigure.hasClass( 'ck-appear' ) ) { writer.addClass( 'ck-appear', viewFigure ); } } // Removes ck-appear class to the image figure if one is not already removed. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer function _stopAppearEffect( viewFigure, writer ) { writer.removeClass( 'ck-appear', viewFigure ); } // Shows placeholder together with infinite progress bar on given image figure. // // @param {String} Data-uri with a svg placeholder. // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer function _showPlaceholder( placeholder, viewFigure, writer ) { if ( !viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) { writer.addClass( 'ck-image-upload-placeholder', viewFigure ); } const viewImg = getViewImgFromWidget( viewFigure ); if ( viewImg.getAttribute( 'src' ) !== placeholder ) { writer.setAttribute( 'src', placeholder, viewImg ); } if ( !_getUIElement( viewFigure, 'placeholder' ) ) { writer.insert( writer.createPositionAfter( viewImg ), _createPlaceholder( writer ) ); } } // Removes placeholder together with infinite progress bar on given image figure. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer function _hidePlaceholder( viewFigure, writer ) { if ( viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) { writer.removeClass( 'ck-image-upload-placeholder', viewFigure ); } _removeUIElement( viewFigure, writer, 'placeholder' ); } // Shows progress bar displaying upload progress. // Attaches it to the file loader to update when upload percentace is changed. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @param {module:upload/filerepository~FileLoader} loader // @param {module:engine/view/view~View} view function _showProgressBar( viewFigure, writer, loader, view ) { const progressBar = _createProgressBar( writer ); writer.insert( writer.createPositionAt( viewFigure, 'end' ), progressBar ); // Update progress bar width when uploadedPercent is changed. loader.on( 'change:uploadedPercent', ( evt, name, value ) => { view.change( writer => { writer.setStyle( 'width', value + '%', progressBar ); } ); } ); } // Hides upload progress bar. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer function _hideProgressBar( viewFigure, writer ) { _removeUIElement( viewFigure, writer, 'progressBar' ); } // Shows complete icon and hides after a certain amount of time. // // @param {module:engine/view/containerelement~ContainerElement} viewFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @param {module:engine/view/view~View} view function _showCompleteIcon( viewFigure, writer, view ) { const completeIcon = writer.createUIElement( 'div', { class: 'ck-image-upload-complete-icon' } ); writer.insert( writer.createPositionAt( viewFigure, 'end' ), completeIcon ); setTimeout( () => { view.change( writer => writer.remove( writer.createRangeOn( completeIcon ) ) ); }, 3000 ); } // Create progress bar element using {@link module:engine/view/uielement~UIElement}. // // @private // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @returns {module:engine/view/uielement~UIElement} function _createProgressBar( writer ) { const progressBar = writer.createUIElement( 'div', { class: 'ck-progress-bar' } ); writer.setCustomProperty( 'progressBar', true, progressBar ); return progressBar; } // Create placeholder element using {@link module:engine/view/uielement~UIElement}. // // @private // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @returns {module:engine/view/uielement~UIElement} function _createPlaceholder( writer ) { const placeholder = writer.createUIElement( 'div', { class: 'ck-upload-placeholder-loader' } ); writer.setCustomProperty( 'placeholder', true, placeholder ); return placeholder; } // Returns {@link module:engine/view/uielement~UIElement} of given unique property from image figure element. // Returns `undefined` if element is not found. // // @private // @param {module:engine/view/element~Element} imageFigure // @param {String} uniqueProperty // @returns {module:engine/view/uielement~UIElement|undefined} function _getUIElement( imageFigure, uniqueProperty ) { for ( const child of imageFigure.getChildren() ) { if ( child.getCustomProperty( uniqueProperty ) ) { return child; } } } // Removes {@link module:engine/view/uielement~UIElement} of given unique property from image figure element. // // @private // @param {module:engine/view/element~Element} imageFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @param {String} uniqueProperty function _removeUIElement( viewFigure, writer, uniqueProperty ) { const element = _getUIElement( viewFigure, uniqueProperty ); if ( element ) { writer.remove( writer.createRangeOn( element ) ); } } // Displays local data from file loader. // // @param {module:engine/view/element~Element} imageFigure // @param {module:engine/view/downcastwriter~DowncastWriter} writer // @param {module:upload/filerepository~FileLoader} loader function _displayLocalImage( viewFigure, writer, loader ) { if ( loader.data ) { const viewImg = getViewImgFromWidget( viewFigure ); writer.setAttribute( 'src', loader.data, viewImg ); } }