imageuploadprogress.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module upload/imageuploadprogress
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import { eventNameToConsumableType } from '@ckeditor/ckeditor5-engine/src/conversion/model-to-view-converters';
  10. import FileRepository from './filerepository';
  11. import uploadingPlaceholder from '../theme/icons/image_placeholder.svg';
  12. import UIElement from '@ckeditor/ckeditor5-engine/src/view/uielement';
  13. import ImageUploadEngine from './imageuploadengine';
  14. import '../theme/imageuploadprogress.scss';
  15. /**
  16. * Image upload progress plugin.
  17. * Shows placeholder when image is read from disk and progress bar while image is uploading.
  18. *
  19. * @extends module:core/plugin~Plugin
  20. */
  21. export default class ImageUploadProgress extends Plugin {
  22. /**
  23. * @inheritDoc
  24. */
  25. static get requires() {
  26. return [ ImageUploadEngine ];
  27. }
  28. constructor( editor ) {
  29. super( editor );
  30. /**
  31. * Image's placeholder that is displayed before real image data can be accessed.
  32. *
  33. * @protected
  34. * @member {String} #placeholder
  35. */
  36. this.placeholder = 'data:image/svg+xml;utf8,' + encodeURIComponent( uploadingPlaceholder );
  37. }
  38. init() {
  39. const editor = this.editor;
  40. // Upload status change - update image's view according to that status.
  41. editor.editing.modelToView.on( 'addAttribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
  42. editor.editing.modelToView.on( 'changeAttribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
  43. }
  44. /**
  45. * This ethod is called each time image's `uploadStatus` attribute is changed.
  46. *
  47. * @param {module:utils/eventinfo~EventInfo} evt Object containing information about the fired event.
  48. * @param {Object} data Additional information about the change.
  49. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  50. */
  51. uploadStatusChange( evt, data, consumable ) {
  52. const editor = this.editor;
  53. const modelImage = data.item;
  54. const uploadId = modelImage.getAttribute( 'uploadId' );
  55. if ( !consumable.consume( data.item, eventNameToConsumableType( evt.name ) ) || !uploadId ) {
  56. return;
  57. }
  58. const fileRepository = editor.plugins.get( FileRepository );
  59. const placeholder = this.placeholder;
  60. const status = data.attributeNewValue;
  61. const viewFigure = editor.editing.mapper.toViewElement( modelImage );
  62. // Show placeholder with infinite progress bar on the top while image is read from disk.
  63. if ( status == 'reading' ) {
  64. viewFigure.addClass( 'ck-appear', 'ck-infinite-progress', 'ck-image-upload-placeholder' );
  65. const viewImg = viewFigure.getChild( 0 );
  66. viewImg.setAttribute( 'src', placeholder );
  67. return;
  68. }
  69. // Show progress bar on the top of the image when image is uploading.
  70. if ( status == 'uploading' ) {
  71. const loader = fileRepository.loaders.get( uploadId );
  72. if ( loader ) {
  73. const progressBar = createProgressBar();
  74. viewFigure.removeClass( 'ck-infinite-progress', 'ck-image-upload-placeholder' );
  75. viewFigure.appendChildren( progressBar );
  76. // Update progress bar width when uploadedPercent is changed.
  77. loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
  78. progressBar.setStyle( 'width', value + '%' );
  79. editor.editing.view.render();
  80. } );
  81. }
  82. return;
  83. }
  84. // Hide progress bar and clean up classes.
  85. const progressBar = getProgressBar( viewFigure );
  86. if ( progressBar ) {
  87. progressBar.remove();
  88. } else {
  89. viewFigure.removeClass( 'ck-infinite-progress' );
  90. }
  91. viewFigure.removeClass( 'ck-appear', 'ck-image-upload-placeholder' );
  92. }
  93. }
  94. // Symbol added to progress bar UIElement to distinguish it from other elements.
  95. const progressBarSymbol = Symbol( 'progress-bar' );
  96. // Create progress bar element using {@link module:engine/view/uielement~UIElement}.
  97. //
  98. // @private
  99. // @returns {module:engine/view/uielement~UIElement}
  100. function createProgressBar() {
  101. const progressBar = new UIElement( 'div', { class: 'ck-progress-bar' } );
  102. progressBar.setCustomProperty( progressBarSymbol, true );
  103. return progressBar;
  104. }
  105. // Returns progress bar {@link module:engine/view/uielement~UIElement} from image figure element. Returns `undefined` if
  106. // progress bar element is not found.
  107. //
  108. // @private
  109. // @param {module:engine/view/element~Element} imageFigure
  110. // @returns {module:engine/view/uielement~UIElement|undefined}
  111. function getProgressBar( imageFigure ) {
  112. for ( const child of imageFigure.getChildren() ) {
  113. if ( child.getCustomProperty( progressBarSymbol ) ) {
  114. return child;
  115. }
  116. }
  117. }