MediaWiki:Gadget-compare-core.js: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
Line 1: Line 1:
/**
/** * Adds links for compare popups * * @author Quarenon * @author Ryan PM * @author Joeytje50 * @author Cqm * @author JaydenKieran * * @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html> * * @todo try to find a standard img url domain to use * @todo re-center (vertical & horizontally) with new items added, or find a way to do it with pure CSS * might require overhaul to #overlay structure/styles */'use strict';var modalOpenedPrev = false;var conf = mw.config.get( [ 'stylepath', 'wgTitle' ] ), self = { /** * Inital loading method */ init: function () { self.buildModal(); var $compare = $( '.cioCompareLink' ), $ibox = $( '.infobox-bonuses' ); $compare.each( function () { var $this = $( this ), props = ( $this.attr( 'title' ) || '' ).split( '|' ), text = props[0] !== '' ? props[0] : 'Compare items', items = props.length >= 2 ? props.slice( 1 ) : [conf.wgTitle], $a = $( '<a>' ) .attr( { href: '#', title: 'Compare this item with other items', 'data-items': items.join( '|' ) } ) .text( text ) .on( 'click', self.open ); $this .empty() .append( $a ) .parent() .show(); } ); $ibox.each( function () { var $this = $( this ) // insert new row with compare link var button = new OO.ui.ButtonWidget( { label: 'Compare', title: 'Compare this item with other items', flags: 'primary' } ); $this.after( button.$element .attr( { 'data-items': conf.wgTitle }) .on( 'click', self.open ) ); } ); }, /** * Images * * These are functions to avoid us having to use .clone() * and to avoid potential memory leaks */ img: { /** * Delete image * * @return {jquery object} */ del: function () { return $( '<img>' ) .attr( { src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='m4.3 2.9 12.8 12.8-1.4 1.4L2.9 4.3z'/%3E%3Cpath d='M17.1 4.3 4.3 17.1l-1.4-1.4L15.7 2.9z'/%3E%3C/g%3E%3C/svg%3E%0A", width: 16, height: 16, alt: 'Delete' } ); }, /** * Loading image * * @return {jquery object} */ loading: function () { return $( '<img>' ) .attr( { // .gif can't be converted to data: URI src: 'https://oldschool.runescape.wiki/images/2/23/Progress-wheel.gif?0a2fe', width: 16, height: 16, alt: '...' } ); }, /** * Error image * * @return {jquery object} */ error: function () { return $( '<img>' ) .attr( { src: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z'/%3E%3C/g%3E%3C/svg%3E%0A", width: 16, height: 16, alt: 'Error' } ); } }, /** * Modal open method * * Callback to on click event * * @param e {jquery.event} */ open: function ( e ) { e.preventDefault(); window.OOUIWindowManager.openWindow( 'compare' ); if (!modalOpenedPrev) { // avoid init-ing modalOpenedPrev = true; var items = $( this ).attr( 'data-items' ).split( '|' ); items.forEach( self.submit ); } }, /** * Builds the compare modal * * @return {jquery object} */ buildModal: function () { var init = function (modal) { modal.content = new OO.ui.PanelLayout( { padded: true, expanded: false } ); var button1 = new OO.ui.ButtonWidget( { flags: [ 'destructive' ], label: 'Cancel' } ); var b1click = ('click', function(modal) { window.OOUIWindowManager.closeWindow(modal); }); button1.on('click', b1click, [modal]); var button2 = new OO.ui.ButtonWidget( { label: 'Submit', flags: [ 'progressive' ], }); button2.on('click', function() { self.submit(); }); var input1 = new OO.ui.TextInputWidget({ id: 'cioItem' }); input1.on('enter', function(){ self.submit(); }); // Create OOUI JS fieldset var fieldset = new OO.ui.FieldsetLayout( { label: 'Comparing ' + conf.wgTitle, id: 'cioCompare' } ); fieldset.addItems( [ new OO.ui.ActionFieldLayout( input1, button2, { label: 'Compare with', align: 'inline', notices: [new OO.ui.HtmlSnippet('<div id="cioStatus"></div>')] } ) ] ); modal.content.$element.append($('<div>').append(fieldset.$element).append( $( '<table>' ) .addClass( 'wikitable' ) .attr( 'id', 'cioItems' ) .append( $( '<thead>' ) .append( $( '<tr>' ) .append( $( '<th>' ) .attr( 'rowspan', '2' ) .text( 'Name' ), $( '<th>' ) .attr( 'colspan', '5' ) .text( 'Attack bonuses' ), $( '<th>' ) .attr( 'colspan', '5' ) .text( 'Defence bonuses' ), $( '<th>' ) .attr( 'colspan', '4' ) .text( 'Other bonuses' ), $( '<th>' ) .attr( 'width', '30' ) .text( 'Speed' ), $( '<th>' ) .attr( 'width', '30' ) .text( 'Weight' ), $( '<th>' ) .attr( 'width', '30' ) .text( 'GE' ) ), $( '<tr>' ) .attr( 'height', '35' ) .append( $( '<th>' ) .attr( { class: 'cioIcon-stab', title: 'Stab bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-slash', title: 'Slash bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-crush', title: 'Crush bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-magic', title: 'Magic bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-ranged', title: 'Ranged bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-stab', title: 'Stab bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-slash', title: 'Slash bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-crush', title: 'Crush bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-magic', title: 'Magic bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-ranged', title: 'Ranged bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-strength', title: 'Strength bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-rangedstrength', title: 'Ranged Strength bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-magicdamage', title: 'Magic Damage bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-prayer', title: 'Prayer bonus', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-speed', title: 'Speed', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-weight', title: 'Weight (kg)', width: '35' } ), $( '<th>' ) .attr( { class: 'cioIcon-price', title: 'Grand Exchange Price', width: '35' } ) ) ), $( '<tbody>' ) .append( $( '<tr>' ) .attr( 'id', 'cioTotals' ) // .addClass('table-bg-green') ) ), button1.$element )); modal.$body.append( modal.content.$element ); }; rs.createOOUIWindow('compare', 'Compare with other items', {size: 'larger', classes: ['rs-compare-modal', 'oo-ui-compare-width']}, init); }, /** * Initial callback for adding new items to the UI * * @param elem {string} (optional) */ submit: function ( elem ) { var item = elem || $( '#cioItem > input' ).val(); $( '#cioStatus' ) .empty() .attr( 'class', 'cioLoading' ) .append( self.img.loading(), ' Loading...' ); // make sure first letter of item is uppercase // otherwise price data won't be found item = item.charAt( 0 ).toUpperCase() + item.slice( 1 ); var mwApiResult, excg, main, excgData; ( new mw.Api() ) .get( { action: 'query', prop: 'revisions', titles: item + '|Module:Exchange/' + item, rvprop: 'content', redirects: '' } ) .then( function (data) { mwApiResult = data; for ( var x in mwApiResult.query.pages ) { if ( mwApiResult.query.pages.hasOwnProperty( x ) ) { if ( x < 0 ) { // the page does not exist mw.log( mwApiResult.query.pages[x] ); continue; } else if ( mwApiResult.query.pages[x].ns === 828 ) { excg = mwApiResult.query.pages[x]; } else if ( mwApiResult.query.pages[x].ns === 0 ) { main = mwApiResult.query.pages[x]; } } } if ( excg ) { excgData = rs.parseExchangeModule( excg.revisions[0]['*'] ); excgData.itemId = excgData.itemId || excgData.itemid; // make this more robust? $.getJSON("https://api.weirdgloop.org/exchange/history/osrs/latest?id=" + excgData.itemId) .done( function (res) { self.done(main, res[excgData.itemId]); } ) .fail( self.fail ); } else { self.done(main, {}); } } ) .fail( self.fail ); return false; }, /** * Success callback for `jQuery.ajax` promise */ done: function ( main, apiRes ) { var bonuses = [ 'astab', 'aslash', 'acrush', 'amagic', 'arange', 'dstab', 'dslash', 'dcrush', 'dmagic', 'drange', 'str', 'rstr', 'mdmg', 'prayer', 'speed' ], main, x, title, content, bonusData, itemData, $tr; mw.log( main, apiRes ); if ( !main ) { self.showError( 'Could not find that item.' ); return; } title = main.title; content = main.revisions[0]['*']; bonusData = rs.parseTemplate( 'infobox bonuses', content ); itemData = rs.parseTemplate( 'infobox item', content ); if ( $.isEmptyObject( bonusData ) ) { self.showError( 'No bonus data found for the item.' ); return; } $tr = $( '<tr>' ) .append( $( '<th>' ) .append( $( '<a>' ) .attr( { href: '#', title: 'Remove this row' } ) .on( 'click', function () { $( this ).closest( 'tr' ).fadeOut( 'slow', function () { $( this ).remove(); self.calcTotals(); window.OOUIWindowManager.getCurrentWindow().updateSize(); } ); return false; } ) .append( self.img.del() ), '&nbsp;', $( '<a>' ) .attr( { href: mw.util.getUrl( title ), title: title } ) .text( title ) ) ); bonuses.forEach( function ( el ) { // Use default version if defined, otherwise check if bonus has a version1 var defaultVersion = $.isEmptyObject( itemData ) || (itemData.defver === undefined) ? '1' : itemData.defver; var versionSpecificBonus = bonusData[el + defaultVersion]; $tr.append( self.format( versionSpecificBonus === undefined ? bonusData[el] : versionSpecificBonus ) ); } ); $tr.append( self.format( !$.isEmptyObject( itemData ) ? itemData.weight : null ) ); $tr.append( self.format( !$.isEmptyObject( apiRes ) ? rs.addCommas( apiRes.price ) : null ) ); $( '#cioTotals' ).before( $tr ); self.calcTotals(); $( '#cioStatus' ).empty(); $( '#cioItem > input' ).val( '' ); window.OOUIWindowManager.getCurrentWindow().updateSize(); }, /** * Error callback for `jQuery.ajax` promise */ fail: function ( _, error ) { self.showError( 'Error: ' + error ); }, /** * Outputs error to the UI * * @param str {string} Error to display */ showError: function ( str ) { $( '#cioStatus' ) .empty() .attr( 'class', 'cioError' ) .append( self.img.error(), ' ' + str ); }, /** * Formats each attribute's value and inserts it into a td cell * * @param str {string} Attribute value to format * * @return {jquery object} td cell to insert into the associated item's row */ format: function ( str ) { var $td = $( '<td>' ), first; // set `null` or `undefined` to an empty string /*jshint eqnull:true */ if ( str == null ) { /* jshint eqnull:false */ str = ''; } // remove comments str = str.replace( /no|<!--.*?-->/gi, '' ).trim(); // cache first character of `str` first = str.substring( 0, 1 ); if ( !str ) { $td .addClass( 'cioEmpty' ) .text( '--' ); } else if ( /\d/.test( first ) ) { $td .addClass( 'cioPos' ) .text( '+' + str ); } else if ( first === '-' ) { $td .addClass( 'cioNeg' ) .text( str ); } else { $td .text( str ); } return $td; }, formatTotals: function (str, index) { var $td = $( '<td>' ), first; // set `null` or `undefined` to an empty string /*jshint eqnull:true */ if ( str == null || str === "null" ) { /* jshint eqnull:false */ str = ''; } // remove comments str = str.replace( /no|<!--.*?-->/gi, '' ).trim(); // cache first character of `str` first = str.substring( 0, 1 ); // lower is better for speed and weight - reverse colors var lowerBetter = [14, 15].includes(index); if ( !str ) { $td .addClass( 'cioEmpty' ) .text( '--' ); } else if (parseFloat(str, 10) === 0) { $td .addClass( 'table-bg-yellow ') .text(str); } else if ( /\d/.test( first ) ) { $td .addClass( lowerBetter ? 'table-bg-red' : 'table-bg-green' ) .text( '+' + str ); } else if ( first === '-' ) { $td .addClass( lowerBetter ? 'table-bg-green' : 'table-bg-red' ) .text( str ); } else { $td.text( str ); } // context dependent whether higher or lower price is better - just don't color it if (index === 16) { $td.removeClass("table-bg-green table-bg-yellow table-bg-red"); } return $td; }, /** * Calculate bonus totals */ calcTotals: function () { // 19 0's, one for each attribute var totals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], $totals = $( '#cioTotals' ); // don't show totals row when not comparing 2 or more items if ($( '#cioItems tbody tr:not( #cioTotals )' ).length < 2) { $totals.empty(); return; } $( '#cioItems tbody tr:not( #cioTotals ):first td' ).each(function(i) { var num = parseFloat($(this).text().replace(/,/g, "")); totals[i] = isNaN(num) ? null : num; }); $( '#cioItems tbody tr:not( #cioTotals ):not(:first)' ).each( function () { $( this ).children( 'td' ).each( function ( i ) { if (totals[i] !== null) { var num = parseFloat($(this).text().replace(/,/g, "")); if (isNaN(num)) { totals[i] = null; } else { totals[i] -= num; } } } ); } ); $totals .empty() .append( $( '<th>' ) .text( 'Diff' ) ); totals.forEach( function ( elem, index ) { $totals.append( self.formatTotals( // don't total speed // 14th index/column respectively // [14].indexOf( index ) > -1 ? null : rs.addCommas( elem ) rs.addCommas(elem), index ) ); } ); }, checkSign: function (value) { return value === 0 ? true : (value > 0 ? true : false); } };$(function(){mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.rsw-util'], self.init )});
* Adds links for compare popups
*
* @author Quarenon
* @author Ryan PM
* @author Joeytje50
* @author Cqm
* @author JaydenKieran
*
* @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
*
* @todo try to find a standard img url domain to use
* @todo re-center (vertical & horizontally) with new items added, or find a way to do it with pure CSS
* might require overhaul to #overlay structure/styles
*/

'use strict';

var modalOpenedPrev = false;

var conf = mw.config.get( [
'stylepath',
'wgTitle'
] ),

self = {
/**
* Inital loading method
*/
init: function () {
self.buildModal();
var $compare = $( '.cioCompareLink' ),
$ibox = $( '.infobox-bonuses' );

$compare.each( function () {
var $this = $( this ),
props = ( $this.attr( 'title' ) || '' ).split( '|' ),
text = props[0] !== '' ? props[0] : 'Compare items',
items = props.length >= 2 ? props.slice( 1 ) : [conf.wgTitle],
$a = $( '<a>' )
.attr( {
href: '#',
title: 'Compare this item with other items',
'data-items': items.join( '|' )
} )
.text( text )
.on( 'click', self.open );

$this
.empty()
.append( $a )
.parent()
.show();
} );

$ibox.each( function () {
var $this = $( this )
// insert new row with compare link
var button = new OO.ui.ButtonWidget( {
label: 'Compare',
title: 'Compare this item with other items',
flags: 'primary'
} );

$this.after( button.$element
.attr( {
'data-items': conf.wgTitle
})
.on( 'click', self.open )
);
} );

},

/**
* Images
*
* These are functions to avoid us having to use .clone()
* and to avoid potential memory leaks
*/
img: {
/**
* Delete image
*
* @return {jquery object}
*/
del: function () {
return $( '<img>' )
.attr( {
src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='m4.3 2.9 12.8 12.8-1.4 1.4L2.9 4.3z'/%3E%3Cpath d='M17.1 4.3 4.3 17.1l-1.4-1.4L15.7 2.9z'/%3E%3C/g%3E%3C/svg%3E%0A",
width: 16,
height: 16,
alt: 'Delete'
} );
},

/**
* Loading image
*
* @return {jquery object}
*/
loading: function () {
return $( '<img>' )
.attr( {
// .gif can't be converted to data: URI
src: 'https://oldschool.runescape.wiki/images/2/23/Progress-wheel.gif?0a2fe',
width: 16,
height: 16,
alt: '...'
} );
},

/**
* Error image
*
* @return {jquery object}
*/
error: function () {
return $( '<img>' )
.attr( {
src: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z'/%3E%3C/g%3E%3C/svg%3E%0A",
width: 16,
height: 16,
alt: 'Error'
} );
}
},

/**
* Modal open method
*
* Callback to on click event
*
* @param e {jquery.event}
*/
open: function ( e ) {
e.preventDefault();
window.OOUIWindowManager.openWindow( 'compare' );
if (!modalOpenedPrev) { // avoid init-ing
modalOpenedPrev = true;
var items = $( this ).attr( 'data-items' ).split( '|' );
items.forEach( self.submit );
}
},

/**
* Builds the compare modal
*
* @return {jquery object}
*/
buildModal: function () {
var init = function (modal) {
modal.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );

var button1 = new OO.ui.ButtonWidget( {
flags: [ 'destructive' ],
label: 'Cancel'
} );
var b1click = ('click', function(modal) {
window.OOUIWindowManager.closeWindow(modal);
});
button1.on('click', b1click, [modal]);
var button2 = new OO.ui.ButtonWidget( {
label: 'Submit',
flags: [ 'progressive' ],
});
button2.on('click', function() {
self.submit();
});
var input1 = new OO.ui.TextInputWidget({ id: 'cioItem' });
input1.on('enter', function(){
self.submit();
});
// Create OOUI JS fieldset
var fieldset = new OO.ui.FieldsetLayout( {
label: 'Comparing ' + conf.wgTitle,
id: 'cioCompare'
} );
fieldset.addItems( [
new OO.ui.ActionFieldLayout(
input1,
button2,
{ label: 'Compare with', align: 'inline', notices: [new OO.ui.HtmlSnippet('<div id="cioStatus"></div>')] }
)
] );

modal.content.$element.append($('<div>').append(fieldset.$element).append(
$( '<table>' )
.addClass( 'wikitable' )
.attr( 'id', 'cioItems' )
.append(
$( '<thead>' )
.append(
$( '<tr>' )
.append(
$( '<th>' )
.attr( 'rowspan', '2' )
.text( 'Name' ),
$( '<th>' )
.attr( 'colspan', '5' )
.text( 'Attack bonuses' ),
$( '<th>' )
.attr( 'colspan', '5' )
.text( 'Defence bonuses' ),
$( '<th>' )
.attr( 'colspan', '4' )
.text( 'Other bonuses' ),
$( '<th>' )
.attr( 'width', '30' )
.text( 'Speed' ),
$( '<th>' )
.attr( 'width', '30' )
.text( 'Weight' ),
$( '<th>' )
.attr( 'width', '30' )
.text( 'GE' )
),
$( '<tr>' )
.attr( 'height', '35' )
.append(
$( '<th>' )
.attr( {
class: 'cioIcon-stab',
title: 'Stab bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-slash',
title: 'Slash bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-crush',
title: 'Crush bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-magic',
title: 'Magic bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-ranged',
title: 'Ranged bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-stab',
title: 'Stab bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-slash',
title: 'Slash bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-crush',
title: 'Crush bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-magic',
title: 'Magic bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-ranged',
title: 'Ranged bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-strength',
title: 'Strength bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-rangedstrength',
title: 'Ranged Strength bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-magicdamage',
title: 'Magic Damage bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-prayer',
title: 'Prayer bonus',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-speed',
title: 'Speed',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-weight',
title: 'Weight (kg)',
width: '35'
} ),
$( '<th>' )
.attr( {
class: 'cioIcon-price',
title: 'Grand Exchange Price',
width: '35'
} )
)
),
$( '<tbody>' )
.append(
$( '<tr>' )
.attr( 'id', 'cioTotals' )
// .addClass('table-bg-green')
)
),
button1.$element
));
modal.$body.append( modal.content.$element );
};
rs.createOOUIWindow('compare', 'Compare with other items', {size: 'larger', classes: ['rs-compare-modal', 'oo-ui-compare-width']}, init);
},

/**
* Initial callback for adding new items to the UI
*
* @param elem {string} (optional)
*/
submit: function ( elem ) {
var item = elem || $( '#cioItem > input' ).val();

$( '#cioStatus' )
.empty()
.attr( 'class', 'cioLoading' )
.append(
self.img.loading(),
' Loading...'
);

// make sure first letter of item is uppercase
// otherwise price data won't be found
item = item.charAt( 0 ).toUpperCase() + item.slice( 1 );
var mwApiResult, excg, main, excgData;

( new mw.Api() )
.get( {
action: 'query',
prop: 'revisions',
titles: item + '|Module:Exchange/' + item,
rvprop: 'content',
redirects: ''
} )
.then( function (data) {
mwApiResult = data;
for ( var x in mwApiResult.query.pages ) {
if ( mwApiResult.query.pages.hasOwnProperty( x ) ) {
if ( x < 0 ) {
// the page does not exist
mw.log( mwApiResult.query.pages[x] );
continue;
} else if ( mwApiResult.query.pages[x].ns === 828 ) {
excg = mwApiResult.query.pages[x];
} else if ( mwApiResult.query.pages[x].ns === 0 ) {
main = mwApiResult.query.pages[x];
}
}
}
if ( excg ) {
excgData = rs.parseExchangeModule( excg.revisions[0]['*'] );
excgData.itemId = excgData.itemId || excgData.itemid; // make this more robust?

$.getJSON("https://api.weirdgloop.org/exchange/history/osrs/latest?id=" + excgData.itemId)
.done( function (res) {
self.done(main, res[excgData.itemId]);
} )
.fail( self.fail );
} else {
self.done(main, {});
}
} )
.fail( self.fail );

return false;
},

/**
* Success callback for `jQuery.ajax` promise
*/
done: function ( main, apiRes ) {
var bonuses = [
'astab',
'aslash',
'acrush',
'amagic',
'arange',
'dstab',
'dslash',
'dcrush',
'dmagic',
'drange',
'str',
'rstr',
'mdmg',
'prayer',
'speed'
],
main,
x,
title,
content,
bonusData,
itemData,
$tr;

mw.log( main, apiRes );

if ( !main ) {
self.showError( 'Could not find that item.' );
return;
}

title = main.title;
content = main.revisions[0]['*'];
bonusData = rs.parseTemplate( 'infobox bonuses', content );
itemData = rs.parseTemplate( 'infobox item', content );

if ( $.isEmptyObject( bonusData ) ) {
self.showError( 'No bonus data found for the item.' );
return;
}

$tr = $( '<tr>' )
.append(
$( '<th>' )
.append(
$( '<a>' )
.attr( {
href: '#',
title: 'Remove this row'
} )
.on( 'click', function () {
$( this ).closest( 'tr' ).fadeOut( 'slow', function () {
$( this ).remove();
self.calcTotals();
window.OOUIWindowManager.getCurrentWindow().updateSize();
} );

return false;
} )
.append( self.img.del() ),
'&nbsp;',
$( '<a>' )
.attr( {
href: mw.util.getUrl( title ),
title: title
} )
.text( title )
)
);

bonuses.forEach( function ( el ) {
// Use default version if defined, otherwise check if bonus has a version1
var defaultVersion = $.isEmptyObject( itemData ) || (itemData.defver === undefined) ? '1' : itemData.defver;
var versionSpecificBonus = bonusData[el + defaultVersion];
$tr.append( self.format( versionSpecificBonus === undefined ? bonusData[el] : versionSpecificBonus ) );
} );

$tr.append( self.format( !$.isEmptyObject( itemData ) ? itemData.weight : null ) );
$tr.append( self.format( !$.isEmptyObject( apiRes ) ? rs.addCommas( apiRes.price ) : null ) );

$( '#cioTotals' ).before( $tr );

self.calcTotals();
$( '#cioStatus' ).empty();
$( '#cioItem > input' ).val( '' );
window.OOUIWindowManager.getCurrentWindow().updateSize();
},

/**
* Error callback for `jQuery.ajax` promise
*/
fail: function ( _, error ) {
self.showError( 'Error: ' + error );
},

/**
* Outputs error to the UI
*
* @param str {string} Error to display
*/
showError: function ( str ) {
$( '#cioStatus' )
.empty()
.attr( 'class', 'cioError' )
.append(
self.img.error(),
' ' + str
);
},

/**
* Formats each attribute's value and inserts it into a td cell
*
* @param str {string} Attribute value to format
*
* @return {jquery object} td cell to insert into the associated item's row
*/
format: function ( str ) {
var $td = $( '<td>' ),
first;

// set `null` or `undefined` to an empty string
/*jshint eqnull:true */
if ( str == null ) {
/* jshint eqnull:false */
str = '';
}

// remove comments
str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

// cache first character of `str`
first = str.substring( 0, 1 );

if ( !str ) {
$td
.addClass( 'cioEmpty' )
.text( '--' );
} else if ( /\d/.test( first ) ) {
$td
.addClass( 'cioPos' )
.text( '+' + str );
} else if ( first === '-' ) {
$td
.addClass( 'cioNeg' )
.text( str );
} else {
$td
.text( str );
}

return $td;
},
formatTotals: function (str, index) {
var $td = $( '<td>' ),
first;

// set `null` or `undefined` to an empty string
/*jshint eqnull:true */
if ( str == null || str === "null" ) {
/* jshint eqnull:false */
str = '';
}

// remove comments
str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

// cache first character of `str`
first = str.substring( 0, 1 );
// lower is better for speed and weight - reverse colors
var lowerBetter = [14, 15].includes(index);

if ( !str ) {
$td
.addClass( 'cioEmpty' )
.text( '--' );
} else if (parseFloat(str, 10) === 0) {
$td
.addClass( 'table-bg-yellow ')
.text(str);
} else if ( /\d/.test( first ) ) {
$td
.addClass( lowerBetter ? 'table-bg-red' : 'table-bg-green' )
.text( '+' + str );
} else if ( first === '-' ) {
$td
.addClass( lowerBetter ? 'table-bg-green' : 'table-bg-red' )
.text( str );
} else {
$td.text( str );
}
// context dependent whether higher or lower price is better - just don't color it
if (index === 16) {
$td.removeClass("table-bg-green table-bg-yellow table-bg-red");
}

return $td;
},

/**
* Calculate bonus totals
*/
calcTotals: function () {
// 19 0's, one for each attribute
var totals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
$totals = $( '#cioTotals' );
// don't show totals row when not comparing 2 or more items
if ($( '#cioItems tbody tr:not( #cioTotals )' ).length < 2) {
$totals.empty();
return;
}
$( '#cioItems tbody tr:not( #cioTotals ):first td' ).each(function(i) {
var num = parseFloat($(this).text().replace(/,/g, ""));
totals[i] = isNaN(num) ? null : num;
});

$( '#cioItems tbody tr:not( #cioTotals ):not(:first)' ).each( function () {
$( this ).children( 'td' ).each( function ( i ) {
if (totals[i] !== null) {
var num = parseFloat($(this).text().replace(/,/g, ""));
if (isNaN(num)) {
totals[i] = null;
}
else {
totals[i] -= num;
}
}
} );
} );

$totals
.empty()
.append(
$( '<th>' )
.text( 'Diff' )
);
totals.forEach( function ( elem, index ) {
$totals.append(
self.formatTotals(
// don't total speed
// 14th index/column respectively
// [14].indexOf( index ) > -1 ? null : rs.addCommas( elem )
rs.addCommas(elem), index
)
);
} );
},
checkSign: function (value) {
return value === 0 ? true : (value > 0 ? true : false);
}
};

$(function(){mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.rsw-util'], self.init )});

Revision as of 17:14, 17 October 2024

/**
 * Adds links for compare popups
 * 
 * @author Quarenon
 * @author Ryan PM
 * @author Joeytje50
 * @author Cqm
 * @author JaydenKieran
 * 
 * @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
 *
 * @todo try to find a standard img url domain to use
 * @todo re-center (vertical & horizontally) with new items added, or find a way to do it with pure CSS
 *	 might require overhaul to #overlay structure/styles
 */

'use strict';

var modalOpenedPrev = false;

var conf = mw.config.get( [
		'stylepath',
		'wgTitle'
	] ),

	self = {
		/**
		 * Inital loading method
		 */
		init: function () {
			self.buildModal();
			
			var $compare = $( '.cioCompareLink' ),
				$ibox = $( '.infobox-bonuses' );

			$compare.each( function () {
				var $this = $( this ),
					props = ( $this.attr( 'title' ) || '' ).split( '|' ),
					text = props[0] !== '' ? props[0] : 'Compare items',
					items = props.length >= 2 ? props.slice( 1 ) : [conf.wgTitle],
					$a = $( '<a>' )
						.attr( {
							href: '#',
							title: 'Compare this item with other items',
							'data-items': items.join( '|' )
						} )
						.text( text )
						.on( 'click', self.open );

					$this
						.empty()
						.append( $a )
						.parent()
							.show();
			} );

			$ibox.each( function () {
				var $this = $( this )
				// insert new row with compare link
				var button = new OO.ui.ButtonWidget( {
					label: 'Compare',
					title: 'Compare this item with other items',
					flags: 'primary'
				} );

				$this.after( button.$element
						.attr( {
							'data-items': conf.wgTitle
						})
						.on( 'click', self.open )
					);
			} );

		},

		/**
		 * Images
		 *
		 * These are functions to avoid us having to use .clone()
		 * and to avoid potential memory leaks
		 */
		img: {
			/**
			 * Delete image
			 *
			 * @return {jquery object}
			 */
			del: function () {
				return $( '<img>' )
					.attr( {
						src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='m4.3 2.9 12.8 12.8-1.4 1.4L2.9 4.3z'/%3E%3Cpath d='M17.1 4.3 4.3 17.1l-1.4-1.4L15.7 2.9z'/%3E%3C/g%3E%3C/svg%3E%0A",
						width: 16,
						height: 16,
						alt: 'Delete'
					} );
			},

			/**
			 * Loading image
			 *
			 * @return {jquery object}
			 */
			loading: function () {
				return $( '<img>' )
					.attr( {
						// .gif can't be converted to data: URI
						src: 'https://oldschool.runescape.wiki/images/2/23/Progress-wheel.gif?0a2fe',
						width: 16,
						height: 16,
						alt: '...'
					} );
			},

			/**
			 * Error image
			 *
			 * @return {jquery object}
			 */
			error: function () {
				return $( '<img>' )
					.attr( {
						src: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z'/%3E%3C/g%3E%3C/svg%3E%0A",
						width: 16,
						height: 16,
						alt: 'Error'
					} );
			}
		},

		/**
		 * Modal open method
		 *
		 * Callback to on click event
		 *
		 * @param e {jquery.event}
		 */
		open: function ( e ) {
			e.preventDefault();
			window.OOUIWindowManager.openWindow( 'compare' );
			
			if (!modalOpenedPrev) { // avoid init-ing
				modalOpenedPrev = true;
				var items = $( this ).attr( 'data-items' ).split( '|' );
				items.forEach( self.submit );
			}
		},

		/**
		 * Builds the compare modal
		 *
		 * @return {jquery object}
		 */
		buildModal: function () {
			var init = function (modal) {
			  modal.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );

				var button1 = new OO.ui.ButtonWidget( {
				  flags: [ 'destructive' ],
				  label: 'Cancel'
				} );
				var b1click = ('click', function(modal) {
					window.OOUIWindowManager.closeWindow(modal);
				});
				button1.on('click', b1click, [modal]);
				
				var button2 = new OO.ui.ButtonWidget( {
					label: 'Submit',
					flags: [ 'progressive' ],
				});
				button2.on('click', function() {
					self.submit();  
				});
				
				var input1 = new OO.ui.TextInputWidget({ id: 'cioItem' });
				input1.on('enter', function(){
					self.submit();
				});
				
				// Create OOUI JS fieldset
				var fieldset = new OO.ui.FieldsetLayout( { 
				  label: 'Comparing ' + conf.wgTitle,
				  id: 'cioCompare'
				} );
				
				fieldset.addItems( [ 
				  new OO.ui.ActionFieldLayout(
					  input1,
					  button2,
					  { label: 'Compare with', align: 'inline', notices: [new OO.ui.HtmlSnippet('<div id="cioStatus"></div>')] }
				  )
				] );

			  modal.content.$element.append($('<div>').append(fieldset.$element).append(
					$( '<table>' )
						.addClass( 'wikitable' )
						.attr( 'id', 'cioItems' )
						.append(
							$( '<thead>' )
								.append(
									$( '<tr>' )
										.append(
											$( '<th>' )
												.attr( 'rowspan', '2' )
												.text( 'Name' ),
											$( '<th>' )
												.attr( 'colspan', '5' )
												.text( 'Attack bonuses' ),
											$( '<th>' )
												.attr( 'colspan', '5' )
												.text( 'Defence bonuses' ),
											$( '<th>' )
												.attr( 'colspan', '4' )
												.text( 'Other bonuses' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'Speed' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'Weight' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'GE' )
										),
									$( '<tr>' )
										.attr( 'height', '35' )
										.append(
											$( '<th>' )
												.attr( {
													class: 'cioIcon-stab',
													title: 'Stab bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-slash',
													title: 'Slash bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-crush',
													title: 'Crush bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magic',
													title: 'Magic bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-ranged',
													title: 'Ranged bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-stab',
													title: 'Stab bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-slash',
													title: 'Slash bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-crush',
													title: 'Crush bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magic',
													title: 'Magic bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-ranged',
													title: 'Ranged bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-strength',
													title: 'Strength bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-rangedstrength',
													title: 'Ranged Strength bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magicdamage',
													title: 'Magic Damage bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-prayer',
													title: 'Prayer bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-speed',
													title: 'Speed',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-weight',
													title: 'Weight (kg)',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-price',
													title: 'Grand Exchange Price',
													width: '35'
												} )
										)
								),
							$( '<tbody>' )
								.append(
									$( '<tr>' )
										.attr( 'id', 'cioTotals' )
										// .addClass('table-bg-green')
								)
						),
					button1.$element
			  ));
			  modal.$body.append( modal.content.$element );
			};
			rs.createOOUIWindow('compare', 'Compare with other items', {size: 'larger', classes: ['rs-compare-modal', 'oo-ui-compare-width']}, init);
		},

		/**
		 * Initial callback for adding new items to the UI
		 *
		 * @param elem {string} (optional)
		 */
		submit: function ( elem ) {
			var item = elem || $( '#cioItem > input' ).val();

			$( '#cioStatus' )
				.empty()
				.attr( 'class', 'cioLoading' )
				.append(
					self.img.loading(),
					' Loading...'
				);

			// make sure first letter of item is uppercase
			// otherwise price data won't be found
			item = item.charAt( 0 ).toUpperCase() + item.slice( 1 );
			
			var mwApiResult, excg, main, excgData;

			( new mw.Api() )
				.get( {
					action: 'query',
					prop: 'revisions',
					titles: item + '|Module:Exchange/' + item,
					rvprop: 'content',
					redirects: ''
				} )
				.then( function (data) {
					mwApiResult = data;
					
					for ( var x in mwApiResult.query.pages ) {
						if ( mwApiResult.query.pages.hasOwnProperty( x ) ) {
							if ( x < 0 ) {
								// the page does not exist
								mw.log( mwApiResult.query.pages[x] );
								continue;
							} else if ( mwApiResult.query.pages[x].ns === 828 ) {
								excg = mwApiResult.query.pages[x];
							} else if ( mwApiResult.query.pages[x].ns === 0 ) {
								main = mwApiResult.query.pages[x];
							}
						}
					}
					
					if ( excg ) {
						excgData = rs.parseExchangeModule( excg.revisions[0]['*'] );
						excgData.itemId = excgData.itemId || excgData.itemid; // make this more robust?

						$.getJSON("https://api.weirdgloop.org/exchange/history/osrs/latest?id=" + excgData.itemId)
							.done( function (res) {
								self.done(main, res[excgData.itemId]);
							} )
							.fail( self.fail );
					} else {
						self.done(main, {});
					}
				} )
				.fail( self.fail );

			return false;
		},

		/**
		 * Success callback for `jQuery.ajax` promise
		 */
		done: function ( main, apiRes ) {
			var bonuses = [
					'astab',
					'aslash',
					'acrush',
					'amagic',
					'arange',
					'dstab',
					'dslash',
					'dcrush',
					'dmagic',
					'drange',
					'str',
					'rstr',
					'mdmg',
					'prayer',
					'speed'
				],
				main,
				x,
				title,
				content,
				bonusData,
				itemData,
				$tr;

			mw.log( main, apiRes );

			if ( !main ) {
				self.showError( 'Could not find that item.' );
				return;
			}

			title = main.title;
			content = main.revisions[0]['*'];
			bonusData = rs.parseTemplate( 'infobox bonuses', content );
			itemData = rs.parseTemplate( 'infobox item', content );

			if ( $.isEmptyObject( bonusData ) ) {
				self.showError( 'No bonus data found for the item.' );
				return;
			}

			$tr = $( '<tr>' )
				.append(
					$( '<th>' )
						.append(
							$( '<a>' )
								.attr( {
									href: '#',
									title: 'Remove this row'
								} )
								.on( 'click', function () {
									$( this ).closest( 'tr' ).fadeOut( 'slow', function () {
										$( this ).remove();
										self.calcTotals();
										window.OOUIWindowManager.getCurrentWindow().updateSize();
									} );

									return false;
								} )
								.append( self.img.del() ),
							'&nbsp;',
							$( '<a>' )
								.attr( {
									href: mw.util.getUrl( title ),
									title: title
								} )
								.text( title )
						)
				);

			bonuses.forEach( function ( el ) {
				// Use default version if defined, otherwise check if bonus has a version1
				var defaultVersion = $.isEmptyObject( itemData ) || (itemData.defver === undefined) ? '1' : itemData.defver;
				var versionSpecificBonus = bonusData[el + defaultVersion]; 
				$tr.append( self.format( versionSpecificBonus === undefined ? bonusData[el] : versionSpecificBonus ) );
			} );

			$tr.append( self.format( !$.isEmptyObject( itemData ) ? itemData.weight : null ) );
			$tr.append( self.format( !$.isEmptyObject( apiRes ) ? rs.addCommas( apiRes.price ) : null ) );

			$( '#cioTotals' ).before( $tr );

			self.calcTotals();
			$( '#cioStatus' ).empty();
			$( '#cioItem > input' ).val( '' );
			
			window.OOUIWindowManager.getCurrentWindow().updateSize();
		},

		/**
		 * Error callback for `jQuery.ajax` promise
		 */
		fail: function ( _, error ) {
			self.showError( 'Error: ' + error );
		},

		/**
		 * Outputs error to the UI
		 *
		 * @param str {string} Error to display
		 */
		showError: function ( str ) {
			$( '#cioStatus' )
				.empty()
				.attr( 'class', 'cioError' )
				.append(
					self.img.error(),
					' ' + str
				);
		},

		/**
		 * Formats each attribute's value and inserts it into a td cell
		 *
		 * @param str {string} Attribute value to format
		 *
		 * @return {jquery object} td cell to insert into the associated item's row
		 */
		format: function ( str ) {
			var $td = $( '<td>' ),
				first;

			// set `null` or `undefined` to an empty string
			/*jshint eqnull:true */
			if ( str == null ) {
			/* jshint eqnull:false */
				str = '';
			}

			// remove comments
			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

			// cache first character of `str`
			first = str.substring( 0, 1 );

			if ( !str ) {
				$td
					.addClass( 'cioEmpty' )
					.text( '--' );
			} else if ( /\d/.test( first ) ) {
				$td
					.addClass( 'cioPos' )
					.text( '+' + str );
			} else if ( first === '-' ) {
				$td
					.addClass( 'cioNeg' )
					.text( str );
			} else {
				$td
					.text( str );
			}

			return $td;
		},
		
		formatTotals: function (str, index) {
			var $td = $( '<td>' ),
				first;

			// set `null` or `undefined` to an empty string
			/*jshint eqnull:true */
			if ( str == null || str === "null" ) {
			/* jshint eqnull:false */
				str = '';
			}

			// remove comments
			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

			// cache first character of `str`
			first = str.substring( 0, 1 );
			
			// lower is better for speed and weight - reverse colors
			var lowerBetter = [14, 15].includes(index);

			if ( !str ) {
				$td
					.addClass( 'cioEmpty' )
					.text( '--' );
			} else if (parseFloat(str, 10) === 0) {
				$td
					.addClass( 'table-bg-yellow ')
					.text(str);
			} else if ( /\d/.test( first ) ) {
				$td
					.addClass( lowerBetter ? 'table-bg-red' : 'table-bg-green' )
					.text( '+' + str );
			} else if ( first === '-' ) {
				$td
					.addClass( lowerBetter ? 'table-bg-green' : 'table-bg-red' )
					.text( str );
			} else {
				$td.text( str );
			}
			
			// context dependent whether higher or lower price is better - just don't color it
			if (index === 16) {
				$td.removeClass("table-bg-green table-bg-yellow table-bg-red");
			}

			return $td;
		},

		/**
		 * Calculate bonus totals
		 */
		calcTotals: function () {
				// 19 0's, one for each attribute
			var totals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
				$totals = $( '#cioTotals' );
				
			// don't show totals row when not comparing 2 or more items
			if ($( '#cioItems tbody tr:not( #cioTotals )' ).length < 2) {
				$totals.empty();
				return;
			}
			
			$( '#cioItems tbody tr:not( #cioTotals ):first td' ).each(function(i) {
				var num = parseFloat($(this).text().replace(/,/g, ""));
				totals[i] = isNaN(num) ? null : num;
			});

			$( '#cioItems tbody tr:not( #cioTotals ):not(:first)' ).each( function () {
				$( this ).children( 'td' ).each( function ( i ) {
					if (totals[i] !== null) {
						var num = parseFloat($(this).text().replace(/,/g, ""));
						if (isNaN(num)) {
							totals[i] = null;
						}
						else {
							totals[i] -= num;
						}
					}
				} );
			} );

			$totals
				.empty()
				.append(
					$( '<th>' )
						.text( 'Diff' )
				);
				
			totals.forEach( function ( elem, index ) {
				$totals.append(
					self.formatTotals(
						// don't total speed
						// 14th index/column respectively
						// [14].indexOf( index ) > -1 ? null : rs.addCommas( elem )
						rs.addCommas(elem), index
					)
				);
			} );
		},
		checkSign: function (value) {
			return value === 0 ? true : (value > 0 ? true : false);
		}
	};

$(function(){mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.rsw-util'], self.init )});