-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
imageuploadprogress.js
276 lines (232 loc) · 9.72 KB
/
imageuploadprogress.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/**
* @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 );
}
}