Bladeren bron

Merge pull request #219 from ckeditor/t/207

Feature: Implemented a CSS–styled image upload loader. Closes #207.
Aleksander Nowodzinski 7 jaren geleden
bovenliggende
commit
2f1197c116

+ 46 - 18
packages/ckeditor5-image/src/imageupload/imageuploadprogress.js

@@ -19,6 +19,7 @@ import env from '@ckeditor/ckeditor5-utils/src/env';
 
 import '../../theme/imageuploadprogress.css';
 import '../../theme/imageuploadicon.css';
+import '../../theme/imageuploadloader.css';
 
 /**
  * The image upload progress plugin.
@@ -120,6 +121,9 @@ export default class ImageUploadProgress extends Plugin {
 // Symbol added to progress bar UIElement to distinguish it from other elements.
 const progressBarSymbol = Symbol( 'progress-bar' );
 
+// Symbol added to placeholder UIElement to distinguish it from other elements.
+const placeholderSymbol = Symbol( 'placeholder' );
+
 // Adds ck-appear class to the image figure if one is not already applied.
 //
 // @param {module:engine/view/containerelement~ContainerElement} viewFigure
@@ -140,6 +144,7 @@ function _stopAppearEffect( viewFigure, writer ) {
 
 // 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/writer~Writer} writer
 function _showPlaceholder( placeholder, viewFigure, writer ) {
@@ -147,15 +152,15 @@ function _showPlaceholder( placeholder, viewFigure, writer ) {
 		writer.addClass( 'ck-image-upload-placeholder', viewFigure );
 	}
 
-	if ( !viewFigure.hasClass( 'ck-infinite-progress' ) ) {
-		writer.addClass( 'ck-infinite-progress', viewFigure );
-	}
-
 	const viewImg = viewFigure.getChild( 0 );
 
 	if ( viewImg.getAttribute( 'src' ) !== placeholder ) {
 		writer.setAttribute( 'src', placeholder, viewImg );
 	}
+
+	if ( !_getUIElement( viewFigure, placeholderSymbol ) ) {
+		writer.insert( ViewPosition.createAfter( viewImg ), _createPlaceholder( writer ) );
+	}
 }
 
 // Removes placeholder together with infinite progress bar on given image figure.
@@ -167,9 +172,7 @@ function _hidePlaceholder( viewFigure, writer ) {
 		writer.removeClass( 'ck-image-upload-placeholder', viewFigure );
 	}
 
-	if ( viewFigure.hasClass( 'ck-infinite-progress' ) ) {
-		writer.removeClass( 'ck-infinite-progress', viewFigure );
-	}
+	_removeUIElement( viewFigure, writer, placeholderSymbol );
 }
 
 // Shows progress bar displaying upload progress.
@@ -180,7 +183,7 @@ function _hidePlaceholder( viewFigure, writer ) {
 // @param {module:upload/filerepository~FileLoader} loader
 // @param {module:engine/view/view~View} view
 function _showProgressBar( viewFigure, writer, loader, view ) {
-	const progressBar = createProgressBar( writer );
+	const progressBar = _createProgressBar( writer );
 	writer.insert( ViewPosition.createAt( viewFigure, 'end' ), progressBar );
 
 	// Update progress bar width when uploadedPercent is changed.
@@ -196,11 +199,7 @@ function _showProgressBar( viewFigure, writer, loader, view ) {
 // @param {module:engine/view/containerelement~ContainerElement} viewFigure
 // @param {module:engine/view/writer~Writer} writer
 function _hideProgressBar( viewFigure, writer ) {
-	const progressBar = getProgressBar( viewFigure );
-
-	if ( progressBar ) {
-		writer.remove( ViewRange.createOn( progressBar ) );
-	}
+	_removeUIElement( viewFigure, writer, progressBarSymbol );
 }
 
 // Shows complete icon and hides after a certain amount of time.
@@ -223,23 +222,52 @@ function _showCompleteIcon( viewFigure, writer, view ) {
 // @private
 // @param {module:engine/view/writer~Writer} writer
 // @returns {module:engine/view/uielement~UIElement}
-function createProgressBar( writer ) {
+function _createProgressBar( writer ) {
 	const progressBar = writer.createUIElement( 'div', { class: 'ck-progress-bar' } );
+
 	writer.setCustomProperty( progressBarSymbol, true, progressBar );
 
 	return progressBar;
 }
 
-// Returns progress bar {@link module:engine/view/uielement~UIElement} from image figure element. Returns `undefined` if
-// progress bar element is not found.
+// Create placeholder element using {@link module:engine/view/uielement~UIElement}.
+//
+// @private
+// @param {module:engine/view/writer~Writer} writer
+// @returns {module:engine/view/uielement~UIElement}
+function _createPlaceholder( writer ) {
+	const placeholder = writer.createUIElement( 'div', { class: 'ck-upload-placeholder-loader' } );
+
+	writer.setCustomProperty( placeholderSymbol, 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 {Symbol} uniqueProperty
 // @returns {module:engine/view/uielement~UIElement|undefined}
-function getProgressBar( imageFigure ) {
+function _getUIElement( imageFigure, uniqueProperty ) {
 	for ( const child of imageFigure.getChildren() ) {
-		if ( child.getCustomProperty( progressBarSymbol ) ) {
+		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/writer~Writer} writer
+// @param {Symbol} uniqueProperty
+function _removeUIElement( viewFigure, writer, uniqueProperty ) {
+	const element = _getUIElement( viewFigure, uniqueProperty );
+
+	if ( element ) {
+		writer.remove( ViewRange.createOn( element ) );
+	}
+}

+ 17 - 13
packages/ckeditor5-image/tests/imageupload/imageuploadprogress.js

@@ -26,7 +26,7 @@ describe( 'ImageUploadProgress', () => {
 
 	// eslint-disable-next-line max-len
 	const base64Sample = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
-	let editor, model, document, fileRepository, view, nativeReaderMock, loader, adapterMock;
+	let editor, model, doc, fileRepository, view, nativeReaderMock, loader, adapterMock;
 
 	class UploadAdapterPluginMock extends Plugin {
 		init() {
@@ -59,7 +59,7 @@ describe( 'ImageUploadProgress', () => {
 			.then( newEditor => {
 				editor = newEditor;
 				model = editor.model;
-				document = model.document;
+				doc = model.document;
 				view = editor.editing.view;
 
 				fileRepository = editor.plugins.get( FileRepository );
@@ -77,8 +77,9 @@ describe( 'ImageUploadProgress', () => {
 		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
 
 		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-infinite-progress ck-widget image" contenteditable="false">' +
+			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
 				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
+				'<div class="ck-upload-placeholder-loader"></div>' +
 			'</figure>]<p>foo</p>'
 		);
 	} );
@@ -107,7 +108,7 @@ describe( 'ImageUploadProgress', () => {
 		const loader = fileRepository.createLoader( file );
 
 		setModelData( model, '<image></image>' );
-		const image = document.getRoot().getChild( 0 );
+		const image = doc.getRoot().getChild( 0 );
 
 		// Set attributes directly on image to simulate instant "uploading" status.
 		model.change( writer => {
@@ -126,7 +127,7 @@ describe( 'ImageUploadProgress', () => {
 
 	it( 'should work correctly when there is no "reading" status and go straight to "uploading" - external changes', () => {
 		setModelData( model, '<image></image>' );
-		const image = document.getRoot().getChild( 0 );
+		const image = doc.getRoot().getChild( 0 );
 
 		// Set attributes directly on image to simulate instant "uploading" status.
 		model.change( writer => {
@@ -135,15 +136,16 @@ describe( 'ImageUploadProgress', () => {
 		} );
 
 		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-infinite-progress ck-widget image" contenteditable="false">' +
+			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
 				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
+				'<div class="ck-upload-placeholder-loader"></div>' +
 			'</figure>]'
 		);
 	} );
 
 	it( 'should "clear" image when uploadId changes to null', () => {
 		setModelData( model, '<image></image>' );
-		const image = document.getRoot().getChild( 0 );
+		const image = doc.getRoot().getChild( 0 );
 
 		// Set attributes directly on image to simulate instant "uploading" status.
 		model.change( writer => {
@@ -158,7 +160,7 @@ describe( 'ImageUploadProgress', () => {
 
 		expect( getViewData( view ) ).to.equal(
 			'[<figure class="ck-widget image" contenteditable="false">' +
-			`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
+				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
 			'</figure>]'
 		);
 	} );
@@ -172,8 +174,8 @@ describe( 'ImageUploadProgress', () => {
 
 			expect( getViewData( view ) ).to.equal(
 				'[<figure class="ck-appear ck-widget image" contenteditable="false">' +
-				`<img src="${ base64Sample }"></img>` +
-				'<div class="ck-progress-bar" style="width:40%"></div>' +
+					`<img src="${ base64Sample }"></img>` +
+					'<div class="ck-progress-bar" style="width:40%"></div>' +
 				'</figure>]<p>foo</p>'
 			);
 
@@ -223,8 +225,9 @@ describe( 'ImageUploadProgress', () => {
 		editor.execute( 'imageUpload', { file: createNativeFileMock() } );
 
 		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-infinite-progress ck-widget image" contenteditable="false">' +
+			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
 				`<img src="${ base64Sample }"></img>` +
+				'<div class="ck-upload-placeholder-loader"></div>' +
 			'</figure>]<p>foo</p>'
 		);
 	} );
@@ -245,15 +248,16 @@ describe( 'ImageUploadProgress', () => {
 	it( 'should not show progress bar and complete icon if there is no loader with given uploadId', () => {
 		setModelData( model, '<image uploadId="123" uploadStatus="reading"></image>' );
 
-		const image = document.getRoot().getChild( 0 );
+		const image = doc.getRoot().getChild( 0 );
 
 		model.change( writer => {
 			writer.setAttribute( 'uploadStatus', 'uploading', image );
 		} );
 
 		expect( getViewData( view ) ).to.equal(
-			'[<figure class="ck-appear ck-image-upload-placeholder ck-infinite-progress ck-widget image" contenteditable="false">' +
+			'[<figure class="ck-appear ck-image-upload-placeholder ck-widget image" contenteditable="false">' +
 				`<img src="data:image/svg+xml;utf8,${ imagePlaceholder }"></img>` +
+				'<div class="ck-upload-placeholder-loader"></div>' +
 			'</figure>]'
 		);
 

+ 1 - 1
packages/ckeditor5-image/tests/manual/imageplaceholder.html

@@ -1,2 +1,2 @@
-<div id="container">
+<div id="editor">
 </div>

+ 15 - 7
packages/ckeditor5-image/tests/manual/imageplaceholder.js

@@ -3,19 +3,27 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global document */
+/* global document, console, window */
 
-import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 import ImageEditing from '../../src/image/imageediting';
 import ImageUploadEditing from '../../src/imageupload/imageuploadediting';
 import ImageUploadProgress from '../../src/imageupload/imageuploadprogress';
 
-VirtualTestEditor.create( { plugins: [ ImageEditing, ImageUploadEditing, ImageUploadProgress ] } )
+ClassicEditor.create( document.querySelector( '#editor' ), {
+	plugins: [ ImageEditing, ImageUploadEditing, ImageUploadProgress ]
+} )
 	.then( editor => {
-		const imageUploadProgress = editor.plugins.get( ImageUploadProgress );
-		const img = document.createElement( 'img' );
+		window.editor = editor;
 
-		img.src = imageUploadProgress.placeholder;
-		document.getElementById( 'container' ).appendChild( img );
+		editor.model.change( writer => {
+			writer.appendElement( 'image', {
+				uploadId: 'fake',
+				uploadStatus: 'uploading'
+			}, editor.model.document.getRoot() );
+		} );
+	} )
+	.catch( error => {
+		console.error( error );
 	} );
 

+ 1 - 1
packages/ckeditor5-image/tests/manual/imageplaceholder.md

@@ -1,3 +1,3 @@
 ## Image placeholder
 
-Check if image placeholder is visible.
+Check if the image placeholder is visible and looks OK after window resize.

+ 1 - 1
packages/ckeditor5-image/theme/icons/image_placeholder.svg

@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 250"><g fill="none" fill-rule="evenodd"><rect width="700" height="250" fill="#FAFAFA" rx="4"/><text fill="#5F6F77" font-family="Arial,sans-serif" font-size="24"><tspan x="247.9" y="135">Uploading image…</tspan></text></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 250"><g fill="#FAFAFA" fill-rule="evenodd"><rect width="700" height="250" rx="4"/></g></svg>

+ 18 - 0
packages/ckeditor5-image/theme/imageuploadloader.css

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+.ck .ck-upload-placeholder-loader {
+	position: absolute;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	top: 0;
+	left: 0;
+
+	&::before {
+		content: '';
+		position: relative;
+	}
+}

+ 0 - 8
packages/ckeditor5-image/theme/imageuploadprogress.css

@@ -12,14 +12,6 @@
 	position: relative;
 	overflow: hidden;
 
-	/* Infinite progress bar on top while the image is read. */
-	&.ck-infinite-progress::before {
-		content: "";
-		position: absolute;
-		top: 0;
-		right: 0;
-	}
-
 	/* Upload progress bar. */
 	& .ck-progress-bar {
 		position: absolute;