@@ -210,17 +210,23 @@ var MarketplaceScreen = (function() {
210210 if ( sellableItems . length === 0 ) {
211211 html += '<div class="empty-state">' + t ( 'market_nothing_to_sell' ) + '</div>' ;
212212 } else {
213+ var sellGroups = _groupSellableItems ( sellableItems ) ;
213214 html += '<div class="market-sell-list" role="list">' ;
214- for ( var j = 0 ; j < sellableItems . length ; j ++ ) {
215- var sItem = sellableItems [ j ] ;
215+ for ( var j = 0 ; j < sellGroups . length ; j ++ ) {
216+ var group = sellGroups [ j ] ;
217+ var sItem = group . items [ 0 ] ;
216218 var sName = t ( 'item_' + sItem . type ) || sItem . type . replace ( / _ / g, ' ' ) ;
217219 var sRarity = ItemSystem . getRarityInfo ( sItem . rarity ) ;
220+ var itemIds = group . items . map ( function ( it ) { return it . id ; } ) . join ( ',' ) ;
218221 html += '<div class="market-sell-item ' + Helpers . rarityClass ( sItem . rarity ) + '" role="listitem" data-item="' + sItem . id + '">' ;
219- html += '<span class="sell-item-name rarity-color-' + sRarity . name + '">' + sRarity . symbol + ' ' + Helpers . escapeHtml ( sName ) + '</span>' ;
222+ html += '<span class="sell-item-name rarity-color-' + sRarity . name + '">' + sRarity . symbol + ' ' + Helpers . escapeHtml ( sName ) +
223+ ( group . items . length > 1 ? ' <span class="sell-item-count">×' + group . items . length + '</span>' : '' ) + '</span>' ;
220224 html += '<div class="sell-item-controls">' ;
221225 html += '<label for="price-' + sItem . id + '" class="sr-only">' + t ( 'market_set_price' ) + '</label>' ;
222226 html += '<input type="number" id="price-' + sItem . id + '" class="input-field sell-price-input" min="1" placeholder="' + t ( 'market_price_placeholder' ) + '" aria-label="' + t ( 'market_set_price' ) + '">' ;
223- html += '<button class="btn btn-primary btn-sm market-list-btn" data-item="' + sItem . id + '">' + t ( 'market_list_item' ) + '</button>' ;
227+ html += '<label for="qty-' + sItem . id + '" class="sr-only">' + t ( 'market_set_quantity' ) + '</label>' ;
228+ html += '<input type="number" id="qty-' + sItem . id + '" class="input-field sell-qty-input" min="1" max="' + group . items . length + '" value="1" aria-label="' + t ( 'market_set_quantity' ) + '">' ;
229+ html += '<button class="btn btn-primary btn-sm market-list-btn" data-item="' + sItem . id + '" data-items="' + itemIds + '">' + t ( 'market_list_item' ) + '</button>' ;
224230 html += '</div>' ;
225231 html += '</div>' ;
226232 }
@@ -325,8 +331,8 @@ var MarketplaceScreen = (function() {
325331 var listBtns = container . querySelectorAll ( '.market-list-btn' ) ;
326332 for ( var l = 0 ; l < listBtns . length ; l ++ ) {
327333 listBtns [ l ] . addEventListener ( 'click' , function ( ) {
328- var itemId = this . getAttribute ( 'data-item' ) ;
329- _handleList ( itemId , container ) ;
334+ var itemIds = ( this . getAttribute ( 'data-items' ) || this . getAttribute ( 'data- item' ) || '' ) . split ( ', ') ;
335+ _handleList ( itemIds , container ) ;
330336 } ) ;
331337 }
332338
@@ -414,11 +420,17 @@ var MarketplaceScreen = (function() {
414420 }
415421
416422 /**
417- * Handle list item for sale
423+ * Handle list item(s) for sale. Identical inventory rows are grouped in the UI,
424+ * but the chain still receives one listing action per selected item.
418425 */
419- function _handleList ( itemId , container ) {
426+ function _handleList ( itemIds , container ) {
420427 var t = Helpers . t ;
421- var priceInput = container . querySelector ( '#price-' + itemId ) ;
428+ itemIds = itemIds || [ ] ;
429+ if ( typeof itemIds === 'string' ) itemIds = [ itemIds ] ;
430+ if ( ! itemIds . length || ! itemIds [ 0 ] ) return ;
431+
432+ var firstId = itemIds [ 0 ] ;
433+ var priceInput = container . querySelector ( '#price-' + firstId ) ;
422434 if ( ! priceInput ) return ;
423435
424436 var price = parseInt ( priceInput . value , 10 ) ;
@@ -428,24 +440,42 @@ var MarketplaceScreen = (function() {
428440 return ;
429441 }
430442
443+ var qtyInput = container . querySelector ( '#qty-' + firstId ) ;
444+ var quantity = qtyInput ? parseInt ( qtyInput . value , 10 ) : 1 ;
445+ if ( ! quantity || quantity <= 0 ) quantity = 1 ;
446+ if ( quantity > itemIds . length ) quantity = itemIds . length ;
447+ var selectedIds = itemIds . slice ( 0 , quantity ) ;
431448 var user = typeof VizAccount !== 'undefined' ? VizAccount . getCurrentUser ( ) : '' ;
432- SoundManager . play ( 'tap' ) ;
433- MarketProtocol . broadcastList ( itemId , price , 0 , function ( err , result ) {
434- if ( err ) {
435- Toast . error ( t ( 'market_list_error' ) ) ;
436- SoundManager . play ( 'error' ) ;
437- } else {
449+ var listed = 0 ;
450+
451+ function listNext ( index ) {
452+ if ( index >= selectedIds . length ) {
453+ Toast . success ( t ( 'market_listed_count' , { count : listed } ) ) ;
454+ SoundManager . play ( 'success' ) ;
455+ render ( ) ;
456+ return ;
457+ }
458+ var itemId = selectedIds [ index ] ;
459+ MarketProtocol . broadcastList ( itemId , price , 0 , function ( err , result ) {
460+ if ( err ) {
461+ Toast . error ( t ( listed > 0 ? 'market_list_partial_error' : 'market_list_error' , { count : listed } ) ) ;
462+ SoundManager . play ( 'error' ) ;
463+ render ( ) ;
464+ return ;
465+ }
438466 var blockNum = _resultBlockNum ( result ) ;
439467 if ( user && StateEngine . processMarketListResult ( user , itemId , price , 0 , blockNum ) ) {
440468 StateEngine . saveCheckpoint ( function ( ) { } ) ;
441469 } else {
442470 _addPendingListing ( itemId , price , result ) ;
443471 }
444- Toast . success ( t ( 'market_listed' ) ) ;
445- SoundManager . play ( 'success' ) ;
446- render ( ) ;
447- }
448- } ) ;
472+ listed ++ ;
473+ listNext ( index + 1 ) ;
474+ } ) ;
475+ }
476+
477+ SoundManager . play ( 'tap' ) ;
478+ listNext ( 0 ) ;
449479 }
450480
451481 function _resultBlockNum ( result ) {
@@ -489,6 +519,31 @@ var MarketplaceScreen = (function() {
489519 return true ;
490520 }
491521
522+ function _groupSellableItems ( items ) {
523+ var byKey = { } ;
524+ var groups = [ ] ;
525+ for ( var i = 0 ; i < items . length ; i ++ ) {
526+ var item = items [ i ] ;
527+ var key = _sellGroupKey ( item ) ;
528+ if ( ! byKey [ key ] ) {
529+ byKey [ key ] = { key : key , items : [ ] } ;
530+ groups . push ( byKey [ key ] ) ;
531+ }
532+ byKey [ key ] . items . push ( item ) ;
533+ }
534+ return groups ;
535+ }
536+
537+ function _sellGroupKey ( item ) {
538+ var stats = item . stats || { } ;
539+ var statKeys = [ 'pot' , 'res' , 'swf' , 'int' , 'for_' ] ;
540+ var statBits = [ ] ;
541+ for ( var i = 0 ; i < statKeys . length ; i ++ ) {
542+ statBits . push ( statKeys [ i ] + ':' + ( stats [ statKeys [ i ] ] || 0 ) ) ;
543+ }
544+ return [ item . type , item . rarity || 1 , statBits . join ( '|' ) ] . join ( '::' ) ;
545+ }
546+
492547 function _isPendingItem ( itemId ) {
493548 for ( var ref in pendingListings ) {
494549 if ( pendingListings . hasOwnProperty ( ref ) && pendingListings [ ref ] . itemRef === itemId ) {
0 commit comments