Merge "Do not show useless form at Special:ChangeContentModel"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.ForeignStructuredUpload.BookletLayout.js
1 /*global moment */
2 ( function ( $, mw ) {
3
4 /**
5 * mw.ForeignStructuredUpload.BookletLayout encapsulates the process
6 * of uploading a file to MediaWiki using the mw.ForeignStructuredUpload model.
7 *
8 * var uploadDialog = new mw.Upload.Dialog( {
9 * bookletClass: mw.ForeignStructuredUpload.BookletLayout,
10 * booklet: {
11 * target: 'local'
12 * }
13 * } );
14 * var windowManager = new OO.ui.WindowManager();
15 * $( 'body' ).append( windowManager.$element );
16 * windowManager.addWindows( [ uploadDialog ] );
17 *
18 * @class mw.ForeignStructuredUpload.BookletLayout
19 * @uses mw.ForeignStructuredUpload
20 * @extends mw.Upload.BookletLayout
21 * @cfg {string} [target] Used to choose the target repository.
22 * If nothing is passed, the {@link mw.ForeignUpload#property-target default} is used.
23 */
24 mw.ForeignStructuredUpload.BookletLayout = function ( config ) {
25 config = config || {};
26 // Parent constructor
27 mw.ForeignStructuredUpload.BookletLayout.parent.call( this, config );
28
29 this.target = config.target;
30 };
31
32 /* Setup */
33
34 OO.inheritClass( mw.ForeignStructuredUpload.BookletLayout, mw.Upload.BookletLayout );
35
36 /* Uploading */
37
38 /**
39 * @inheritdoc
40 */
41 mw.ForeignStructuredUpload.BookletLayout.prototype.initialize = function () {
42 var booklet = this;
43 return mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ).then(
44 function () {
45 // Point the CategorySelector to the right wiki
46 return booklet.upload.getApi().then(
47 function ( api ) {
48 // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
49 if ( api.apiUrl ) {
50 // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
51 booklet.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
52 }
53 return $.Deferred().resolve();
54 },
55 function () {
56 return $.Deferred().resolve();
57 }
58 );
59 },
60 function () {
61 return $.Deferred().resolve();
62 }
63 );
64 };
65
66 /**
67 * Returns a {@link mw.ForeignStructuredUpload mw.ForeignStructuredUpload}
68 * with the {@link #cfg-target target} specified in config.
69 *
70 * @protected
71 * @return {mw.Upload}
72 */
73 mw.ForeignStructuredUpload.BookletLayout.prototype.createUpload = function () {
74 return new mw.ForeignStructuredUpload( this.target );
75 };
76
77 /* Form renderers */
78
79 /**
80 * @inheritdoc
81 */
82 mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
83 var fieldset, $ownWorkMessage, $notOwnWorkMessage,
84 ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal,
85 validTargets = mw.config.get( 'wgForeignUploadTargets' ),
86 target = this.target || validTargets[ 0 ] || 'local',
87 layout = this;
88
89 // upload-form-label-own-work-message-local
90 // upload-form-label-own-work-message-shared
91 ownWorkMessage = mw.message( 'upload-form-label-own-work-message-' + target );
92 // upload-form-label-not-own-work-message-local
93 // upload-form-label-not-own-work-message-shared
94 notOwnWorkMessage = mw.message( 'upload-form-label-not-own-work-message-' + target );
95 // upload-form-label-not-own-work-local-local
96 // upload-form-label-not-own-work-local-shared
97 notOwnWorkLocal = mw.message( 'upload-form-label-not-own-work-local-' + target );
98
99 if ( !ownWorkMessage.exists() ) {
100 ownWorkMessage = mw.message( 'upload-form-label-own-work-message-default' );
101 }
102 if ( !notOwnWorkMessage.exists() ) {
103 notOwnWorkMessage = mw.message( 'upload-form-label-not-own-work-message-default' );
104 }
105 if ( !notOwnWorkLocal.exists() ) {
106 notOwnWorkLocal = mw.message( 'upload-form-label-not-own-work-local-default' );
107 }
108
109 $ownWorkMessage = $( '<p>' ).append( ownWorkMessage.parseDom() )
110 .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' );
111 $notOwnWorkMessage = $( '<div>' ).append(
112 $( '<p>' ).append( notOwnWorkMessage.parseDom() ),
113 $( '<p>' ).append( notOwnWorkLocal.parseDom() )
114 );
115 $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' ).attr( 'target', '_blank' );
116
117 this.selectFileWidget = new OO.ui.SelectFileWidget( {
118 showDropTarget: true
119 } );
120 this.messageLabel = new OO.ui.LabelWidget( {
121 label: $notOwnWorkMessage
122 } );
123 this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) {
124 layout.messageLabel.toggle( !on );
125 } );
126
127 fieldset = new OO.ui.FieldsetLayout();
128 fieldset.addItems( [
129 new OO.ui.FieldLayout( this.selectFileWidget, {
130 align: 'top'
131 } ),
132 new OO.ui.FieldLayout( this.ownWorkCheckbox, {
133 align: 'inline',
134 label: $( '<div>' ).append(
135 $( '<p>' ).text( mw.msg( 'upload-form-label-own-work' ) ),
136 $ownWorkMessage
137 )
138 } ),
139 new OO.ui.FieldLayout( this.messageLabel, {
140 align: 'top'
141 } )
142 ] );
143 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
144
145 // Validation
146 this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) );
147 this.ownWorkCheckbox.on( 'change', this.onUploadFormChange.bind( this ) );
148
149 this.selectFileWidget.on( 'change', function () {
150 var file = layout.getFile();
151
152 // Set the date to lastModified once we have the file
153 if ( layout.getDateFromLastModified( file ) !== undefined ) {
154 layout.dateWidget.setValue( layout.getDateFromLastModified( file ) );
155 }
156
157 // Check if we have EXIF data and set to that where available
158 layout.getDateFromExif( file ).done( function ( date ) {
159 layout.dateWidget.setValue( date );
160 } );
161
162 layout.updateFilePreview();
163 } );
164
165 return this.uploadForm;
166 };
167
168 /**
169 * @inheritdoc
170 */
171 mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {
172 var file = this.selectFileWidget.getValue(),
173 ownWork = this.ownWorkCheckbox.isSelected(),
174 valid = !!file && ownWork;
175 this.emit( 'uploadValid', valid );
176 };
177
178 /**
179 * @inheritdoc
180 */
181 mw.ForeignStructuredUpload.BookletLayout.prototype.renderInfoForm = function () {
182 var fieldset;
183
184 this.filePreview = new OO.ui.Widget( {
185 classes: [ 'mw-upload-bookletLayout-filePreview' ]
186 } );
187 this.progressBarWidget = new OO.ui.ProgressBarWidget( {
188 progress: 0
189 } );
190 this.filePreview.$element.append( this.progressBarWidget.$element );
191
192 this.filenameWidget = new OO.ui.TextInputWidget( {
193 required: true,
194 validate: /.+/
195 } );
196 this.descriptionWidget = new OO.ui.TextInputWidget( {
197 required: true,
198 validate: /\S+/,
199 multiline: true,
200 autosize: true
201 } );
202 this.categoriesWidget = new mw.widgets.CategorySelector( {
203 // Can't be done here because we don't know the target wiki yet... done in #initialize.
204 // api: new mw.ForeignApi( ... ),
205 $overlay: this.$overlay
206 } );
207 this.dateWidget = new mw.widgets.DateInputWidget( {
208 $overlay: this.$overlay,
209 required: true,
210 mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
211 } );
212
213 fieldset = new OO.ui.FieldsetLayout( {
214 label: mw.msg( 'upload-form-label-infoform-title' )
215 } );
216 fieldset.addItems( [
217 new OO.ui.FieldLayout( this.filenameWidget, {
218 label: mw.msg( 'upload-form-label-infoform-name' ),
219 align: 'top',
220 classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
221 notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ]
222 } ),
223 new OO.ui.FieldLayout( this.descriptionWidget, {
224 label: mw.msg( 'upload-form-label-infoform-description' ),
225 align: 'top',
226 classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
227 notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ]
228 } ),
229 new OO.ui.FieldLayout( this.categoriesWidget, {
230 label: mw.msg( 'upload-form-label-infoform-categories' ),
231 align: 'top'
232 } ),
233 new OO.ui.FieldLayout( this.dateWidget, {
234 label: mw.msg( 'upload-form-label-infoform-date' ),
235 align: 'top'
236 } )
237 ] );
238 this.infoForm = new OO.ui.FormLayout( {
239 classes: [ 'mw-upload-bookletLayout-infoForm' ],
240 items: [ this.filePreview, fieldset ]
241 } );
242
243 // Validation
244 this.filenameWidget.on( 'change', this.onInfoFormChange.bind( this ) );
245 this.descriptionWidget.on( 'change', this.onInfoFormChange.bind( this ) );
246 this.dateWidget.on( 'change', this.onInfoFormChange.bind( this ) );
247
248 this.on( 'fileUploadProgress', function ( progress ) {
249 this.progressBarWidget.setProgress( progress * 100 );
250 }.bind( this ) );
251
252 return this.infoForm;
253 };
254
255 /**
256 * @inheritdoc
257 */
258 mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () {
259 var layout = this;
260 $.when(
261 this.filenameWidget.getValidity(),
262 this.descriptionWidget.getValidity(),
263 this.dateWidget.getValidity()
264 ).done( function () {
265 layout.emit( 'infoValid', true );
266 } ).fail( function () {
267 layout.emit( 'infoValid', false );
268 } );
269 };
270
271 /* Getters */
272
273 /**
274 * @inheritdoc
275 */
276 mw.ForeignStructuredUpload.BookletLayout.prototype.getText = function () {
277 var language = mw.config.get( 'wgContentLanguage' );
278 this.upload.clearDescriptions();
279 this.upload.addDescription( language, this.descriptionWidget.getValue() );
280 this.upload.setDate( this.dateWidget.getValue() );
281 this.upload.clearCategories();
282 this.upload.addCategories( this.categoriesWidget.getItemsData() );
283 return this.upload.getText();
284 };
285
286 /**
287 * Get original date from EXIF data
288 *
289 * @param {Object} file
290 * @return {jQuery.Promise} Promise resolved with the EXIF date
291 */
292 mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromExif = function ( file ) {
293 var fileReader,
294 deferred = $.Deferred();
295
296 if ( file && file.type === 'image/jpeg' ) {
297 fileReader = new FileReader();
298 fileReader.onload = function () {
299 var fileStr, arr, i, metadata;
300
301 if ( typeof fileReader.result === 'string' ) {
302 fileStr = fileReader.result;
303 } else {
304 // Array buffer; convert to binary string for the library.
305 arr = new Uint8Array( fileReader.result );
306 fileStr = '';
307 for ( i = 0; i < arr.byteLength; i++ ) {
308 fileStr += String.fromCharCode( arr[ i ] );
309 }
310 }
311
312 try {
313 metadata = mw.libs.jpegmeta( this.result, file.name );
314 } catch ( e ) {
315 metadata = null;
316 }
317
318 if ( metadata !== null && metadata.exif !== undefined && metadata.exif.DateTimeOriginal ) {
319 deferred.resolve( moment( metadata.exif.DateTimeOriginal, 'YYYY:MM:DD' ).format( 'YYYY-MM-DD' ) );
320 } else {
321 deferred.reject();
322 }
323 };
324
325 if ( 'readAsBinaryString' in fileReader ) {
326 fileReader.readAsBinaryString( file );
327 } else if ( 'readAsArrayBuffer' in fileReader ) {
328 fileReader.readAsArrayBuffer( file );
329 } else {
330 // We should never get here
331 deferred.reject();
332 throw new Error( 'Cannot read thumbnail as binary string or array buffer.' );
333 }
334 }
335
336 return deferred.promise();
337 };
338
339 /**
340 * Get last modified date from file
341 *
342 * @param {Object} file
343 * @return {Object} Last modified date from file
344 */
345 mw.ForeignStructuredUpload.BookletLayout.prototype.getDateFromLastModified = function ( file ) {
346 if ( file && file.lastModified ) {
347 return moment( file.lastModified ).format( 'YYYY-MM-DD' );
348 }
349 };
350
351 /* Setters */
352
353 /**
354 * @inheritdoc
355 */
356 mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
357 mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
358
359 this.ownWorkCheckbox.setSelected( false );
360 this.categoriesWidget.setItemsFromData( [] );
361 this.dateWidget.setValue( '' ).setValidityFlag( true );
362 };
363
364 }( jQuery, mediaWiki ) );