imageuploadprogress.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module image/imageupload/imageuploadprogress
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
  10. import uploadingPlaceholder from '../../theme/icons/image_placeholder.svg';
  11. import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
  12. import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range';
  13. import '../../theme/imageuploadprogress.css';
  14. /**
  15. * Image upload progress plugin.
  16. * Shows placeholder when image is read from disk and progress bar while image is uploading.
  17. *
  18. * @extends module:core/plugin~Plugin
  19. */
  20. export default class ImageUploadProgress extends Plugin {
  21. /**
  22. * @inheritDoc
  23. */
  24. constructor( editor ) {
  25. super( editor );
  26. /**
  27. * Image's placeholder that is displayed before real image data can be accessed.
  28. *
  29. * @protected
  30. * @member {String} #placeholder
  31. */
  32. this.placeholder = 'data:image/svg+xml;utf8,' + encodeURIComponent( uploadingPlaceholder );
  33. }
  34. /**
  35. * @inheritDoc
  36. */
  37. init() {
  38. const editor = this.editor;
  39. // Upload status change - update image's view according to that status.
  40. editor.editing.downcastDispatcher.on( 'attribute:uploadStatus:image', ( ...args ) => this.uploadStatusChange( ...args ) );
  41. }
  42. /**
  43. * This ethod is called each time image's `uploadStatus` attribute is changed.
  44. *
  45. * @param {module:utils/eventinfo~EventInfo} evt Object containing information about the fired event.
  46. * @param {Object} data Additional information about the change.
  47. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  48. * @param {Object} conversionApi
  49. */
  50. uploadStatusChange( evt, data, conversionApi ) {
  51. const editor = this.editor;
  52. const modelImage = data.item;
  53. const uploadId = modelImage.getAttribute( 'uploadId' );
  54. if ( !conversionApi.consumable.consume( data.item, evt.name ) || !uploadId ) {
  55. return;
  56. }
  57. const fileRepository = editor.plugins.get( FileRepository );
  58. const status = data.attributeNewValue;
  59. const placeholder = this.placeholder;
  60. const viewFigure = editor.editing.mapper.toViewElement( modelImage );
  61. const viewWriter = conversionApi.writer;
  62. if ( status == 'reading' ) {
  63. // Start "appearing" effect and show placeholder with infinite progress bar on the top
  64. // while image is read from disk.
  65. _startAppearEffect( viewFigure, viewWriter );
  66. _showPlaceholder( placeholder, viewFigure, viewWriter );
  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. // Start appear effect if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
  73. _startAppearEffect( viewFigure, viewWriter );
  74. if ( !loader ) {
  75. // There is no loader associated with uploadId - this means that image came from external changes.
  76. // In such cases we still want to show the placeholder until image is fully uploaded.
  77. // Show placeholder if needed - see https://github.com/ckeditor/ckeditor5-image/issues/191.
  78. _showPlaceholder( placeholder, viewFigure, viewWriter );
  79. } else {
  80. // Hide placeholder and initialize progress bar showing upload progress.
  81. _hidePlaceholder( viewFigure, viewWriter );
  82. _showProgressBar( viewFigure, viewWriter, loader, editor.editing.view );
  83. }
  84. return;
  85. }
  86. // Clean up.
  87. _hideProgressBar( viewFigure, viewWriter );
  88. _hidePlaceholder( viewFigure, viewWriter );
  89. _stopAppearAffect( viewFigure, viewWriter );
  90. }
  91. }
  92. // Symbol added to progress bar UIElement to distinguish it from other elements.
  93. const progressBarSymbol = Symbol( 'progress-bar' );
  94. // Adds ck-appear class to the image figure if one is not already applied.
  95. //
  96. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  97. // @param {module:engine/view/writer~Writer} writer
  98. function _startAppearEffect( viewFigure, writer ) {
  99. if ( !viewFigure.hasClass( 'ck-appear' ) ) {
  100. writer.addClass( 'ck-appear', viewFigure );
  101. }
  102. }
  103. // Removes ck-appear class to the image figure if one is not already removed.
  104. //
  105. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  106. // @param {module:engine/view/writer~Writer} writer
  107. function _stopAppearAffect( viewFigure, writer ) {
  108. writer.removeClass( 'ck-appear', viewFigure );
  109. }
  110. // Shows placeholder together with infinite progress bar on given image figure.
  111. //
  112. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  113. // @param {module:engine/view/writer~Writer} writer
  114. function _showPlaceholder( placeholder, viewFigure, writer ) {
  115. if ( !viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
  116. writer.addClass( 'ck-image-upload-placeholder', viewFigure );
  117. }
  118. if ( !viewFigure.hasClass( 'ck-infinite-progress' ) ) {
  119. writer.addClass( 'ck-infinite-progress', viewFigure );
  120. }
  121. const viewImg = viewFigure.getChild( 0 );
  122. if ( viewImg.getAttribute( 'src' ) !== placeholder ) {
  123. writer.setAttribute( 'src', placeholder, viewImg );
  124. }
  125. }
  126. // Removes placeholder together with infinite progress bar on given image figure.
  127. //
  128. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  129. // @param {module:engine/view/writer~Writer} writer
  130. function _hidePlaceholder( viewFigure, writer ) {
  131. if ( viewFigure.hasClass( 'ck-image-upload-placeholder' ) ) {
  132. writer.removeClass( 'ck-image-upload-placeholder', viewFigure );
  133. }
  134. if ( viewFigure.hasClass( 'ck-infinite-progress' ) ) {
  135. writer.removeClass( 'ck-infinite-progress', viewFigure );
  136. }
  137. }
  138. // Shows progress bar displaying upload progress.
  139. // Attaches it to the file loader to update when upload percentace is changed.
  140. //
  141. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  142. // @param {module:engine/view/writer~Writer} writer
  143. // @param {module:upload/filerepository~FileLoader} loader
  144. // @param {module:engine/view/view~View} view
  145. function _showProgressBar( viewFigure, writer, loader, view ) {
  146. const progressBar = createProgressBar( writer );
  147. writer.insert( ViewPosition.createAt( viewFigure, 'end' ), progressBar );
  148. // Update progress bar width when uploadedPercent is changed.
  149. loader.on( 'change:uploadedPercent', ( evt, name, value ) => {
  150. view.change( writer => {
  151. writer.setStyle( 'width', value + '%', progressBar );
  152. } );
  153. } );
  154. }
  155. // Hides upload progress bar.
  156. //
  157. // @param {module:engine/view/containerelement~ContainerElement} viewFigure
  158. // @param {module:engine/view/writer~Writer} writer
  159. function _hideProgressBar( viewFigure, writer ) {
  160. const progressBar = getProgressBar( viewFigure );
  161. if ( progressBar ) {
  162. writer.remove( ViewRange.createOn( progressBar ) );
  163. }
  164. }
  165. // Create progress bar element using {@link module:engine/view/uielement~UIElement}.
  166. //
  167. // @private
  168. // @param {module:engine/view/writer~Writer} writer
  169. // @returns {module:engine/view/uielement~UIElement}
  170. function createProgressBar( writer ) {
  171. const progressBar = writer.createUIElement( 'div', { class: 'ck-progress-bar' } );
  172. writer.setCustomProperty( progressBarSymbol, true, progressBar );
  173. return progressBar;
  174. }
  175. // Returns progress bar {@link module:engine/view/uielement~UIElement} from image figure element. Returns `undefined` if
  176. // progress bar element is not found.
  177. //
  178. // @private
  179. // @param {module:engine/view/element~Element} imageFigure
  180. // @returns {module:engine/view/uielement~UIElement|undefined}
  181. function getProgressBar( imageFigure ) {
  182. for ( const child of imageFigure.getChildren() ) {
  183. if ( child.getCustomProperty( progressBarSymbol ) ) {
  184. return child;
  185. }
  186. }
  187. }