MediaWiki:Gadget-compare-core.js

From Old School Near-Reality Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
  1 /** <nowiki>
  2  * Adds links for compare popups
  3  * 
  4  * @author Quarenon
  5  * @author Ryan PM
  6  * @author Joeytje50
  7  * @author Cqm
  8  * @author JaydenKieran
  9  * 
 10  * @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
 11  *
 12  * @todo try to find a standard img url domain to use
 13  * @todo re-center (vertical & horizontally) with new items added, or find a way to do it with pure CSS
 14  *	 might require overhaul to #overlay structure/styles
 15  */
 16 
 17 'use strict';
 18 
 19 var modalOpenedPrev = false;
 20 
 21 var conf = mw.config.get( [
 22 		'stylepath',
 23 		'wgTitle'
 24 	] ),
 25 
 26 	self = {
 27 		/**
 28 		 * Inital loading method
 29 		 */
 30 		init: function () {
 31 			self.buildModal();
 32 			
 33 			var $compare = $( '.cioCompareLink' ),
 34 				$ibox = $( '.infobox-bonuses' );
 35 
 36 			$compare.each( function () {
 37 				var $this = $( this ),
 38 					props = ( $this.attr( 'title' ) || '' ).split( '|' ),
 39 					text = props[0] !== '' ? props[0] : 'Compare items',
 40 					items = props.length >= 2 ? props.slice( 1 ) : [conf.wgTitle],
 41 					$a = $( '<a>' )
 42 						.attr( {
 43 							href: '#',
 44 							title: 'Compare this item with other items',
 45 							'data-items': items.join( '|' )
 46 						} )
 47 						.text( text )
 48 						.on( 'click', self.open );
 49 
 50 					$this
 51 						.empty()
 52 						.append( $a )
 53 						.parent()
 54 							.show();
 55 			} );
 56 
 57 			$ibox.each( function () {
 58 				var $this = $( this )
 59 				// insert new row with compare link
 60 				var button = new OO.ui.ButtonWidget( {
 61 					label: 'Compare',
 62 					title: 'Compare this item with other items',
 63 					flags: 'primary'
 64 				} );
 65 
 66 				$this.after( button.$element
 67 						.css({'margin-left':'1em'})
 68 						.attr( {
 69 							'data-items': conf.wgTitle
 70 						})
 71 						.on( 'click', self.open )
 72 					);
 73 			} );
 74 
 75 		},
 76 
 77 		/**
 78 		 * Images
 79 		 *
 80 		 * These are functions to avoid us having to use .clone()
 81 		 * and to avoid potential memory leaks
 82 		 */
 83 		img: {
 84 			/**
 85 			 * Delete image
 86 			 *
 87 			 * @return {jquery object}
 88 			 */
 89 			del: function () {
 90 				return $( '<img>' )
 91 					.attr( {
 92 						src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjadFJBTxNREP7m7ba1kRAg2SZyUKFcCHAhXI134w/Qg+FCAt7ECwgXygESQMKlRDQxXjRKQq1COBEPeBBixB8gHGxSmrbJtuUA3V12x7e76WshcZI5vPnmm2/mywMzw89qsczmryNuvK+nefjzCiYgo1Yosr2ehrWygsr+d8a1qOx9Y2v1JcoLiwqjWv6UrXQabi4HCDknoiP2ZBSd9++RIn14D7gu4HnQentgpFKkt3ffopLj+PqApgGXLuyNVzD9fep1OO/eAjfiISbJ7Hqhoo+f5Qtcf/0G3skfUDQa3EByetAYiYCIwLYNrb8fxuwMKWIjilPTzPk8KBZDox6QpLJIJpGYT5G6sZXoR2nyGXuVGshfTQZLVWEYSCwvUWufaH2YW5+Zi+XQJNUhwKcFmDu7VxSUormVYTuTAeSNcj9p0mXYoesIjHMcRB89RtfDB6QUzY+bbGezTZK8yTdC3O0BLCusyQH25ieYmWygJKrHJ+xsf23uICdrIyMwXkxTYn6O9MGhoBasL512vmRR/Ztj0dGXpMj4OCgeB19cQB8ehvF8UhkRHRuDNiDJ5+dA203EJp6i485tav7FHwdcXl37718tLS1z5ei3wv8JMADpOhbE0Ou4cwAAAABJRU5ErkJggg==',
 93 						width: 14,
 94 						height: 13,
 95 						alt: '[X]'
 96 					} );
 97 			},
 98 
 99 			/**
100 			 * Loading image
101 			 *
102 			 * @return {jquery object}
103 			 */
104 			loading: function () {
105 				return $( '<img>' )
106 					.attr( {
107 						// .gif can't be converted to data: URI
108 						src: 'https://oldschool.runescape.wiki/images/2/23/Progress-wheel.gif?0a2fe',
109 						width: 16,
110 						height: 16,
111 						alt: '...'
112 					} );
113 			},
114 
115 			/**
116 			 * Error image
117 			 *
118 			 * @return {jquery object}
119 			 */
120 			error: function () {
121 				return $( '<img>' )
122 					.attr( {
123 						src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcpJREFUeNpi3FvPwoAHMEPpv+gSTg2/wTQTPs2MjMx/QBjJIAyAz4AKMS0/BjHtQDCbVAPkmNm4WxSdyhkUHYoYQGyQGCkG5MvbFDCwcfECMTeDvHUuSKyIWANs2Hkli6SNoxh+fzzK8PvDQQYZowgGTkGFfKCcKzEGZCo5VTMw/v/BcHxGGsPxWXkM//+9Y1ByLAPLETIgmV/WNEpMzY7h59uNcMGfr9cxCCsaMfDLmIBCNA2fAZnKTpUMf77fYPj/5xNC9O9Xhr/frjGouJQxAKN1JnK0IhtQLa7lZ8wjLMLw6+02BoY/H+ES//9+AbpiPQOXAD+DpEEYSKgRJscITYnSzGxcT0yS1jIw/TzJ8OfLJZAUSCsQ/WH4/x+Y6v79YmDmUmFg5g9kODUvnOHPj49KwNR4H+aCfDnzVAZWtr/AUD8EtPEzGJ9evY7h9JpNQC98Awbkd4Y/n88zMDF/Y5CzSAHrgbkAGG3ih03iFzD8fD4RaOEXoCgjkgv+Q7LCf0h2YGTiYuCUq2Y4sziZ4fv7Rz4s4GizLwTq+cbAIZnMQAz4/+8jg7JTGcOVtTmZIAOmX98CTupRDKSBrSC9jP/BTiQfAAQYAMjEmRhwR6odAAAAAElFTkSuQmCC',
124 						width: 16,
125 						height: 16,
126 						alt: '!!'
127 					} );
128 			}
129 		},
130 
131 		/**
132 		 * Modal open method
133 		 *
134 		 * Callback to on click event
135 		 *
136 		 * @param e {jquery.event}
137 		 */
138 		open: function ( e ) {
139 			e.preventDefault();
140 			window.OOUIWindowManager.openWindow( 'compare' );
141 			
142 			if (!modalOpenedPrev) { // avoid init-ing
143 				modalOpenedPrev = true;
144 				var items = $( this ).attr( 'data-items' ).split( '|' );
145 				items.forEach( self.submit );
146 			}
147 		},
148 
149 		/**
150 		 * Builds the compare modal
151 		 *
152 		 * @return {jquery object}
153 		 */
154 		buildModal: function () {
155 			var init = function (modal) {
156 			  modal.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
157 
158 				var button1 = new OO.ui.ButtonWidget( {
159 				  flags: [ 'destructive' ],
160 				  label: 'Cancel'
161 				} );
162 				var b1click = ('click', function(modal) {
163 					window.OOUIWindowManager.closeWindow(modal);
164 				});
165 				button1.on('click', b1click, [modal]);
166 				
167 				var button2 = new OO.ui.ButtonWidget( {
168 					label: 'Submit',
169 					flags: [ 'progressive' ],
170 				});
171 				button2.on('click', function() {
172 					self.submit();  
173 				});
174 				
175 				var input1 = new OO.ui.TextInputWidget({ id: 'cioItem' });
176 				input1.on('enter', function(){
177 					self.submit();
178 				});
179 				
180 				// Create OOUI JS fieldset
181 				var fieldset = new OO.ui.FieldsetLayout( { 
182 				  label: 'Comparing ' + conf.wgTitle,
183 				  id: 'cioCompare'
184 				} );
185 				
186 				fieldset.addItems( [ 
187 				  new OO.ui.ActionFieldLayout(
188 					  input1,
189 					  button2,
190 					  { label: 'Compare with', align: 'inline', notices: [new OO.ui.HtmlSnippet('<div id="cioStatus"></div>')] }
191 				  )
192 				] );
193 
194 			  modal.content.$element.append($('<div>').append(fieldset.$element).append(
195 					$( '<table>' )
196 						.addClass( 'wikitable' )
197 						.attr( 'id', 'cioItems' )
198 						.append(
199 							$( '<thead>' )
200 								.append(
201 									$( '<tr>' )
202 										.append(
203 											$( '<th>' )
204 												.attr( 'rowspan', '2' )
205 												.text( 'Name' ),
206 											$( '<th>' )
207 												.attr( 'colspan', '5' )
208 												.text( 'Attack bonuses' ),
209 											$( '<th>' )
210 												.attr( 'colspan', '5' )
211 												.text( 'Defence bonuses' ),
212 											$( '<th>' )
213 												.attr( 'colspan', '4' )
214 												.text( 'Other bonuses' ),
215 											$( '<th>' )
216 												.attr( 'width', '30' )
217 												.text( 'Speed' ),
218 											$( '<th>' )
219 												.attr( 'width', '30' )
220 												.text( 'Weight' ),
221 											$( '<th>' )
222 												.attr( 'width', '30' )
223 												.text( 'GE' )
224 										),
225 									$( '<tr>' )
226 										.attr( 'height', '35' )
227 										.append(
228 											$( '<th>' )
229 												.attr( {
230 													class: 'cioIcon-stab',
231 													title: 'Stab bonus',
232 													width: '35'
233 												} ),
234 											$( '<th>' )
235 												.attr( {
236 													class: 'cioIcon-slash',
237 													title: 'Slash bonus',
238 													width: '35'
239 												} ),
240 											$( '<th>' )
241 												.attr( {
242 													class: 'cioIcon-crush',
243 													title: 'Crush bonus',
244 													width: '35'
245 												} ),
246 											$( '<th>' )
247 												.attr( {
248 													class: 'cioIcon-magic',
249 													title: 'Magic bonus',
250 													width: '35'
251 												} ),
252 											$( '<th>' )
253 												.attr( {
254 													class: 'cioIcon-ranged',
255 													title: 'Ranged bonus',
256 													width: '35'
257 												} ),
258 											$( '<th>' )
259 												.attr( {
260 													class: 'cioIcon-stab',
261 													title: 'Stab bonus',
262 													width: '35'
263 												} ),
264 											$( '<th>' )
265 												.attr( {
266 													class: 'cioIcon-slash',
267 													title: 'Slash bonus',
268 													width: '35'
269 												} ),
270 											$( '<th>' )
271 												.attr( {
272 													class: 'cioIcon-crush',
273 													title: 'Crush bonus',
274 													width: '35'
275 												} ),
276 											$( '<th>' )
277 												.attr( {
278 													class: 'cioIcon-magic',
279 													title: 'Magic bonus',
280 													width: '35'
281 												} ),
282 											$( '<th>' )
283 												.attr( {
284 													class: 'cioIcon-ranged',
285 													title: 'Ranged bonus',
286 													width: '35'
287 												} ),
288 											$( '<th>' )
289 												.attr( {
290 													class: 'cioIcon-strength',
291 													title: 'Strength bonus',
292 													width: '35'
293 												} ),
294 											$( '<th>' )
295 												.attr( {
296 													class: 'cioIcon-rangedstrength',
297 													title: 'Ranged Strength bonus',
298 													width: '35'
299 												} ),
300 											$( '<th>' )
301 												.attr( {
302 													class: 'cioIcon-magicdamage',
303 													title: 'Magic Damage bonus',
304 													width: '35'
305 												} ),
306 											$( '<th>' )
307 												.attr( {
308 													class: 'cioIcon-prayer',
309 													title: 'Prayer bonus',
310 													width: '35'
311 												} ),
312 											$( '<th>' )
313 												.attr( {
314 													class: 'cioIcon-speed',
315 													title: 'Speed',
316 													width: '35'
317 												} ),
318 											$( '<th>' )
319 												.attr( {
320 													class: 'cioIcon-weight',
321 													title: 'Weight (kg)',
322 													width: '35'
323 												} ),
324 											$( '<th>' )
325 												.attr( {
326 													class: 'cioIcon-price',
327 													title: 'Grand Exchange Price',
328 													width: '35'
329 												} )
330 										)
331 								),
332 							$( '<tbody>' )
333 								.append(
334 									$( '<tr>' )
335 										.attr( 'id', 'cioTotals' )
336 										// .addClass('table-bg-green')
337 								)
338 						),
339 					button1.$element
340 			  ));
341 			  modal.$body.append( modal.content.$element );
342 			};
343 			rs.createOOUIWindow('compare', 'Compare with other items', {size: 'larger', classes: ['rs-compare-modal', 'oo-ui-compare-width']}, init);
344 		},
345 
346 		/**
347 		 * Initial callback for adding new items to the UI
348 		 *
349 		 * @param elem {string} (optional)
350 		 */
351 		submit: function ( elem ) {
352 			var item = elem || $( '#cioItem > input' ).val();
353 
354 			$( '#cioStatus' )
355 				.empty()
356 				.attr( 'class', 'cioLoading' )
357 				.append(
358 					self.img.loading(),
359 					' Loading...'
360 				);
361 
362 			// make sure first letter of item is uppercase
363 			// otherwise price data won't be found
364 			item = item.charAt( 0 ).toUpperCase() + item.slice( 1 );
365 			
366 			var mwApiResult, excg, main, excgData;
367 
368 			( new mw.Api() )
369 				.get( {
370 					action: 'query',
371 					prop: 'revisions',
372 					titles: item + '|Module:Exchange/' + item,
373 					rvprop: 'content',
374 					redirects: ''
375 				} )
376 				.then( function (data) {
377 					mwApiResult = data;
378 					
379 					for ( var x in mwApiResult.query.pages ) {
380 						if ( mwApiResult.query.pages.hasOwnProperty( x ) ) {
381 							if ( x < 0 ) {
382 								// the page does not exist
383 								mw.log( mwApiResult.query.pages[x] );
384 								continue;
385 							} else if ( mwApiResult.query.pages[x].ns === 828 ) {
386 								excg = mwApiResult.query.pages[x];
387 							} else if ( mwApiResult.query.pages[x].ns === 0 ) {
388 								main = mwApiResult.query.pages[x];
389 							}
390 						}
391 					}
392 					
393 					if ( excg ) {
394 						excgData = rs.parseExchangeModule( excg.revisions[0]['*'] );
395 						excgData.itemId = excgData.itemId || excgData.itemid; // make this more robust?
396 
397 						$.getJSON("https://api.weirdgloop.org/exchange/history/osrs/latest?id=" + excgData.itemId)
398 							.done( function (res) {
399 								self.done(main, res[excgData.itemId]);
400 							} )
401 							.fail( self.fail );
402 					} else {
403 						self.done(main, {});
404 					}
405 				} )
406 				.fail( self.fail );
407 
408 			return false;
409 		},
410 
411 		/**
412 		 * Success callback for `jQuery.ajax` promise
413 		 */
414 		done: function ( main, apiRes ) {
415 			var bonuses = [
416 					'astab',
417 					'aslash',
418 					'acrush',
419 					'amagic',
420 					'arange',
421 					'dstab',
422 					'dslash',
423 					'dcrush',
424 					'dmagic',
425 					'drange',
426 					'str',
427 					'rstr',
428 					'mdmg',
429 					'prayer',
430 					'speed'
431 				],
432 				main,
433 				x,
434 				title,
435 				content,
436 				bonusData,
437 				itemData,
438 				$tr;
439 
440 			mw.log( main, apiRes );
441 
442 			if ( !main ) {
443 				self.showError( 'Could not find that item.' );
444 				return;
445 			}
446 
447 			title = main.title;
448 			content = main.revisions[0]['*'];
449 			bonusData = rs.parseTemplate( 'infobox bonuses', content );
450 			itemData = rs.parseTemplate( 'infobox item', content );
451 
452 			if ( $.isEmptyObject( bonusData ) ) {
453 				self.showError( 'No bonus data found for the item.' );
454 				return;
455 			}
456 
457 			$tr = $( '<tr>' )
458 				.append(
459 					$( '<th>' )
460 						.append(
461 							$( '<a>' )
462 								.attr( {
463 									href: '#',
464 									title: 'Remove this row'
465 								} )
466 								.on( 'click', function () {
467 									$( this ).closest( 'tr' ).fadeOut( 'slow', function () {
468 										$( this ).remove();
469 										self.calcTotals();
470 									} );
471 
472 									return false;
473 								} )
474 								.append( self.img.del() ),
475 							'&nbsp;',
476 							$( '<a>' )
477 								.attr( {
478 									href: mw.util.getUrl( title ),
479 									title: title
480 								} )
481 								.text( title )
482 						)
483 				);
484 
485 			bonuses.forEach( function ( el ) {
486 				// Use default version if defined, otherwise check if bonus has a version1
487 				var defaultVersion = $.isEmptyObject( itemData ) || (itemData.defver === undefined) ? '1' : itemData.defver;
488 				var versionSpecificBonus = bonusData[el + defaultVersion]; 
489 				$tr.append( self.format( versionSpecificBonus === undefined ? bonusData[el] : versionSpecificBonus ) );
490 			} );
491 
492 			$tr.append( self.format( !$.isEmptyObject( itemData ) ? itemData.weight : null ) );
493 			$tr.append( self.format( !$.isEmptyObject( apiRes ) ? rs.addCommas( apiRes.price ) : null ) );
494 
495 			$( '#cioTotals' ).before( $tr );
496 
497 			self.calcTotals();
498 			$( '#cioStatus' ).empty();
499 			$( '#cioItem > input' ).val( '' );
500 			
501 			window.OOUIWindowManager.getCurrentWindow().updateSize();
502 		},
503 
504 		/**
505 		 * Error callback for `jQuery.ajax` promise
506 		 */
507 		fail: function ( _, error ) {
508 			self.showError( 'Error: ' + error );
509 		},
510 
511 		/**
512 		 * Outputs error to the UI
513 		 *
514 		 * @param str {string} Error to display
515 		 */
516 		showError: function ( str ) {
517 			$( '#cioStatus' )
518 				.empty()
519 				.attr( 'class', 'cioError' )
520 				.append(
521 					self.img.error(),
522 					' ' + str
523 				);
524 		},
525 
526 		/**
527 		 * Formats each attribute's value and inserts it into a td cell
528 		 *
529 		 * @param str {string} Attribute value to format
530 		 *
531 		 * @return {jquery object} td cell to insert into the associated item's row
532 		 */
533 		format: function ( str ) {
534 			var $td = $( '<td>' ),
535 				first;
536 
537 			// set `null` or `undefined` to an empty string
538 			/*jshint eqnull:true */
539 			if ( str == null ) {
540 			/* jshint eqnull:false */
541 				str = '';
542 			}
543 
544 			// remove comments
545 			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();
546 
547 			// cache first character of `str`
548 			first = str.substring( 0, 1 );
549 
550 			if ( !str ) {
551 				$td
552 					.addClass( 'cioEmpty' )
553 					.text( '--' );
554 			} else if ( /\d/.test( first ) ) {
555 				$td
556 					.addClass( 'cioPos' )
557 					.text( '+' + str );
558 			} else if ( first === '-' ) {
559 				$td
560 					.addClass( 'cioNeg' )
561 					.text( str );
562 			} else {
563 				$td
564 					.text( str );
565 			}
566 
567 			return $td;
568 		},
569 		
570 		formatTotals: function (str) {
571 			var $td = $( '<td>' ),
572 				first;
573 
574 			// set `null` or `undefined` to an empty string
575 			/*jshint eqnull:true */
576 			if ( str == null ) {
577 			/* jshint eqnull:false */
578 				str = '';
579 			}
580 
581 			// remove comments
582 			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();
583 
584 			// cache first character of `str`
585 			first = str.substring( 0, 1 );
586 
587 			if ( !str ) {
588 				$td
589 					.addClass( 'cioEmpty' )
590 					.text( '--' );
591 			} else if ( /\d/.test( first ) ) {
592 				$td
593 					.addClass( 'table-bg-green' )
594 					.text( '+' + str );
595 			} else if ( first === '-' ) {
596 				$td
597 					.addClass( 'table-bg-red' )
598 					.text( str );
599 			} else {
600 				$td
601 					.addClass('table-bg-green')
602 					.text( str );
603 			}
604 
605 			return $td;
606 		},
607 
608 		/**
609 		 * Calculate bonus totals
610 		 */
611 		calcTotals: function () {
612 				// 19 0's, one for each attribute
613 			var totals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
614 				$totals = $( '#cioTotals' );
615 
616 			$( '#cioItems tbody tr:not( #cioTotals )' ).each( function () {
617 				$( this ).children( 'td' ).each( function ( i ) {
618 					var num = $( this ).text().replace( /,/g, '' );
619 					if (!isNaN(num)) {
620 					  var totalsSign = self.checkSign(totals[i])
621 					  var numSign = self.checkSign(num)
622 				      if (!totalsSign && !numSign) {
623 				        // If both values are negative
624 				        totals[i] = totals[i] === num ? 0 : totals[i] > num ? parseFloat(-1 * (num - totals[i]), 10) : parseFloat(totals[i] - num, 10)
625 				      } else if (!totalsSign || !numSign) {
626 				        // If one of the values are negative
627 				        totals[i] = totals[i] === 0 ? num : totalsSign === false ? parseFloat(-1 * (num - totals[i]), 10) : parseFloat(totals[i] - num, 10)
628 				      } else {
629 				        // Nothing is negative, calculate it out.
630 				        totals[i] = totals[i] === 0 ? num : totals[i] === num ? 0 : totals[i] > num ? parseFloat(totals[i] - num, 10) : parseFloat(-1 * (num - totals[i]), 10)
631 				      }
632 				    } else {
633 				      totals[i] += 0
634 				    }
635 				} );
636 			} );
637 
638 			$totals
639 				.empty()
640 				.append(
641 					$( '<th>' )
642 						.text( 'Total' )
643 				);
644 				
645 			totals.forEach( function ( elem, index ) {
646 				$totals.append(
647 					self.formatTotals(
648 						// don't total speed
649 						// 14th index/column respectively
650 						// [14].indexOf( index ) > -1 ? null : rs.addCommas( elem )
651 						rs.addCommas(elem)
652 					)
653 				);
654 			} );
655 		},
656 		checkSign: function (value) {
657 			return value === 0 ? true : (value > 0 ? true : false);
658 		}
659 	};
660 
661 $(function(){mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.rsw-util'], self.init )});