WebModule.controller('LiveAuctionController', ['$scope', '$interval', '$sce', '$rootScope', 'WMService', 'LotService', '$timeout', '$http', '$filter', '$location', 'LiveLotViewState', 'LotViewState', function($scope, $interval, $sce, $rootScope, WMService, LotService, $timeout, $http, $filter, $location, LiveLotViewState, LotViewState) { $scope.connection = null; $scope.WMService = WMService; $scope.LotService = LotService; $scope.LotViewState = LotViewState; $scope.LiveLotViewState = LiveLotViewState; $scope.LotViewState = LotViewState; $scope.me = viewVars.me; $scope.lastLotIndex = -1; $scope.Math = Math; $scope.lotsStripSelectors = { 'wright':'.lots-strip-wrap', 'phillips':'.lots-strip', 'bonhams':'.lots-strip' }; $scope.lotsStripSelector = $scope.lotsStripSelectors[viewVars.brand]; $scope.groupVars = { selectedLots: [], selectedLotsConfirmation: false, endTime: null, isOffer: false, lotSelectionCountdownInterval: null, lotSelectionCountdown: null, isWinner: false, confirmedSelectedLots: [], flashGroupInfoTimeout: null, groupModeFlashCt: 0, isAbsenteeWinner: false, absenteeWinnerLotIds: [], }; $scope.biddersChoiceErrorMessage = { message: null, }; $scope.lotStripItems = []; $scope.pastLotsLoaded = false; $scope.reviewLotVisible = false; $scope.salesNoticeExpanded = false; $scope.showSalesNoticeExpanded = false; $scope.isBidButton = false; $scope.lotsPagination = ['n4-dorotheum', 'n4-hindmanauctions', 'n4-skinnerinc', 'n4-spectrumvehicleauctions', 'n4-spectrumauctions', 'n5-spectrum']; $scope.upcomingLotsStrip = []; $scope.lotsStripSelectors = { 'liveAuctionTheme1':'.theme1-lots-strip-wrap', 'wright':'.lots-strip-wrap', 'liveAuctionTheme2':'.theme2-lots-strip', 'phillips':'.lots-strip', 'bonhams':'.lots-strip-wrap', 'liveAuctionTheme3':'.lots-strip-wrap', 'liveAuctionTheme4':'.lots-strip-wrap', 'liveAuctionTheme5':'.lots-strip-wrap' }; if (viewVars.features.liveAuctionTheme1){ $scope.lotsStripSelector = $scope.lotsStripSelectors['liveAuctionTheme1']; } else if (viewVars.features.liveAuctionTheme2 && viewVars.brand !== 'bonhams'){ $scope.lotsStripSelector = $scope.lotsStripSelectors['liveAuctionTheme2']; } else if (viewVars.features.liveAuctionTheme3) { $scope.lotsStripSelector = $scope.lotsStripSelectors['liveAuctionTheme3']; } else if (viewVars.features.liveAuctionTheme4) { $scope.lotsStripSelector = $scope.lotsStripSelectors['liveAuctionTheme4']; } else if (viewVars.features.liveAuctionTheme5) { $scope.lotsStripSelector = $scope.lotsStripSelectors['liveAuctionTheme5']; } else { $scope.lotsStripSelector = $scope.lotsStripSelectors[viewVars.brand]; } $rootScope.ablyConnectedForLive = false; if (viewVars.features.eventMessagingService && viewVars.hasOwnProperty('eventMessagingService')) { // here we are setting the event_message_service_api_token in rootscope $rootScope.event_message_service_api_token = viewVars.eventMessagingService.event_message_service_api_token ? viewVars.eventMessagingService.event_message_service_api_token : ''; viewVars.eventMessagingService.event_message_service_api_token = ''; $rootScope.ablyConnectedForLive = viewVars.eventMessagingService.event_messaging_service_name && viewVars.eventMessagingService.event_messaging_service_name === 'ably' ? true : false; $scope.ablyToken = viewVars.ablyToken ? viewVars.ablyToken : ''; viewVars.ablyToken = ''; } $rootScope.loadLatestLotMessage = {}; $scope.loadPendingBids = {}; // Stores the lot object coming from the Realtime Server. It's named 'liveLot' here so it doesn't collide with 'lot' which is usually used for the // lot detail object. $scope.liveLot = {}; // Stores the lot data for the current lot, coming from the App Server Lot Detail endpoint (which will also give us the images, unlike $scope.liveLot) $scope.currentLotDetail = {}; // Stores the list of all lots for the current auction, coming from the App Server Lot List endpoint. $scope.lots = null; $scope.spincarUrl = ''; $scope.myBids = null; $scope.myWatching = null; $scope.currentAbsenteeBid = null; $scope.currentMessage = null; $scope.placeBidText = null; $scope.prevLot = {}; $scope.isClerkMessage = false; $rootScope.eventSendAt = null; var promiseFairWarningCounter; $scope.getSliderImages = []; if (!$scope.hasOwnProperty('model')){ $scope.model = {}; } $scope.model = $.extend($scope.model, { bidButtonState: 'bid', bidButtonStateOffIncrement: 'bid', firstLoad: true, canOffIncrementBid: false, videoVisible: true, globalvideoStreamExists: false, videoStreamExists: false, isHalfIncrement: true, videoStreamException: null, audioStreamMute: localStorage.getItem('audioStreamStatus') === 'OFF' ? 'OFF' : 'ON', nanocosmosPlayer: { 'live-video': { mute: function () {}, unmute: function () {} }, 'live-video-2': { mute: function () {}, unmute: function () {} } }, videoAutoplayAttempted: false, lotViewStyle: 'list', lotViewStyles: ['list','grid'], initialized: false, // The bidding room will not be displayed until this is true. This is false until we connect to the ws_url and get the lot details via ajax. connected: false, // Set to true once we connect. If we don't receive a heartbeat for 6 seconds, this will be set false by heartAttack(). heartbeatPromise: null, // Used by heartbeat() to store the heartbeat timeout, which throws heartAttack() if necessary. stateTime: null, // Stores the number of seconds remaining for a given time sensitive state such as "Fair Bid Warning 5..4..3..2..1". // State variables used for viewing a list of lots. currentLotsView: 'all', // Indicates which list of lots are currently being viewed. Can be 'all' to see list of all lots, 'watched' for watched lots. currentViewLots: null, // Here we store list of all lots, or watched lots, depending on which list is currently being viewed, based on currentLotsView. // State variables used for viewing a single lot detail. currentLotView: 'current', // Can be 'detail' to view a chosen lot detail within all/watched lots, or 'current' to see the current lot. lotDetail: null, // Here we store the lot detail for the current view. lotDetailImageIndex: 0, // Stores the thumbnail index within a lot detail, which changes whenever you click on a thumbnail. mobileDisclaimer: $scope.isMobile, currentLotClicked: -1, currentTab: 'currentLot', groupView: 'currentGroup', lotListFilter: 'all',// 'upcoming', lotListLimit: 10, lotListPage: 1, jumpToLot: "", initializationFailed: false, isRetina: false, isSingleLotAuction: false, numReconnectAttempts: 3, reconnectAttemptCount: 0, reconnectInterval: "", isVideoDisabled: true, biddingMessage: null, showReconnectLoader: false, isFullScreenVideo: false, numReconnectAttempts: 3, reconnectAttemptCount: 0, reconnectInterval: "", bidderInfoOpen: false, liveLotListTab: 'upcoming', // Live lot list displayed on the right for all clients slideToBid: { value: 0, bidAtSlideStart: 0, lastChange: 0, skipEndEvent: false, options: { floor: 0, ceil: 100, step: 1, minLimit: 0, maxLimit: 100, showSelectionBar: true, onChange: function (id) { if ($scope.model.slideToBid.lastChange == 0 && $scope.model.slideToBid.value > 75) { $scope.model.slideToBid.skipEndEvent = true; } else { $scope.model.slideToBid.skipEndEvent = false; } $scope.model.slideToBid.lastChange = $scope.model.slideToBid.value; }, onStart: function (id) { $scope.model.slideToBid.lastChange = 0; if ($scope.model.canOffIncrementBid === true) { if ($scope.model.isHalfIncrement === false) { $scope.model.slideToBid.bidAtSlideStart = $scope.model.offIncrementBids[0]; } else { $scope.model.slideToBid.bidAtSlideStart = $scope.liveLot.state.asking_price; } } else { $scope.model.slideToBid.bidAtSlideStart = $scope.liveLot.state.asking_price; } }, onEnd: function (id) { if ($scope.model.slideToBid.skipEndEvent === true) { $scope.model.slideToBid.value = 0; } else if ($scope.model.slideToBid.bidAtSlideStart === $scope.liveLot.state.asking_price) { $scope.placeMobileBid(); } else if (($scope.model.isHalfIncrement === false && $scope.model.canOffIncrementBid === true) && ($scope.model.slideToBid.bidAtSlideStart === $scope.model.offIncrementBids[0])) { $scope.model.bidButtonStateOffIncrement = 'confirm'; $scope.placeMobileBid(true); } else { $scope.setLocalStateMessage("Another bid was taken"); $scope.model.slideToBid.value = 0; } }, }, message: 'Slide to Bid', }, hasHeartAttack: false, isAudioEnabled: (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) ? false : true, confirmationGroupPopup: true }); $scope.localStateMessageExclamationMark = false; $scope.liveLotStateMessageExclamationMark = false; $scope.isClerkMessage = false; $scope.mappingEventBidAndMeLiveBids = { "auction_lot_id": {"field_name": "auction_lot_id", "default": null}, "creating_client_id": {"field_name": "creating_client_id", "default": null}, "row_id": {"field_name": "id", "default": null}, "amount": {"field_name": "bid_value", "default": null}, "type": {"field_name": "type", "default": null}, "registration_id": {"field_name": "registration_id", "default": null}, "tenant_id": {"field_name": "tenant_id", "default": null}, "created_at": {"tifield_nametle": "rxts", "default": null}, "updated_at": {"field_name": "rxts", "default": null}, "absentee_bid_id": {"field_name": "absentee_bid_id", "default": null}, "note": {"field_name": "note", "default": null}, "integration_id": {"field_name": "integration_id", "default": null}, "amount_percentage": {"field_name": "amount_percentage", "default": null}, "auction_lot_group_id": {"field_name": "auction_lot_group_id", "default": null}, "house_bid": {"field_name": "house_bid", "default": false}, "cancelled": {"field_name": "cancelled", "default": false}, "rejected": {"field_name": "rejected", "default": false} }; if (viewVars.currentRouteName === 'live-auction-mobile') { /* supported current views are current history lots */ $scope.model.mobile = { currentView: 'current', }; window.onpageshow = function (event) { if (event.persisted) { window.location.reload(); } }; } if (viewVars.brand == 'n4-dorotheum' && viewVars.currentRouteName == 'live-auction-mobile' && $rootScope.isMobile) { jQuery(document).ready(function(event) { $(window).on('popstate', function() { var hash = window.location.hash; if (hash === '') { $scope.closeLotReview(); } }); window.history.pushState('forward', null, './'); }); } $scope.staticGuides = {}; $scope.lotList = { bids: { list: null, visible: false, }, watching: { list: null, visible: false, }, }; if (viewVars.features.enlargeThumbnailImagesPopup) { $rootScope.popoverThumbnailLots(3500); } $scope.isGroupedLot = function () { if ($scope.liveLot.hasOwnProperty('items') === true && $scope.model.currentTab !== 'watching' && $scope.model.currentTab !== 'bids') { $scope.model.currentViewLots = $scope.liveLot.items; return true; } return false; } if (viewVars.features.enlargeThumbnailImagesPopup) { $rootScope.popoverThumbnailLots(1000); } $scope.replaceLots = function(globalLots, localLots) { var newGlobalLots = $.map(globalLots, function(globalLot, index) { for (var i=0;i -1) { globalLot.auction_lot_group = null; globalLot.status = "active"; } return globalLot; }); newGlobalLots = WMService.afterLots(newGlobalLots); return newGlobalLots; }; $scope.replaceMeLiveBids = function(meLiveLot, eventBid, mappingkeys) { mappingkeys = mappingkeys || mappingEventBidAndMeLiveBids; if (!mappingkeys || !meLiveLot || !eventBid) return; var auctionRegistration = WMService.auctionRegistration(viewVars.auctionId); // Add bid data for the user if no existing bid is found, but the bid event contains details for the user if ( $scope.currentLotDetail && auctionRegistration && eventBid.registration_id == auctionRegistration.row_id && !LotService.hasBid($scope.currentLotDetail) ) { meLiveLot.push($scope.getBidFromMapping(eventBid, {}, mappingkeys)); } if (viewVars.auctionId !== eventBid.auction_id) return; var updateBid = false; for (i in meLiveLot) { if (meLiveLot[i].row_id == eventBid.id) { if (eventBid.active === false) { updateBid = true; } else { updateBid = true; meLiveLot[i] = $scope.getBidFromMapping(eventBid, meLiveLot[i], mappingkeys); } } } if (eventBid.user_id == viewVars.me.row_id && !updateBid && eventBid.active === true) { meLiveLot.push($scope.getBidFromMapping(eventBid, {}, mappingkeys)); } }; $scope.getBidFromMapping = function(source, dest, mappingkeys) { if (!mappingkeys || !source) return dest; angular.forEach(mappingkeys, function (value, key) { dest[key] = source[value.field_name] || value.default; }); return dest; }; $rootScope.viewWatchedLotDetail = function () { $scope.viewLotDetail($scope.model.currentLotClicked); } // Sync globalLots (typically $scope.lots.result_page) with another list of lots within the bidding room. This will allow us to click on the watch // icon of a global lot in $scope.lots, and have the watch icon within a watched lots list ($scope.lotList.watching.list.result_page) update as well. // Use this every time you query and show a list of lots that is separate from the global lots. This will keep all lot lists in sync. // Its just an alias of $scope.replaceLots to make the call more self-documenting. $scope.syncLots = $scope.replaceLots; $scope.syncAllLots = function(globalLots){ if ($scope.isShowingWatchlist()){ globalLots = $scope.syncLots(globalLots, $scope.lotList.watching.list.result_page); } else if ($scope.isShowingMyBids()){ globalLots = $scope.syncLots(globalLots, $scope.lotList.bids.list.result_page); } // Sync the current lot with the lot in the global lot list so that clicking on the star icon in the list will also star the current lot // detail. globalLots = $scope.syncLots(globalLots, [$scope.liveLot.item]); if ($scope.model.currentLotView == 'current'){ globalLots = $scope.syncLots(globalLots, [$scope.model.lotDetail]); } return globalLots; } $scope.isLiveLotStateMessage = function() { return ($scope.liveLot.state && $scope.liveLot.state.message && $scope.liveLot.state.message.trim() && (viewVars.currentRouteName === 'live-auction' ? !$scope.isClerkMessage : true)); }; $scope.$watch('liveLot.state.message', function(){ $scope.isClerkMessage = false; $scope.liveLotStateMessageExclamationMark = false; if ($scope.isLiveLotStateMessage()) { $scope.isClerkMessage = ['Fair Warning', 'Last Call'].indexOf($scope.liveLot.state.message) === -1; $scope.liveLotStateMessageExclamationMark = ['Fair Warning', 'Last Call'].indexOf($scope.liveLot.state.message) > -1; $scope.isClerkMessage = ['Fair Warning', 'Last Call'].indexOf($scope.liveLot.state.message) === -1; } }); $scope.executeMessage = function(message){ $scope.$apply(function(){ var envelope = (viewVars.features.eventMessagingService && $rootScope.ablyConnectedForLive) ? message.data : $.parseJSON(message.data); $scope.model.connected = true; /** * For the ably, the customer channel will be the same for live and timed auctions * so if the customer is registered with the timed auction of the same brand then * the event like “outbid” event will be received here too. * So to prevent it, we are not reading the outbid event of the timed auction. */ if (["outbid"].indexOf(envelope.type) !== -1) { return false; } if ( $scope.isAblyEnabled() && $scope.liveLot && $scope.liveLot.state && ["state", "bid", "lot", "lot-update"].indexOf(envelope.type) !== -1 && checkSequenceNumber($scope.liveLot, envelope) === false && new Date($rootScope.eventSendAt) < new Date(envelope.send_at) ) { return false; } if ('state' == envelope.type){ if ($scope.isAblyEnabled() && $scope.liveLot.item.row_id !== envelope.message.lot_id) { return false; } /** * Here we are not updating state data in case the current event state sequence number is less than * The stored state sequence number in the live lot. */ if ($scope.isAblyEnabled() && $scope.liveLot.state && $scope.liveLot.item.row_id === envelope.message.lot_id && $scope.liveLot.state.state_sequence_number && envelope.message.state_sequence_number < $scope.liveLot.state.state_sequence_number) { return false; } var previousAskingPrice = $scope.liveLot.state.asking_price; $scope.updateState(envelope.message); if ($scope.liveLot.state.message === "Fair Warning" && viewVars.features.fairWarningCountdownTimer && $scope.liveLot.state.fair_warning_time_left) { $scope.startFairWarningCountDown(); } $scope.updateCurrentLot(envelope.message, 'state'); // Reset the bid button only if asking price is changed, not when state is changed if ((viewVars.features.noConfirmLiveBid === false && (envelope.message.asking_price !== previousAskingPrice)) || viewVars.features.noConfirmLiveBid === true) { $scope.model.bidButtonState = 'bid'; // Reset the bid button state since we may have a new asking price. $scope.model.bidButtonStateOffIncrement = 'bid'; } if(envelope.message.highbid != null && viewVars.me && envelope.message.highbid.user_id == viewVars.me.row_id){ $scope.model.localStateMessage = ""; } if(envelope.message.hasOwnProperty('allowed_off_increment_bids')){ $scope.model.offIncrementBids = envelope.message.allowed_off_increment_bids; } if(envelope.message.hasOwnProperty('state') && envelope.message.state === 'grouped') { $scope.closeLotSelectionModal(); } if(viewVars.features.persistentBiddingRoomMessages === true) { if (envelope.message.message === undefined || envelope.message.message === null || envelope.message.message === '') { $scope.liveLot.state.message = $scope.currentMessage; } else { $scope.currentMessage = envelope.message.message; } } if (envelope.message.message === 'BIDRESET') { $scope.liveLot.state.message = WMService.hasProduct('realEstateHybrid') ? 'Bidding has been reset for this item' : 'Bidding has been reset for this lot'; } } else if ('bid' == envelope.type){ // Here we are not updating the bid event data if it's not for the current lot in case of ably. if ($scope.isAblyEnabled() && $scope.liveLot.item.row_id !== envelope.message.state.lot_id) { return false; } $scope.updateBids(envelope.message); if ($scope.model.bidButtonState === 'confirm' && viewVars.brand === 'n4-dorotheum') { $scope.setLocalStateMessage("Another bid was taken"); $scope.localStateMessageExclamationMark = true; } $scope.model.bidButtonState = 'bid'; // Reset the bid button state since we will have a new asking price. if (envelope.message.state && envelope.message.state.hasOwnProperty('allowed_off_increment_bids')) { $scope.model.offIncrementBids = envelope.message.state.allowed_off_increment_bids; } // See if this bid is also in the pending bids array. If it // is, remove it. var index = null; if ($scope.liveLot.hasOwnProperty('pendingBids') && $scope.liveLot.pendingBids){ $scope.liveLot.pendingBids.some(function(b, i) { // Only remove this bid if it is no longer pending if (b.id == envelope.message.id && !envelope.message.pending && !(envelope.message.hasOwnProperty('reversePending') && envelope.message.reversePending)){ return index = i; } }); } if (index!=null){ $scope.liveLot.pendingBids.splice(index, 1); } //show the outbid message if(viewVars.featureVersions.liveAuction == 2){ if($scope.hasOwnProperty('me') && $scope.me != null && $scope.me.hasOwnProperty('row_id')){ if(envelope.message.active && envelope.message.user_id != $scope.me.row_id && typeof $scope.liveLot.bids[$scope.liveLot.bids.length-2] != 'undefined' && $scope.liveLot.bids[$scope.liveLot.bids.length-2].user_id == $scope.me.row_id){ if(viewVars.brand === 'n4-dorotheum') { $scope.setLocalStateMessage($filter('translate')("OUTBID")); $scope.localStateMessageExclamationMark = true; } else if (viewVars.brand=="stacksbowers") { $scope.model.localStateMessage = $filter('translate')("You've been outbid!"); } else { $scope.model.requireToDisplayLocalMessage = true; $scope.setLocalStateMessage($filter('translate')("You've been outbid!")); } } } } var auctionRegistration = WMService.auctionRegistration(viewVars.auctionId); if (auctionRegistration && typeof auctionRegistration == 'object' && auctionRegistration.hasOwnProperty('live_bids')) { $scope.replaceMeLiveBids(auctionRegistration.live_bids, envelope.message, $scope.mappingEventBidAndMeLiveBids); } /*var registration = WMService.auctionRegistration(viewVars.auctionId); //check that the bid_id exists, if so replace it. if not check that the user_id matches the viewVars.me.row_id and if so add it. var updateBid = false; for(i in registration.live_bids){ if(registration.live_bids[i].id == envelope.message.id){ updateBid = true; registration.live_bids[i] = envelope.message; } } if(envelope.message.user_id == viewVars.me.row_id && !updateBid){ registration.live_bids.push(envelope.message); }*/ } else if ('lot' == envelope.type){ if (!$scope.isAblyEnabled()) { $scope.heartbeat(); // On initial load, make this the first heartbeat, that means if we don't get another heartbeat in 6 seconds, then connection lost. } $scope.flashGroupInfo(); $scope.model.isSingleLotAuction = envelope.message.item !== null && envelope.message.item.auction.lot_count <= 1; $scope.updateLots(envelope.message); $scope.model.bidButtonState = 'bid'; // Reset the bid button state since we may have a new asking price. if (!(viewVars.brand === 'n4-dorotheum' && $scope.model.reviewLotVisible)) { $scope.model.hideImages = true; } $timeout(function() { $scope.model.hideImages = false; },250); //set the off increments bid if it exists if(envelope.message.state.hasOwnProperty('allowed_off_increment_bids')){ $scope.model.offIncrementBids = envelope.message.state.allowed_off_increment_bids; if($scope.model.firstLoad){ $scope.model.firstLoad = false; $scope.model.canOffIncrementBid = (WMService.isPercentageBiddingEnabled(envelope.message) === true) ? false : viewVars.joinAuction.response.user_can_bid_off_increment; }else{ $scope.model.canOffIncrementBid = true; } } if(envelope.message.state.hasOwnProperty('message')) { $scope.currentMessage = envelope.message.state.message; } if ($scope.liveLot.state.message === "Fair Warning" && viewVars.features.fairWarningCountdownTimer && $scope.liveLot.state.fair_warning_time_left) { $scope.startFairWarningCountDown(); } if (viewVars.features.realEstate){ // Query the lot detail to get auction registration and deposit amount. var promise = $http.get(viewVars.endpoints.lotAjax + envelope.message.item.row_id); promise.success(function(data){ $.extend(true, $scope.liveLot.item, data.response); }); promise.error(WMService.handleError); } if(viewVars.featureVersions.liveAuction == 2){ $scope.lotList.bids.list = null; $scope.lotList.bids.visible = null; $scope.lotList.bids.loadingComplete = null; $scope.lotList.watching.list = null; $scope.lotList.watching.visible = null; $scope.lotList.watching.loadingComplete = null; if($scope.model.currentTab == 'bids'){ $scope.showMyBids(); }else if($scope.model.currentTab == 'watching'){ $scope.showWatching(); } } if ($scope.model.initialized) { $scope.setLotStripItems(); } $scope.getSliderImages = envelope.message.item.images; // Here we are updating the pending bids data again in case the same lot loaded and has old data. if ($scope.isAblyEnabled() && envelope.message.item && $scope.loadPendingBids && $scope.loadPendingBids[envelope.message.item.row_id] !== undefined) { $scope.updatePendingBid($scope.loadPendingBids[envelope.message.item.row_id]); } } else if ('lots-status-update' == envelope.type) { $scope.lots.result_page = $scope.deleteLotGroup($scope.lots.result_page, envelope.message.lots); $scope.setLotStripItems(); } else if ('off-increment-bidding' == envelope.type){ $scope.model.canOffIncrementBid = envelope.message.can_bid; } else if ('heartbeat' == envelope.type){ $scope.heartbeat(); } else if ('pending-bids' == envelope.type){ $scope.updatePendingBids(envelope.message); } else if ('pending-bid' == envelope.type){ /** * Here we are storing the pending bids event data into a variable to update * it later for the current lot on ably and attach it to the current live lot. */ if ($scope.isAblyEnabled() && $scope.liveLot.hasOwnProperty('item') && $scope.liveLot.item.row_id && envelope.message.auction_lot_id == $scope.liveLot.item.row_id) { $scope.loadPendingBids[$scope.liveLot.item.row_id] = envelope.message; } $scope.updatePendingBid(envelope.message); } else if ('rejected-bid' == envelope.type){ $scope.updateRejectedBid(envelope.message); } else if ('lot-update' == envelope.type){ /*console.log('lot-update ', envelope); console.log($scope.lots.result_page)*/ // Here we are not updating the lot update data if it's not for the current live lot in case of ably. if ($scope.isAblyEnabled() && $scope.liveLot.item.row_id !== envelope.message.item.row_id) { return false; } if ($scope.isGroupedLot() !== true && envelope.message.state.highbid) { if (viewVars.me && envelope.message.state.highbid.user_id && envelope.message.state.highbid.user_id == viewVars.me.row_id) { envelope.message.item['winning_bid_id'] = envelope.message.state.highbid.id; } else { envelope.message.item['winning_bid_id'] = null; } } if ($scope.isGroupedLot() === true) { $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, envelope.message.items); } else { if ($scope.lots && $scope.lots.result_page) { $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, [envelope.message.item]); } } if ($scope.isGroupedLot() === true) { $scope.liveLot.item = envelope.message.items[0]; // need to call convertGroupToLotItem $scope.liveLot.item = $scope.convertGroupToLotItem(envelope.message); } else { $scope.liveLot.item = envelope.message.item; $scope.currentLotDetail = envelope.message.item; } if (envelope.message.hasOwnProperty('items')) { $scope.updateLots(envelope.message); } if ($scope.model.initialized) { $scope.setLotStripItems(); } /** * Here for ably we are updating the state data from lot update in case from rewind * we do not find the latest state data of the lot and the lot update state sequence number * greater than from lot state sequence number. */ if ($scope.isAblyEnabled() && $scope.liveLot.item.row_id === envelope.message.item.row_id && envelope.message.state && envelope.message.state.state_sequence_number && envelope.message.state.state_sequence_number > $scope.liveLot.state.state_sequence_number) { $scope.liveLot.state.state_sequence_number = envelope.message.state.state_sequence_number; } } else if ('current-absentee-bid' == envelope.type){ $scope.currentAbsenteeBid = envelope.message; //console.log('current-absentee-bid!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!',envelope.message); } else if ('lot-group-selecting-lots-finished' == envelope.type){ $scope.groupVars.selectedLotsConfirmation = true; $scope.groupVars.confirmedSelectedLots = envelope.message.lots; if (envelope.message.hasOwnProperty('state') && envelope.message.state === 'lotSelectionFinishedByTimeout' && envelope.message.is_winner === true) { // hide the modal //$scope.closeLotSelectionModal(); $scope.groupVars.selectionTimesUp = true; // show the timeout modal //$scope.showTimeoutModal(); } else if (envelope.message.hasOwnProperty('state') && envelope.message.state === 'lotSelectionFinished') { if (viewVars.hasOwnProperty('me') && viewVars.me.hasOwnProperty('row_id') && $scope.liveLot.state.highbid.user_id === viewVars.me.row_id) { $scope.groupVars.endTime = 4000; $scope.groupVars.selectionTimesUp = false; if ($scope.model.confirmationGroupPopup) { $scope.showLotSelectionModal(); } else { $scope.model.confirmationGroupPopup = true; } countdown($scope.groupVars.endTime); } } } else if ('lot-group-start-selecting-lots' == envelope.type){ $scope.groupVars.isWinner = envelope.message.is_winner; if (envelope.message.is_winner === true && envelope.message.end_time !== 0) { $scope.authenticateWebsocket(); $scope.groupVars.endTime = envelope.message.end_time; clearInterval($scope.groupVars.lotSelectionCountdownInterval); countdown($scope.groupVars.endTime); if(!$scope.$$phase){ $scope.$apply(); } $scope.groupVars.isSubjectTo = envelope.message.subject_to; $scope.groupVars.isOffer = envelope.message.is_offer; $scope.groupVars.selectedLotsConfirmation = false; $scope.groupVars.confirmedSelectedLots = []; $scope.groupVars.isAbsenteeWinner = false; // update items from this message if (viewVars.features.hasOwnProperty('enableChoiceAbsenteeBid') && viewVars.hasOwnProperty('manually_approve_online_bids') && viewVars.manually_approve_online_bids) { $scope.groupVars.selectedLots = []; envelope.message.items.forEach(function(item) { if (item.absentee_bid_lot && $scope.liveLot.mode == 'take_any_quantity') { $scope.groupVars.selectedLots.push(item.row_id); $scope.groupVars.absenteeWinnerLotIds = [item.row_id]; $scope.groupVars.isAbsenteeWinner = item.absentee_bid_lot; } }); } $scope.showLotSelectionModal(); } else if (envelope.message.is_winner === true && envelope.message.end_time === 0) { // todo remove this if we fix AM-36005 $scope.groupVars.selectionTimesUp = true; $scope.showTimeoutModal(); } } else if ('lot-group-select-lots-response' == envelope.type) { var data = envelope.message.data; if (data.success === false) { if (data.error.class === 'LOT-SELECT-ERROR') { let lotSelectErrorMap = { "isRealEstateHybrid": { "preMessage": "Requested list of items can't be selected: ", "doesnt_belong_to_auction": 'Some of selected items doesn\'t belong to the current auction', "one_bid_take_all": 'All items must be selected', "take_only_one": 'You can select only one item', "take_between_x_and_y": 'You can select only between ' + $scope.liveLot['take_between'].min + ' and ' + $scope.liveLot['take_between'].max + ' items', }, "notRealEstateHybrid": { "preMessage": "Requested list of lots can't be selected: ", "doesnt_belong_to_auction": 'Some of selected lots doesn\'t belong to the current auction', "one_bid_take_all": 'All lots must be selected', "take_only_one": 'You can select only one lot', "take_between_x_and_y": 'You can select only between ' + $scope.liveLot['take_between'].min + ' and ' + $scope.liveLot['take_between'].max + ' lots', }, } let productType = WMService.hasProduct('realEstateHybrid') ? 'isRealEstateHybrid' : 'notRealEstateHybrid'; data.error.message = lotSelectErrorMap[productType]['preMessage'] + lotSelectErrorMap[productType][$scope.liveLot.mode]; } $scope.handleBiddersChoiceError(data.error); } else { // todo we want to show the confirmation screen once that is possible clearInterval($scope.groupVars.lotSelectionCountdownInterval); } } else if ('previous-lots-update' == envelope.type) { try { var updatedLots = envelope.message.lots; if (updatedLots) { updatedLots.forEach(function(updatedLot) { var lot = $scope.lots.result_page.find(function(l) { return l.row_id === updatedLot.lot_id; }); // Update the status only if there is a difference if (lot && lot.status != updatedLot.status) { lot.status = updatedLot.status; } }); } } catch (error) { AMLogger.logToConsole(error, 'error'); } } if (['lot'].indexOf(envelope.type) > -1 || ('bid' == envelope.type && $scope.liveLot.rejectedBids && $scope.liveLot.rejectedBids.length > 0 && envelope.message.bid > $scope.liveLot.rejectedBids[$scope.liveLot.rejectedBids.length-1].bid)){ if (!$scope.model.requireToDisplayLocalMessage) { $scope.model.localStateMessage = null; // Remove the "Another bid was taken" message. } else { $scope.model.requireToDisplayLocalMessage = false; } // TODO Refactor variable so it reflects the message properly. // Maybe just use $scope.model.currentRejectedBid and show "Another bid was taken" message in the template. } // any message of these types that come in we want to set the slider back to 0 var resetMessageTypes = [ 'state', 'bid', 'lot', 'off-increment-bidding', 'pending-bids', 'pending-bid', 'rejected-bid', 'lot-update', ]; if (resetMessageTypes.indexOf(envelope.type) !== -1) { $scope.model.slideToBid.value = 0; } }); }; function countdown(countdown) { // countdown should be supplied in ms so we need to divide by 1000 countdown = Math.round(countdown / 1000); $scope.groupVars.lotSelectionCountdown = countdown; $scope.groupVars.lotSelectionCountdownInterval = setInterval(function(){ if (countdown == 0) { return; } countdown--; $scope.groupVars.lotSelectionCountdown = countdown; var timerSeconds = (Math.floor($scope.groupVars.lotSelectionCountdown)), totalTime = $scope.getLotSelectionTime(), pct = timerSeconds * $('.selection-timer-progress').width() / totalTime; $('.selection-timer-progress-bar').animate({ width: pct + 'px' }, 1000, 'linear'); $scope.$apply(); if (countdown <= 0) { clearInterval($scope.groupVars.lotSelectionCountdownInterval); $scope.lotSelectionTimerCountdownCallback(); } }, 1000); } $scope.hasCurrentAbsenteeBid = function(){ /*console.log($scope.currentAbsenteeBid) console.log($scope.me.row_id) console.log($scope.currentAbsenteeBid.user_id) console.log($scope.liveLot.state.highbid.bid) console.log($scope.currentAbsenteeBid.max_bid) console.log('done')*/ return $scope.currentAbsenteeBid != null && $scope.me.row_id == $scope.currentAbsenteeBid.user_id && parseFloat($scope.liveLot.state.highbid.bid) <= parseFloat($scope.currentAbsenteeBid.max_bid); } $scope.auctionSubtitleDate = function(auction){ if(auction) { var date; if (auction.timezone){ date = moment(auction.time_start).tz(auction.timezone); } else { date = moment(auction.time_start); } date.locale(viewVars.locale); if (viewVars.brand === 'bonhams' || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5) { var format = 'D MMMM YYYY | hA z'; if (date.minute() != 0){ format = 'D MMMM YYYY | h:mmA z'; } } else if (viewVars.brand === "n4-dorotheum") { var format = "D.MM.YYYY-H:mm z"; } else if (viewVars.brand === "n5-keeneland") { var format = 'MMMM D, YYYY | ha z'; } else { var format = 'D MMMM YYYY hA z'; if (date.minute() != 0){ format = 'D MMMM YYYY h:mmA z'; } } return date.format(format); } return null; } $scope.paddleMediumTime = function(time){ var date; date = moment(time); date.locale(viewVars.locale); var format = 'h:mm:ss A'; return date.format(format); } $scope.paddleMediumTime24 = function (time) { var date; date = moment(time); date.locale(viewVars.locale); var format = 'HH:mm:ss'; return date.format(format); }; $scope.setStateTime = function(milliseconds){ var updateInterval = 1000; if (milliseconds > 0){ $scope.model.stateTime = milliseconds/1000; $timeout(function(){$scope.setStateTime(milliseconds-updateInterval);},updateInterval); } else { $scope.model.stateTime = null; } }; $scope.isMyBidPending = function(){ return Boolean(LotService.myLivePendingBid($scope.liveLot)); }; $scope.handleBiddersChoiceError = function (error) { $scope.biddersChoiceErrorMessage = { message: error.message, }; $('#bidders-choice-error-popup').modal(); } $scope.hideBiddersChoiceErrorModal = function () { $scope.biddersChoiceErrorMessage = { message: '', }; $('#bidders-choice-error-popup').modal('hide'); } $scope.updateState = function(state){ if($scope.liveLot.state.paused_at && !state.paused_at){ if (WMService.hasProduct('realEstateHybrid') === false) { $scope.setLocalStateMessage($filter('translate')("Lot Resumed")); } else { $scope.setLocalStateMessage($filter('translate')("Item Resumed")); } $scope.liveLot.state.paused_at = null; } if (state.hasOwnProperty('timer')){ $scope.setStateTime(state.timer); } $.extend($scope.liveLot.state, state); }; $scope.updatePendingBids = function(pendingBids){ $scope.liveLot.pendingBids = pendingBids; }; $scope.updatePendingBid = function(pendingBid){ if (!$scope.liveLot.hasOwnProperty('pendingBids') || !$scope.liveLot.pendingBids){ $scope.liveLot.pendingBids = []; } var index = null; $scope.liveLot.pendingBids.some(function(b, i) { if (b.id == pendingBid.id){ return index = i; } }); if(index!=null){ $scope.liveLot.pendingBids[index] = pendingBid; } else { $scope.liveLot.pendingBids.push(pendingBid); } }; $scope.updateRejectedBid = function(rejectedBid){ var price = $scope.liveLot.state.asking_price; if($scope.liveLot.hasOwnProperty('bids') && $scope.liveLot.bids.length != 0) { price = $scope.liveLot.bids[$scope.liveLot.bids.length-1].bid; } var myCurrentPendingBid = LotService.myLivePendingBid($scope.liveLot); if (!$scope.liveLot.hasOwnProperty('pendingBids') || !$scope.liveLot.pendingBids){ $scope.liveLot.pendingBids = []; } if (myCurrentPendingBid.hasOwnProperty('bid') && myCurrentPendingBid.bid !== null && price > myCurrentPendingBid.bid){ rejectedBid.id = myCurrentPendingBid.id; } var pendingBidIndex = null; $scope.liveLot.pendingBids.some(function(b, i) { if (b.id == rejectedBid.id){ return pendingBidIndex = i; } }); // Remove bid from pending bids list if(pendingBidIndex!=null) { $scope.liveLot.pendingBids.splice(pendingBidIndex); } if (!$scope.liveLot.hasOwnProperty('rejectedBids') || !$scope.liveLot.rejectedBids){ $scope.liveLot.rejectedBids = []; } var index = null; $scope.liveLot.rejectedBids.some(function(b, i) { if (b.id == rejectedBid.id){ return index = i; } }); // Update rejected bids list if(index!=null){ $scope.liveLot.rejectedBids[index] = rejectedBid; } else { $scope.liveLot.rejectedBids.push(rejectedBid); } if (myCurrentPendingBid && myCurrentPendingBid.id == rejectedBid.id){ if (viewVars.brand === 'n4-dorotheum' && typeof rejectedBid.anotherBidTaken === 'undefined') { $scope.setLocalStateMessage("Your bid was not taken"); } else { $scope.setLocalStateMessage("Another bid was taken"); } $scope.localStateMessageExclamationMark = true; } }; $scope.bidLabel = function(bid, isPendingBid) { var label = ''; if (isPendingBid === true || bid.paddle !== 'floor') { if (viewVars.features.hidePaddle) { label += (viewVars.brand === 'fasigtipton') ? 'Internet' : 'Internet bid'; } else if (bid) { label += viewVars.paddleLabel.charAt(0).toUpperCase() + viewVars.paddleLabel.slice(1) + ' #' + (isPendingBid === true && bid.user && bid.user.paddle ? bid.user.paddle : bid.paddle); } } else { label += 'Floor bid'; } if ($scope.me && bid && (bid.user_id === $scope.me.row_id || isPendingBid === true) && (['live-auction-bulletin-spincar'].indexOf(viewVars.currentRouteName) === -1)) { if ((['n4-skinnerinc'].indexOf(viewVars.brand)) !== -1) { label = $filter('translate')("You") + " (" + label + ")"; } else { label = label + " (" + $filter('translate')("You") + ")"; } } return label; }; $scope.checkMyBid = function(bids) { var myBidFound = false; for (var i = 0; i < bids.length; i++) { if ($scope.me && bids[i].user_id == $scope.me.row_id) { myBidFound = true; break; } } return myBidFound; } $scope.isOutbidUser = function() { return ( ( LotService.isNow($scope.currentLotDetail, $scope.liveLot.state) && $scope.model.connected && !$scope.isHighestBidder() && !WMService.isPendingAuctionRegistration($scope.currentLotDetail.auction.row_id) && !WMService.isDeclinedAuctionRegistration($scope.currentLotDetail.auction) && !($scope.isMyBidPending() || $scope.model.bidSent) && $scope.model.currentLotView != "detail" ) || ( LiveLotViewState.isSold($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) && $scope.model.currentLotView != "detail" ) ); } $scope.isWinningUser = function() { return ( ( LiveLotViewState.isWinning($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) && $scope.model.currentLotView != "detail" && !LiveLotViewState.isPendingBid($scope.liveLot, $scope.currentLotDetail, $scope.model.connected, $scope.model.bidSentShowStatus) ) || ( LiveLotViewState.isWon($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) && $scope.model.currentLotView != "detail" ) ); } $scope.bidLabelV2 = function(bid){ var label = ''; if (bid.paddle !='floor' && bid.type != 'proxy'){ if (viewVars.features.hidePaddle) { label += 'Internet bid'; } else { //todo if we ever have a foreign auction house not using paddle we will have to rethink this. if(viewVars.locale == 'en'){ var justBidLabel = (viewVars.brand == "n4-dorotheum") ? "" : " just bid"; label += viewVars.paddleLabel.charAt(0).toUpperCase() + viewVars.paddleLabel.slice(1) + ' #'+bid.paddle + justBidLabel; }else{ label += $filter('translate')('PADDLE_JUST_BID', {PADDLE: bid.paddle}); } } } else if (bid.type =='proxy'){ label += 'Proxy bid'; } else { label += 'Floor bid'; } if ($scope.me && bid.user_id == $scope.me.row_id){ label = "You just bid!"; if (viewVars.brand == 'stacksbowers') { label = "Paddle #" + bid.paddle+" (You)"; } } return label; } /*$scope.bidToHistoryItem = function(bid){ var item = { time: bid.rxts, description: $scope.bidLabel(bid), value: $scope.model.currencySign + bid.bid }; return item; }; $scope.addHistoryItem = function(item){ $scope.model.history.push(item); };*/ $scope.updateBids = function(newBid){ var newState = newBid.state; if (newBid.active){ // Adding a bid var bidFound = false; for (var i=0; i<$scope.liveLot.bids.length; i++){ if ($scope.liveLot.bids[i].id == newBid.id){ $scope.liveLot.bids[i] = newBid; bidFound = true; break; } } if (!bidFound){ $scope.liveLot.bids.push(newBid); } } else { // Deactivating a bid var deactivated = false; for (var i=0; i<$scope.liveLot.bids.length; i++){ if ($scope.liveLot.bids[i].id == newBid.id){ $scope.liveLot.bids[i] = newBid; deactivated = true; if ($scope.me && newBid.user_id == $scope.me.row_id){ $scope.setLocalStateMessage("Another bid was taken"); $scope.localStateMessageExclamationMark = true; } } } if(!deactivated){ $scope.liveLot.bids.push(newBid); } } $.extend($scope.liveLot.state, newState); var myCurrentPendingBid = LotService.myLivePendingBid($scope.liveLot); if(myCurrentPendingBid && myCurrentPendingBid.hasOwnProperty('bid') && newBid.hasOwnProperty('bid') && newBid.bid !== null && newBid.bid > myCurrentPendingBid.bid){ $scope.updateRejectedBid(newBid); } }; // Adjust offset and limit so that we only result in filling in lot list gaps (where gap is defined by isPlacholder(lot) == true). We don't want to refresh those lots that are not placeholders. $scope.sanitizedOffsetAndLimitForGaps = function(allLots, offset, limit){ // If offset is a negative number, readjust offset and limit such that we get all lots that don't have a negative index. if (offset < 0){ var previousOffset = offset; var previousLimit = limit < 0 ? 50 : limit; // Set value to 50 in case the limit is negative offset = 0; // Find the offset of the first placeholder lot within (offset, offset+limit). for (var i=offset; i= allLots.length){ endOffset = allLots.length-1; } var edgeLots = [allLots[startOffset]]; if (startOffset != endOffset){ edgeLots.push(allLots[endOffset]); } return edgeLots; } $scope.showMoreLotsLabel = function(offset, limit){ var edgeLots = $scope.edgeLotsFromOffsetAndLimit($scope.lots.result_page, offset, limit); if (edgeLots.length == 1){ return "See Lot " + edgeLots[0].lot_number + edgeLots[0].lot_number_extension; } else if (edgeLots.length == 2){ return "See Lots " + edgeLots[0].lot_number + edgeLots[0].lot_number_extension + " - " + edgeLots[1].lot_number + edgeLots[1].lot_number_extension; } } $scope.showMoreLots = function(offset, limit, isPastLots){ var newOffsetAndLimit = $scope.sanitizedOffsetAndLimitForGaps($scope.lots.result_page, offset, limit); offset = newOffsetAndLimit.offset; limit = newOffsetAndLimit.limit; if (offset >= 0 && limit > 0){ var promise = $http.get(viewVars.endpoints.lotsAjax + $scope.liveLot.item.auction.row_id, {params: {limit: limit, offset: offset}}).success(function(data){ $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, data.result_page); $scope.lots.result_page = $scope.syncAllLots($scope.lots.result_page); }); } if (viewVars.features.enlargeThumbnailImagesPopup) { $rootScope.popoverThumbnailLots(3000, offset); } } $scope.openLotReview = function(lotId){ $scope.setToAllLots(); var lotIndex = $scope.lotIdToIndex(lotId); var url = ""; url = viewVars.endpoints.lotAjax + $scope.model.currentViewLots[lotIndex].row_id; $scope.spincarUrl = ''; var promise = $http.get(url).success(function(data){ $scope.model.lotDetailIndex = lotIndex; $scope.model.lotDetail = $.extend($scope.model.currentViewLots[lotIndex], $scope.WMService.afterLot(data.response)); $scope.lots.result_page = $scope.syncLots($scope.lots.result_page, [$scope.model.lotDetail]); $scope.model.lotDetailImageIndex = 0; $scope.reviewLotVisible = true; }); $scope.model.currentLotView = 'current'; promise.error(WMService.handleError); } $scope.closeLotReview = function(){ $scope.model.currentLotView = null; $scope.reviewLotVisible = false; $scope.viewCurrentLotDetail(); } $scope.scrollToSalesNotice = function(){ $('.main-area-content').animate({ scrollTop:$('.sales-room-notice').offset().top },2000); } $scope.isLiveLotStateMessage = function(){ return (typeof $scope.liveLot.state != 'undefined' && typeof $scope.liveLot.state.message != 'undefined' && $scope.liveLot.state.message && $scope.liveLot.state.message.trim() && (viewVars.currentRouteName === 'live-auction' ? !$scope.isClerkMessage : true)); } $scope.lotNumberVisible = function(){ return (!(LotService.isStarting($scope.currentLotDetail, $scope.liveLot.state) && !LiveLotViewState.isPendingBid($scope.liveLot, $scope.currentLotDetail, $scope.model.connected, $scope.model.bidSent) && !LotService.isHighestBidder($scope.liveLot.state)) && !(LiveLotViewState.isPendingBid($scope.liveLot, $scope.currentLotDetail, $scope.model.connected, $scope.model.bidSent)) && !($scope.isLiveLotStateMessage()) && !(LiveLotViewState.isPassed($scope.liveLot, $scope.currentLotDetail, $scope.model.connected)) && !($scope.model.localStateMessage)) || $scope.reviewLotVisible || LiveLotViewState.isWinning($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) ; } $scope.hasLotsToLoad = function() { return $scope.lots.hasOwnProperty('query_info') && $scope.lots.query_info.hasOwnProperty('next_page') && $scope.lots.query_info.next_page; } // Add watch on the lots property so that the strip data can be updated $scope.$watch('lots.result_page', function(newValue) { if (newValue && typeof newValue === 'object') { $scope.upcomingLotsStrip = newValue.filter(function (lot) { return !$scope.isPlaceholderLot(lot); }); } }); $scope.isPlaceholderLot = function(lot){ return !lot.hasOwnProperty('title'); } // Given a lot, determines if it is a placeholder edge lot. A placeholder lot is basically one that doesn't have all fields such as a title field // (See isPlaceholderLot() for the definition). // So, for example, you may have lots such as: // [1p, // 2p, // 3p, // 4, // 5, // 6, // 7p, // 8p, // 9, // 10p, // 11, // 12p, // 13p] // where p represents a placeholder lot. // Now, 1p to 3p is considered a gap. Within this gap, 1p is the top edge of this gap. And so 1p is considered a top edge placeholder lot. // Similarly, 3p is considered a bottom edge placeholder lot. $scope.isEdgePlaceholderLot = function(lot, edgeType){ // edgeType 1 for top edge, -1 for bottom, 0 for both // Before we do any computations, let's ensure the lot is a placeholder lot. if (!$scope.isPlaceholderLot(lot)){ return false; } // Stores the start and end indices of $scope.lots.result_page. The 0th lot will be considered an edge placeholder lot if edgeType == 1 or 0 and the last lot // will be considered an edge placeholder lot if edgeType == -1 or 0. var globalEdges = []; var isLocalEdge = false; var isTopLocalEdge; var isBottomLocalEdge; var currIndex = $scope.lotIdToIndex(lot.row_id); if (edgeType == 1 || edgeType == 0){ globalEdges.push(0); // Make sure the previous lot is not a placeholder, in order for the current lot to be considered a top edge. isTopLocalEdge = currIndex-1 >= 0 && !$scope.isPlaceholderLot($scope.lots.result_page[currIndex-1]); } if (edgeType == -1 || edgeType == 0){ globalEdges.push($scope.lots.result_page.length - 1); // Make sure the next lot is not a placeholder, in order for the current lot to be considered a bottom edge. isBottomLocalEdge = currIndex + 1 < $scope.lots.result_page.length && !$scope.isPlaceholderLot($scope.lots.result_page[currIndex+1]); } if (edgeType == -1){ isLocalEdge = isBottomLocalEdge; } else if (edgeType == 1){ isLocalEdge = isTopLocalEdge; } else { isLocalEdge = isBottomLocalEdge || isTopLocalEdge; } return globalEdges.indexOf(currIndex) > -1 || isLocalEdge; } $scope.fullLotListScrollToCurrentLot = function(){ if ($scope.model.liveLotListTab == 'upcoming'){ var lotsStrip = $($scope.lotsStripSelector); var newScrollTop = $scope.fullLotListCurrentLotScrollTop(); $($scope.lotsStripSelector).animate({ scrollTop: newScrollTop }, 1000); } else { if (!viewVars.features.disableAutoSwitchTab) { $scope.model.liveLotListTab = 'upcoming'; } } } $scope.fullLotListCurrentLotScrollTop = function(){ var lotsStrip = $($scope.lotsStripSelector); var current = $($scope.lotsStripSelector+" .lot-item.current"); if (current.length > 0){ return current.offset().top - lotsStrip.offset().top + lotsStrip.scrollTop(); } else { return 0; } } $scope.convertGroupToLotItem = function (group) { var lotItem = {}; lotItem._isGroup = true; lotItem.items = group.items; lotItem.auction_lot_group = (group.items[1] && group.items[1].auction_lot_group) || true; lotItem.auction = group.auction !== null ? group.auction : group.items[0].auction; lotItem.row_id = group.row_id; return lotItem; } // Call this function whenever a lot or state message is received from the WS. It is responsible for updating the current lot, specifically, the lot.status // to ensure that the bid states are updated. $scope.updateCurrentLot = function(liveLot, messageType){ var newLotId = null; if (messageType == 'lot'){ $scope.spincarUrl = ''; // If we received a lot from WS, simply use it to update the currentLotDetail, which will update the bid states. if ($scope.liveLot.item === null) { $scope.currentLotDetail = $scope.convertGroupToLotItem(liveLot); newLotId = liveLot.row_id; } else { $scope.currentLotDetail = WMService.afterLot(liveLot.item); newLotId = liveLot.item.row_id; } if (['live-auction', 'live-auction-bulletin-spincar'].includes(viewVars.currentRouteName) && viewVars.features.enableSpincar && $scope.currentLotDetail.spincar_url) { LotService.getSpincarView($scope.currentLotDetail.spincar_url, $scope.successHandlerSpincar); } $scope.isPriceGuideLoading = true; $scope.isPriceGuideLoadAgain = true; $scope.isPriceGuideError = false; $scope.isPriceGuideEmpty = 0; $scope.model.lotDescriptionTab = 'currentlot'; } else if (messageType == 'state' && liveLot.active){ // If we received a state message, manually set the current lot's status to 'bidding_open' to update the bid states. $scope.currentLotDetail.status = 'bidding_open'; newLotId = liveLot.row_id; } // If you are placing an absentee bid and you are no longer allowed to place it, then exit the edit bid mode. if (!LotViewState.isAbsenteeBiddable($scope.currentLotDetail) && LotViewState.isEditBidMode($scope.currentLotDetail)){ LotViewState.exitEditBidMode($scope.currentLotDetail); } // If we are looking at the current lot, from either the main current tab, or through a detail tab such as watching/bids, update the current lot based // on new info from the WS. Note that $scope.currentLotDetail always refers to the live auction's current lot, not the lot detail that the user is // currently browsing. // In either case, whether they are browsing a current lot through a detail tab, or whether they are looking at the current lot through the current tab, // update the $scope.model.lotDetail (which is what they are currently looking at). if ($scope.model.currentLotView == 'current' || ($scope.model.currentLotView == 'detail' && $scope.model.lotDetail.row_id == newLotId)){ $scope.viewCurrentLotDetail(); } } $scope.updateLots = function(liveLot){ //liveLot.item = WMService.afterLot(lot.item); //liveLot.auction.lots = WMService.afterLots(lot.auction.lots); if ($scope.liveLot.item && liveLot.item && $scope.liveLot.item.row_id !== liveLot.item.row_id) { $scope.lotsStripScrollToNext(); } $scope.liveLot = liveLot; // todo choice bidding, this could cause issues if ($scope.liveLot.item === null) { $scope.liveLot.item = $scope.convertGroupToLotItem($scope.liveLot); $scope.liveLot.auction = liveLot.auction !== null ? liveLot.auction : $scope.liveLot.items[0].auction; if ($scope.lots) { angular.forEach($scope.liveLot.items, function (item) { item.auction_lot_group = $scope.liveLot.item.auction_lot_group; item.auction = $scope.liveLot.auction; $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, [item]); $scope.setLotStripItems(); }); } } else { $scope.liveLot.item = WMService.afterLot($scope.liveLot.item); } if (viewVars.features.fullLotListInLiveAuction && $scope.lots){ $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, [$scope.liveLot.item]); var currentLotScrollTop = $scope.fullLotListCurrentLotScrollTop(); var lotsStripScrollTop = $($scope.lotsStripSelector).scrollTop(); // Auto scroll lots if the current lot is in view if (lotsStripScrollTop <= currentLotScrollTop && lotsStripScrollTop+$($scope.lotsStripSelector).height() >= currentLotScrollTop){ $timeout($scope.fullLotListScrollToCurrentLot); } } var currentLotLoaded = false; var lotsListLoaded = false; $scope.updateCurrentLot(liveLot, 'lot'); currentLotLoaded = true; if (!$scope.model.initialized && lotsListLoaded){ $scope.model.initialized = true; //$scope.initVideo(); } if (viewVars.currentRouteName == 'live-auction-bulletin'){ var lotLimit = 20; if($scope.lastLotIndex() == null || $scope.isLotsRefreshPending(lotLimit) || $scope.currentLotIndex() == null || $scope.currentLotIndex() > 0){ var offset = liveLot.item.query_offset; offset = Math.max(0, offset-10); // Get 10 previous lots for the bulletin. var promise = $http.get(viewVars.endpoints.lotsAjax + liveLot.item.auction.row_id, {params: {limit: lotLimit, offset: offset}}).success(function(data){ //$scope.lots = data; $scope.lots = data; $scope.lots.result_page = WMService.afterLots($scope.lots.result_page); if (!$scope.model.initialized && currentLotLoaded){ $scope.model.initialized = true; } var index = $scope.currentLotIndex(); if (index > 0) { $scope.prevLot = $scope.lots.result_page[index - 1] || $scope.lots.result_page[index]; } }); promise.error(WMService.silentHandleError); } } else { var lotLimit = 50; // Query this many lots. // Requery list of lots when there are less than these many lots left. // You will want a threshold that sufficiently occupies the lots strip. // The current lot + lots strip = 9 lots. We make the lotLimitThreshold=10 // here in order to avoid the UI lag that would occur when loading the 10th // lot into the lots strip as a result of the lot list ajax call. var lotLimitThreshold = 10; if(viewVars.featureVersions.liveAuction == 2){ //since on the live auction v2 screen we have 24 lots across the top in the lotstrip we need to do 26 to avoid interface lag lotLimitThreshold = 26; } if($scope.lastLotIndex() == null || $scope.isLotsRefreshPending(lotLimitThreshold) || $scope.currentLotIndex() == null){ var offset = liveLot.item.query_offset; if ($scope.isGroupedLot() === true) { offset = liveLot.items[0].query_offset; } if(viewVars.featureVersions.liveAuction == 2){ lotLimitThreshold = 20; offset = Math.max(0, offset-10); } var promise = $http.get(viewVars.endpoints.lotsAjax + liveLot.item.auction.row_id, {params: {limit: lotLimit, offset: offset}}).success(function(data){ if (viewVars.features.fullLotListInLiveAuction && $scope.model.initialized){ $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, data.result_page); } else { $scope.lots = data; $scope.lots.result_page = WMService.afterLots($scope.lots.result_page); // Update the strip with the latest result page data $scope.setLotStripItems(); } lotsListLoaded = true; if (!$scope.model.initialized && currentLotLoaded){ if (viewVars.features.fullLotListInLiveAuction){ var allLotsPromise = $http.get(viewVars.endpoints.lotsAjax + liveLot.item.auction.row_id, {params: {limit: 10000, offset: 0, fieldset: 'lot-number'}}).success(function(allLots){ $scope.lots.result_page = $scope.replaceLots(allLots.result_page, $scope.lots.result_page); // Sync the current lot with the lot in the global lot list so that clicking on the star icon in the list will also star the lot // detail. $scope.lots.result_page = $scope.syncAllLots($scope.lots.result_page); $scope.model.initialized = true; }); } else { $scope.lots.result_page = $scope.syncAllLots($scope.lots.result_page); $scope.model.initialized = true; } // This logic is added for when we get socket connection first and get all lots are not loaded then lot stripe needs to recall for active lots if ($scope.isGroupedLot() && $scope.liveLot.item && $scope.lots) { angular.forEach($scope.liveLot.items, function (item) { item.auction_lot_group = $scope.liveLot.item.auction_lot_group; item.auction = $scope.liveLot.auction; $scope.lots.result_page = $scope.replaceLots($scope.lots.result_page, [item]); }); $scope.setLotStripItems(); } //$scope.initVideo(); } }); promise.error(WMService.silentHandleError); }else{ // Ensure that the current lot is the first in the lots strip, only for non-fullLotListInLiveAuction because fullLotListInLiveAuction clients displays all lots. // Also do not do this for the liveAuctionV2 as the lotstrip extends across the entire screen if(!viewVars.features.fullLotListInLiveAuction && !viewVars.featureVersions.liveAuction == 2 && $scope.currentLotIndex() - 1 >= 0){ $scope.lots.result_page.splice(0, $scope.currentLotIndex()); } if (viewVars.features.fullLotListInLiveAuction) { $scope.showMoreLots($scope.lotIdToIndex($scope.liveLot.item.row_id),50); } } } }; $scope.heartbeat = function(){ if ($scope.model.heartbeatPromise){ $timeout.cancel($scope.model.heartbeatPromise); } $scope.model.heartbeatPromise = $timeout($scope.heartAttack, 6000); if (typeof(viewVars.hasJWPlayer) != 'undefined' && viewVars.hasJWPlayer && $scope.videoVisible() && $scope.hasOwnProperty('jwplayer') && $scope.jwplayer.getState() == 'IDLE'){ $scope.jwplayer.play(true); } }; $scope.heartAttack = function(){ $scope.model.connected = false; setTimeout(() => { if (!$scope.model.connected) { $scope.model.hasHeartAttack = true; } }, 3000); }; /** * isAblyEnabled function is used to check whether the Ably is set or not * If the Ably flag is set to true and choice bidding is disabled then the function will return true * @return {boolean} */ $scope.isAblyEnabled = function(){ return viewVars.features.eventMessagingService && $rootScope.ablyConnectedForLive && viewVars.joinAuction.hasOwnProperty('response') && viewVars.joinAuction.response.hasOwnProperty('auction') && viewVars.joinAuction.response.auction.bidders_choice_enabled === false; }; $scope.initAbly = function($scope, url) { return new Promise((resolve, reject) => { const ably = new Ably.Realtime({ authUrl: url, token: $scope.ablyToken, transportParams: { heartbeatInterval: 5000 } }); ably.connection.on('connected', function() { console.log('✓ Connected to Ably.'); $rootScope.ably = ably; $rootScope.ablyConnectedForLive = true; if ($rootScope.ablyActive === false) { let disconnectMinutes = moment.utc(moment().utc()).diff($scope.disconnectedTimestamp, 'minutes'); if (disconnectMinutes >= 2) { // Here we are calling the join room API when ably disconnected to load the latest lot data $scope.loadLotMessageFromJoinRoomAPI($scope, true); $timeout(function() { $scope.updateLotStripItems($scope); }); } } resolve(ably); }); ably.connection.on('disconnected', function() { console.log('✗ Disconnected from Ably.'); $rootScope.ablyConnectedForLive = false; if ($rootScope.ablyActive) { $rootScope.ablyActive = false; $scope.disconnectedTimestamp = moment().utc().toISOString(); } $scope.$apply(); }); ably.connection.on('suspended', function() { console.log('✗ Suspended from Ably.'); $rootScope.ablyConnectedForLive = false; if (!$scope.$$phase) { $scope.$apply(); } }); ably.connection.on('failed', function() { console.log('✗ Connection failed from Ably.'); $rootScope.ablyConnectedForLive = false; if (!$scope.$$phase) { $scope.$apply(); } }); }); } $scope.initAblyChannels = function(channel) { return $rootScope.ably.channels.get(channel); } $scope.subscribeAblyChannels = function($scope, viewVars) { let auctionJoinRoom = viewVars.joinAuction.hasOwnProperty('response') ? viewVars.joinAuction.response : ''; let channelList = auctionJoinRoom.hasOwnProperty('event_messaging_service') && auctionJoinRoom.event_messaging_service.hasOwnProperty('channels') ? auctionJoinRoom.event_messaging_service.channels : []; let rewindCount = auctionJoinRoom.hasOwnProperty('event_messaging_service') && auctionJoinRoom.event_messaging_service.hasOwnProperty('rewind_count') ? auctionJoinRoom.event_messaging_service.rewind_count : 50; if (channelList.length > 0) { let channelName; channelList.forEach(function (channel) { channelName = channel; var channelSubscribe = $scope.initAblyChannels(channelName); channelSubscribe.subscribe(function (message) { ablyChannelListener($scope, message); }); }); } } var ablyChannelListener = function($scope, message) { $scope.executeMessage(message); } $scope.loadLotMessageFromJoinRoomAPI = function ($scope, apiCall) { let messages = {}; if (apiCall && $rootScope.ablyActive === false) { var promise = $http.get(viewVars.endpoints.getLiveAuctionJoinRoomData + viewVars.joinAuction.response.auction.row_id); promise.success(function(data){ messages = data.hasOwnProperty('response') && data.response.hasOwnProperty('event_messaging_service') ? data.response.event_messaging_service : []; if (messages.hasOwnProperty('events') && messages.events.length > 0) { if ($rootScope.ablyActive === false) { $rootScope.ablyActive = true; } // setting timeout here to update scope data for the lot $timeout(function() { $scope.setLotEventData(messages); }); } }); promise.error(WMService.handleError); } else if ($rootScope.ablyActive === true && !apiCall) { let auctionJoinRoom = viewVars.joinAuction.hasOwnProperty('response') ? viewVars.joinAuction.response : ''; messages = auctionJoinRoom.hasOwnProperty('event_messaging_service') && auctionJoinRoom.event_messaging_service ? auctionJoinRoom.event_messaging_service : []; if (messages.hasOwnProperty('events') && messages.events.length > 0) { $scope.setLotEventData(messages); } } } $scope.setLotEventData = function(messages) { let MessageData = {}; messages.events.forEach(function(item) { MessageData.data = item; $scope.executeMessage(MessageData); }) } $scope.connectAbly = function($scope) { // here we are connecting to ably new Promise((resolve, reject) => { $scope.ably = $scope.initAbly($scope, viewVars.endpoints.generateAblyToken); resolve($scope.ably); }).then((connection) => { // here we are connecting to ably channel and subscribe events $scope.subscribeAblyChannels($scope, viewVars, false); //load the current lot data from join room API $scope.loadLotMessageFromJoinRoomAPI($scope, false); }).catch((error) => { console.log('Ably Error: ' + error); $rootScope.ablyConnectedForLive = false; $scope.model.initializationFailed = true; }); } /** * updateLotStripItems function is used to update the lot strip items * if the Ably disconnected and connected again, and * if the timestamp difference is more than 2 min * then it will call the API to update the lot strip items data */ $scope.updateLotStripItems = function($scope) { var lotLimit = 50; // Query this many lots. var offset = $scope.liveLot.item.query_offset; if ($scope.isGroupedLot() === true) { offset = liveLot.items[0].query_offset; } if (viewVars.featureVersions.liveAuction == 2) { offset = Math.max(0, offset-10); } var promise = $http.get(viewVars.endpoints.lotsAjax + $scope.liveLot.item.auction.row_id, {params: {limit: lotLimit, offset: offset} }).success(function(data) { $scope.lots = data; $scope.lots.result_page = WMService.afterLots($scope.lots.result_page); // Update the strip with the latest result page data $scope.setLotStripItems(); }); promise.error(WMService.silentHandleError); } $scope.newConnection = function(ws_url){ $scope.connection = new WebSocket(ws_url); // When the connection is open, send some data to the server $scope.connection.onopen = function () { $scope.model.hasHeartAttack = false; $scope.model.reconnectAttemptCount = 0; //$scope.connection.send('Ping'); // Send the message 'Ping' to the server }; // Log errors $scope.connection.onerror = function (error) { console.log('WebSocket Error: ' + error); $scope.model.initializationFailed = true; }; $scope.connection.onmessage = $scope.executeMessage; $scope.connection.onclose = function(){ $scope.$apply(function(){ $scope.model.connected = false; if ($scope.model.hasHeartAttack === false) { setTimeout(() => { $scope.model.hasHeartAttack = true; }, 3000); } }); }; }; $scope.init = function(){ $scope.model.firstLoad = true; $scope.model.confirmationGroupPopup = false; $scope.model.isRetina = (window.devicePixelRatio > 1 || (window.matchMedia && window.matchMedia("(-webkit-min-device-pixel-ratio: 1.5),(-moz-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)").matches)); if ($scope.isAblyEnabled()) { $rootScope.ablyActive = true; $scope.connectAbly($scope); } else { $scope.newConnection(viewVars.joinAuction.response.ws_url); } if ((viewVars.features.bidderRegWithAccountCreation || viewVars.features.globalBidderRegistration || viewVars.me) && viewVars.currentRouteName != 'live-auction-bulletin') { // we do not want to push to the user if they are not logged in if (viewVars.joinAuction.response.user !== null) { // todo rather than the push here we need to check the auction registrations and replace the item if it exists. // if not push this var auction_index = viewVars.me.auction_registrations.findIndex(function(auction_registration, i){ return auction_registration.auction_id === viewVars.auctionId; }); if (auction_index >= 0) { $.extend(viewVars.me.auction_registrations[auction_index], viewVars.joinAuction.response.user.auction_registration); } else { viewVars.me.auction_registrations.push(viewVars.joinAuction.response.user.auction_registration); } } } console.log(viewVars.joinAuction.response); }; $scope.closeLotSelectionModal = function () { $scope.groupVars.selectedLots = []; $scope.groupVars.selectedLotsConfirmation = false; $scope.groupVars.endTime = null; $scope.groupVars.isOffer = false; $scope.groupVars.isSubjectTo = false; $scope.groupVars.confirmedSelectedLots = []; $('#choice-bidding-lot-selection').modal('hide'); } $scope.showLotSelectionModal = function () { $('#choice-bidding-lot-selection').on('shown.bs.modal', function () { if(!$scope.$$phase){ $scope.$apply(); } }); $('#choice-bidding-lot-selection').modal({backdrop: 'static', keyboard: false}); } $scope.closeTimeoutModal = function () { console.log('closeTimeoutModal') $('#choice-bidding-timeout').modal('hide'); } $scope.showTimeoutModal = function () { $('#choice-bidding-timeout').on('shown.bs.modal', function () { if(!$scope.$$phase){ $scope.$apply(); } }).on('hidden.bs.modal', function () { $scope.groupVars.selectionTimesUp = false; }); $('#choice-bidding-timeout').modal({backdrop: 'static', keyboard: false}); } $scope.initVideo = function(playerId){ if ($scope.hasLiveVideo() && !$rootScope.isWrapperApp()){ if (viewVars.brand === 'bonhams') { $scope.nanocosmosConnect(playerId); } else { if (typeof(viewVars.hasJWPlayer) != 'undefined' && viewVars.hasJWPlayer){ $scope.jwPlayerConnect(playerId); } else { if ($scope.isMobile) { var touchDetected = false; document.body.addEventListener('touchstart', function() { if (!touchDetected) { touchDetected = true; $scope.openTokConnect(playerId); } }); } else { $scope.openTokConnect(playerId); } } } } } /* Checks if there are less than lotLimit lots left starting from the current lot and ending at the last lot available */ $scope.isLotsRefreshPending = function(lotLimit){ var lastLotIndex = $scope.lastLotIndex(); var requiredLotIndex = $scope.currentLotIndex() + lotLimit - 1; if (lastLotIndex == null || requiredLotIndex > lastLotIndex){ var infoUnavailable = false; if ($scope.liveLot.hasOwnProperty('items') === false) { infoUnavailable = lastLotIndex == null || !$scope.liveLot || !$scope.liveLot.hasOwnProperty('item') || !$scope.liveLot.item.hasOwnProperty('query_offset') || $scope.liveLot.item.query_offset == null; } if (infoUnavailable){ return true; } var auctionLastLotIndex = lastLotIndex; var moreLotsAvailable; if ($scope.liveLot.hasOwnProperty('items') === true) { $scope.liveLot.items[0].query_offset + (lastLotIndex - $scope.currentLotIndex()); moreLotsAvailable = auctionLastLotIndex < $scope.liveLot.items[0].auction.lot_count - 1; } else { $scope.liveLot.item.query_offset + (lastLotIndex - $scope.currentLotIndex()); moreLotsAvailable = auctionLastLotIndex < $scope.liveLot.item.auction.lot_count - 1; } if (moreLotsAvailable){ return true; } } return false; } $scope.lastLotIndex = function(){ if ($scope.lots == null){ return null; } return $scope.lots.result_page.length - 1; } $scope.onOTStreamException = function (event) { $scope.model.videoStreamExists = true; $scope.model.globalvideoStreamExists = true; var settings = {insertMode: 'append'}; if (['phillips', 'swanngalleries'].indexOf(viewVars.brand) === -1 && viewVars.features.liveAuctionTheme2 == false && viewVars.features.liveAuctionTheme3 == false) { if (viewVars.currentRouteName == 'live-auction') { var width; var height; if (viewVars.featureVersions.liveAuction != 2) { if (viewVars.brand === 'wright') { width = '100%'; height = '100%'; } else if (viewVars.features.liveAuctionTheme1) { width = '100%'; height = '100%'; } else { width = 615; height = Math.round(width * 9 / 16); } } else { width = ($scope.model.isRetina) ? $('.video').width() : $('.live-auction-container-v2').width(); height = Math.round(width * 9 / 16); } settings = $.extend(true, settings, {width: width, height: height}); } if (viewVars.currentRouteName == 'live-auction-video') { settings = $.extend(true, settings, {width: $(window).width(), height: $(window).height()}); } if (viewVars.currentRouteName == 'live-auction-bulletin') { if (viewVars.brand == 'acker') { width = 380; height = 280; } settings = $.extend(true, settings, {width: width, height: height}); } } else { // This will be 16:9 aspect ratio for a 1200px minimum wide browser size. // The resulting video width would be 750px. if (viewVars.brand === 'phillips' || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme3) { settings = $.extend(true, settings, { width: "100%", height: "200px !important", fitMode: viewVars.features.liveAuctionTheme3 ? "cover" : "contain" }); } else { settings = $.extend(true, settings, {width: "100%", height: 312}); } } angular.element('#live-video').css({'height': settings.height}); }; $scope.toggleVideo = function(){ $scope.model.videoVisible = !$scope.model.videoVisible; if (viewVars.brand == 'n4-dorotheum' && $scope.model.globalvideoStreamExists){ $scope.model.videoStreamExists = true; $scope.OTsubscriber.subscribeToAudio(false); if ($scope.OTsession && $scope.OTstream){ $scope.onOTStreamCreated($scope.OTsession, $scope.OTstream); if($('.OT_root').length > 1){ $scope.model.videoStreamExists = false; $('div.OT_root:nth-of-type(1n)').remove(); $scope.OTsubscriber.setAudioVolume(0); } } else { $scope.initVideo(); } }else if ((['n4-skinnerinc'].indexOf(viewVars.brand)) !== -1 && $scope.model.globalvideoStreamExists) { $scope.model.videoStreamExists = true; if ($scope.OTsession && $scope.OTstream) { if ($scope.model.videoVisible) { angular.element('.video-toggle').removeClass("video-off"); $('.OT_root').find('.OT_video-poster').removeClass('video-thumb'); } else { angular.element('.video-toggle').addClass("video-off"); $('.OT_root').find('.OT_video-poster').addClass('video-thumb'); } $scope.OTsubscriber.subscribeToVideo($scope.model.videoVisible); } else { $scope.initVideo(); } }else if(viewVars.brand != 'n4-dorotheum' && $scope.videoVisible()){ if ($scope.OTsession && $scope.OTstream){ $scope.onOTStreamCreated($scope.OTsession, $scope.OTstream); } else { $scope.initVideo(); } }else { //$scope.OTsession.unsubscribe(); if (typeof(OT) != 'undefined'){ if(viewVars.brand == 'n4-dorotheum' && !$scope.model.globalvideoStreamExists) { $scope.noStreamingMessage = $filter('translate')("Video streaming for this auction is not available"); WMService.handleError({message: $scope.noStreamingMessage}); } if(viewVars.brand == 'n4-dorotheum'){ $scope.model.videoStreamExists = false; angular.element('.live-video-wrap').css({"min-height":"0px"}); } if($scope.OTsession && $scope.OTsubscriber) $scope.OTsession.unsubscribe($scope.OTsubscriber); } else { if($scope.jwplayer) $scope.jwplayer.remove(); } } } $scope.videoVisible = function(){ return $scope.model.videoVisible; } $scope.disconnect = function(){ if ($scope.connection){ $scope.connection.close(); } } $scope.reconnect = function(){ $scope.disconnect(); $scope.newConnection(viewVars.joinAuction.response.ws_url); $scope.model.showReconnectLoader = true; $timeout(function() { $scope.model.showReconnectLoader = false; },5000); } $scope.highestBidLabel = function(){ var label = ''; if ($scope.isHighestBidder()){ if(viewVars.featureVersions.liveAuction == 2){ if (viewVars.features.showFinalBidWhenWon && LiveLotViewState.isWon($scope.liveLot, $scope.currentLotDetail, $scope.model.connected)) { label += 'Final'; } else { label += 'Current'; } }else{ if(viewVars.brand == 'phillips') label += 'Your'; else label += 'Winning'; } } else { if(viewVars.featureVersions.liveAuction == 2){ var hasBids = false; if ($scope.liveLot.bids) { for (var i = 0; i < $scope.liveLot.bids.length; i++) { if ($scope.liveLot.bids[i].active == true) { hasBids = true; break; } } } if(!hasBids){ label += "Starting"; }else{ label += 'Current'; } }else{ label += 'Current'; } } label += ' Bid'; if (viewVars.features.localStateMessageWithExclamatory) { if ($scope.messageInPaddle() && !$scope.model.localStateMessage) { label = $scope.liveLot.state.message + '!'; } else { label += ':'; } } return label; }; $scope.highestBidLabelMobile = function () { var label = ''; var hasBids = false; for(var i=0; i<$scope.liveLot.bids.length; i++){ if($scope.liveLot.bids[i].active == true){ hasBids = true; break; } } if(!hasBids){ label += "Starting at"; }else{ label += 'Current Bid'; } return label; } $scope.hasBids = function(){ for(var i=0; i<$scope.liveLot.bids.length; i++){ if($scope.liveLot.bids[i].active == true){ return true; break; } } return false; } $scope.connectionLabel = function(){ if (viewVars.featureVersions.liveAuction == 2) { if ($scope.model.connected) { connectionLabel = '(Connected)'; } else { connectionLabel = '(Disconnected)'; } } else { if ($scope.model.connected) { if( viewVars.brand == 'grays' || viewVars.brand == 'stacksbowers' ) { connectionLabel = 'Connected'; } else { connectionLabel = 'You are connected'; } } else { connectionLabel = 'Disconnected'; } } // the mobile bidding room has different states if (viewVars.currentRouteName === 'live-auction-mobile') { if ($scope.model.connected && viewVars.me !== null) { connectionLabel = '(Connected)'; } else if ($scope.model.connected && viewVars.me === null) { connectionLabel = 'You are connected to watch only'; } else { connectionLabel = '(Disconnected)'; } } return connectionLabel; }; $scope.paddleNoLabel = function() { let label = ''; if ($scope.model.connected && viewVars.me !== null && viewVars.joinAuction.response.user !== null && viewVars.joinAuction.response.user.paddle !== null) { label = viewVars.paddleLabel +' #'+viewVars.joinAuction.response.user.paddle; } return label; } $scope.toggleAudio = function() { $scope.model.isAudioEnabled = !$scope.model.isAudioEnabled; $scope.subscribeToAudio($scope.model.isAudioEnabled) } $scope.subscribeToAudio = function(onOffFlag = true) { console.log('subscribeToAudio called ', onOffFlag); $scope.OTsubscriber.subscribeToAudio(onOffFlag); } $scope.bidButtonPrefix = function(offIncrement){ $scope.isBidButton = false; if(($scope.model.bidButtonState == 'confirm' && !offIncrement) || ($scope.model.bidButtonStateOffIncrement == 'confirm' && offIncrement)){ if ((['n4-dorotheum'].indexOf(viewVars.brand) > -1 && viewVars.features.liveAuctionTheme2)) { return 'Yes, Confirm'; }else if(['hdh'].indexOf(viewVars.brand) > -1){ return 'Submit '; } return 'Confirm '; }else if(($scope.model.bidButtonState == 'bid' && !offIncrement) || ($scope.model.bidButtonStateOffIncrement == 'bid' && offIncrement)){ if (['phillips','swanngalleries'].indexOf(viewVars.brand) > -1 || (!viewVars.features.showPlaceBidTextInBiddingRoom && viewVars.features.liveAuctionTheme2)){ return 'Place Bid '; }else{ $scope.isBidButton = true; return 'Bid '; } } }; $scope.bidButtonText = function(offIncrement, liveBiddingRoomV2) { var isPercentageBiddingEnabled = WMService.isPercentageBiddingEnabled($scope.liveLot); if (typeof offIncrement === 'undefined' || isPercentageBiddingEnabled === true) { offIncrement=false; } if (typeof $scope.currentLotDetail.auction !== 'undefined' && WMService.isApprovedAuctionRegistration($scope.currentLotDetail.auction.row_id)) { var price = (isPercentageBiddingEnabled === true) ? $scope.liveLot.state.asking_price_percentage : $scope.liveLot.state.asking_price; if (offIncrement && isPercentageBiddingEnabled === false) { price = $scope.model.offIncrementBids.length > 0 ? $scope.model.offIncrementBids[0] : 0; } if (typeof $scope.currentLotDetail.auction !== 'undefined' && WMService.isApprovedAuctionRegistration($scope.currentLotDetail.auction.row_id)) { var price = $scope.liveLot.state.asking_price; if (($scope.currentLotDetail.auction.bidders_choice_enabled || viewVars.features.enableAskingPriceIncrementButtons) && price == 0) { $scope.model.bidButtonState = 'disable'; return ' Please wait...'; } if(offIncrement){ price = $scope.model.offIncrementBids.length > 0 ? $scope.model.offIncrementBids[0] : 0; } if (isPercentageBiddingEnabled === true) { price = $filter('price')(price, $scope.liveLot.item.auction.currency_code, 1, $scope.liveLot); } else { price = $filter('price')(price, $scope.liveLot.item.auction.currency_code); } //todo: once we have a locale that needs the price first we will want to change how this functions. var returnString = $scope.bidButtonPrefix(offIncrement); returnString = $filter('translate')(returnString); if (viewVars.brand === 'stacksbowers' && isPercentageBiddingEnabled === true) { returnString += " "; } else { if ((offIncrement && $scope.model.bidButtonStateOffIncrement == 'confirm') || (typeof($scope.model.offIncrementBids) != 'undefined' && $scope.model.offIncrementBids.length>0 && $scope.model.bidButtonStateOffIncrement == 'confirm' && !offIncrement)) { returnString+="
"; }else if(typeof($scope.model.offIncrementBids) != 'undefined' && $scope.model.offIncrementBids.length>0 && $scope.model.canOffIncrementBid){ returnString+=((viewVars.features.liveAuctionTheme1 || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5) ? ($scope.liveLot.state.asking_price > 999999 ? "
" : " ") : "
"); }else{ // Add
tag only if the asking price is higher than 999999 if ($scope.liveLot.state.asking_price > 999999 && (viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5)) { returnString += "
"; } else { returnString+=" "; } } } if (viewVars.brand != "n4-dorotheum" || ($scope.model.bidButtonState == 'bid')) { if ($scope.isBidButton && viewVars.locale === 'de-DE') { returnString = price + " " + $filter('translate')("place bid"); } else { returnString += price; } } if (viewVars.brand === "n4-dorotheum") { if ($scope.model.bidButtonState == "confirm") { $scope.placeBidText = $filter('translate')("Place %price% Bid?").replace("%price%",price); } else { $scope.placeBidText = null; } } if (offIncrement && $scope.model.bidButtonStateOffIncrement == 'bid' && isPercentageBiddingEnabled === false) { returnString += "
" + $filter('translate')('(Half Increment)') + ""; } if (isPercentageBiddingEnabled === true) { returnString += '
(' + $filter('price')($scope.liveLot.state.asking_price) + ')'; } if ($scope.liveLot.times_the_money === true && !liveBiddingRoomV2) { returnString += "
" + $filter('translate')('Times The Money') + ""; } if (offIncrement && $scope.model.bidPlaceStateOffIncrement == true) { returnString = ''+returnString; } if (!offIncrement && $scope.model.bidPlaceState == true) { returnString = ''+returnString; } return returnString; }else if(viewVars.me == null){ return $filter('translate')('Login To Bid'); }else{ return $filter('translate')('Register To Bid'); } } else if (viewVars.me == null) { return $filter('translate')('Login To Bid'); } else { return $filter('translate')('Register To Bid'); } } $scope.placeMobileBid = function (offIncrement) { if ($scope.model.slideToBid.value < 95) { $scope.model.slideToBid.value = 0; } else { $scope.clickBidButton(offIncrement); } } $scope.clickBidButton = function(offIncrement){ if (viewVars.brand === 'n4-dorotheum' && !viewVars.me) { var dorotheumLoginURL = viewVars.clientInfo.tenantUrl; if (!viewVars.clientInfo.tenantUrl.startsWith('http')){ dorotheumLoginURL = '//' + dorotheumLoginURL; } window.location.href = dorotheumLoginURL + "/" + $rootScope.getBaseLocale(viewVars.locale) + "/c/login-75/?redirect_url=" + encodeURI(window.location.href); return; } if(typeof offIncrement=="undefined") offIncrement=false; /** * For Phillips Brand Only: Auth flow code * phillipsAuthLogin will be called when enablePhillipsAuthSdkWeb flag is enabled from backend & user is not loggedIn * */ if(!viewVars.me && viewVars.enablePhillipsAuthSdkWeb) { return $rootScope.phillipsAuthLogin(); } $scope.isOffIncrementBidButtonClick = false; if (offIncrement) { $scope.isOffIncrementBidButtonClick = true; } if (viewVars.features.noConfirmLiveBid === true || (viewVars.brand !== 'n4-dorotheum' && viewVars.currentRouteName === 'live-auction-mobile')){ if(offIncrement){ $scope.makeBid($scope.model.offIncrementBids[0]); }else{ $scope.makeBid(); } }else { if (((!offIncrement && $scope.model.bidButtonState == 'confirm') || (offIncrement == true && $scope.model.bidButtonStateOffIncrement == 'confirm'))){ if($scope.WMService.hasGoogleAnalytics()){ //LiveAuction -> LiveBid -> ConfirmBid -> (value of bid) sendGoogleAnalyticsData('send', 'event', 'LiveAuction', 'LiveBid', 'ConfirmBid', $scope.liveLot.state.asking_price); } if(offIncrement){ $scope.makeBid($scope.model.offIncrementBids[0]); }else{ $scope.makeBid(); } $scope.model.localStateMessage = null; }else if(($scope.model.bidButtonState == 'bid' && offIncrement==false) || ($scope.model.bidButtonStateOffIncrement == 'bid' && offIncrement==true)){ if (!WMService.isApprovedAuctionRegistration($scope.currentLotDetail.auction.row_id)) { $scope.makeBid(); return; } if(viewVars.featureVersions.liveAuction == 2 && viewVars.brand !== 'n4-dorotheum') { $scope.setLocalStateMessage($filter('translate')('By bidding, you agree to pay the price shown')); } if($scope.WMService.hasGoogleAnalytics()){ //LiveAuction -> LiveBid -> PlaceBid -> 1 sendGoogleAnalyticsData('send', 'event', 'LiveAuction', 'LiveBid', 'PlaceBid', 1); } if(offIncrement){ $scope.model.bidButtonStateOffIncrement = 'confirm'; }else{ $scope.model.bidButtonState = 'confirm'; } } } }; $scope.makeBid = function(amount){ if ($scope.isOffIncrementBidButtonClick) { $scope.model.bidPlaceStateOffIncrement = true; } else { $scope.model.bidPlaceState = true; } // If the registration is not approved, it means they cannot bid, meaning the bid button will say login or // register, in which case we should send an amount of null that will make the backend redirect us properly // to the either the bidder registration or login page based on what's necessary. var isApproved = WMService.isApprovedAuctionRegistration($scope.currentLotDetail.auction.row_id); if (!isApproved){ // If they're not approved (according to the current browser state), send a bid of null just in // case the backend's state is different (for example, if they logged in in another tab). // This prevents surprises where they click on "Login to Bid Live" but end up making a bid due to // the backend's state being different. amount = null; } if (!$scope.isHighestBidder()){ if ($rootScope.hasOwnProperty('ajaxErrorMessageClasses') && $rootScope.ajaxErrorMessageClasses.indexOf('bid-error') > -1){ $rootScope.ajaxErrorMessage = null; // Clear the red alert bar if it contained a previous bid error. } var bidData = {}; if (typeof amount == 'undefined'){ if (WMService.isPercentageBiddingEnabled($scope.liveLot) === true) { bidData = { bid_percentage: $scope.liveLot.state.asking_price_percentage }; } else { bidData = { bid: $scope.liveLot.state.asking_price }; } } else { if (WMService.isPercentageBiddingEnabled($scope.liveLot) === true) { bidData = { bid_percentage: amount }; } else { bidData = { bid: amount }; } } bidData.groupedLot = $scope.isGroupedLot(); var promise = $http.post( viewVars.endpoints.liveAuctionBid + $scope.currentLotDetail.auction.row_id + '/' + $scope.currentLotDetail.row_id, bidData ); if (isApproved) { $scope.model.bidSent = true; } $scope.model.bidSentShowStatus = null; // If the response takes 500 secs or longer, show an intermediate "BID SENT" state $timeout(function(){ if ($scope.model.bidSentShowStatus == null){ if (isApproved) { $scope.model.bidSentShowStatus = true; } } },500); promise.success(function(data){ if ($scope.isOffIncrementBidButtonClick) { $scope.model.bidPlaceStateOffIncrement = false; } else { $scope.model.bidPlaceState = false; } if (viewVars.googleTagManager && viewVars.brand == 'bonhams') { $rootScope.googleTagMangerbidOnLotFn($scope.currentLotDetail , WMService.isPercentageBiddingEnabled($scope.liveLot) === true ? data.response.bid_percentage : data.response.bid) } $scope.model.bidSent = false; $scope.model.bidSentShowStatus = false; // bid was sent so lets set the slideToBid value back to 0 $scope.model.slideToBid.value = 0; if (data.response.hasOwnProperty('pending') && data.response.pending){ $scope.updatePendingBid(data.response); } }); $scope.model.bidButtonState = 'bid'; $scope.model.bidButtonStateOffIncrement = 'bid'; promise.error(function(data){ if ($scope.isOffIncrementBidButtonClick) { $scope.model.bidPlaceStateOffIncrement = false; } else { $scope.model.bidPlaceState = false; } $scope.model.bidSent = false; $scope.model.bidSentShowStatus = false; $scope.model.slideToBid.value = 0; WMService.handleError(data); }); } /*promise.success(function(){ });*/ /*$.ajax({ method: 'post', url: '/sales/bid/' + $scope.liveLot.state.row_id, data: { bid: $scope.liveLot.state.asking_price } });*/ }; $scope.auctionStatusClasses = function(){ var lot = $scope.currentLotDetail; return { now: $scope.LotService.isNow(lot, $scope.liveLot.state), paused: LiveLotViewState.isPaused($scope.liveLot, lot, $scope.model.connected), closed: $scope.LotService.isClosed(lot, $scope.liveLot.state), preparing: LiveLotViewState.isPreparing($scope.liveLot, lot, $scope.model.connected), }; }; $scope.auctionStatusLabel = function(){ var lot = $scope.currentLotDetail; if ($scope.LotService.isNow(lot, $scope.liveLot.state)){ return (viewVars.brand == 'stacksbowers') ? 'Now' : 'Now!'; } else if ($scope.LotService.isPaused(lot, $scope.liveLot.state)){ return 'Paused'; } if(!viewVars.featureVersions.liveAuction == 2){ if ($scope.LotService.isClosed(lot, $scope.liveLot.state)){ return 'Closed'; } }else{ if ($scope.LotService.isClosed(lot, $scope.liveLot.state) && !LotService.isPassed($scope.liveLot.item)){ return 'Closed'; }else if ($scope.LotService.isClosed(lot, $scope.liveLot.state) && LotService.isPassed($scope.liveLot.item)){ return 'Passed'; } } }; $scope.lotIdToIndex = function(lotId){ var i; for (i = 0; i < $scope.lots.result_page.length; i++) { if (lotId == $scope.lots.result_page[i].row_id){ return i; } }; return null; }; $scope.viewCurrentLotDetail = function(){ if (!(viewVars.brand === 'n4-dorotheum' && $scope.reviewLotVisible)) { $scope.model.lotDetail = $scope.currentLotDetail; $scope.model.currentLotView = 'current'; $scope.model.currentTab = 'currentLot'; } }; $scope.clickLotDetail = function(lotIndex, elementClicked){ if (typeof elementClicked != "undefined"){ var element = $(elementClicked.target); if (element.is(".watch-ribbon, .watch-icon, .watch-icon-wrap, .place-bid-box, input, select, button, a, .bid-suggestion, .bid-amount") || element.parents(".watch-ribbon, .watch-icon-wrap, .phillips-watch-icon-wrap, .theme-2-watch-icon-wrap").length){ return false; } $scope.model.currentLotClicked = lotIndex; } if ($scope.isGroupedLot() === true && viewVars.currentRouteName !== 'live-auction-mobile') { $scope.model.groupView = 'detail'; } return $scope.viewLotDetail(lotIndex); } /** * The purpose of this function is to go directly to the lot rather than show it in the lot list inside of the bidding room * this was needed because we filter the list of lots and cannot show the correct lot * @param {String} lotId the lot id * @param {Obj} elementClicked the click event */ $scope.clickLotDetailFromSearchInV1 = function (lot, elementClicked) { var url = ''; if (lot.hasOwnProperty('_slug')) { url = WMService.generateUrl('lotDetailSlug', { lotId: lot.row_id, slug: lot._slug }); } else { url = WMService.generateUrl('lotDetail', { lotId: lot.row_id }); } window.open(url); } $scope.clickPrevNext = function(lotIndex){ $scope.model.currentLotClicked = lotIndex; return $scope.viewLotDetail(lotIndex); } $scope.getLotIndexFromId = function (lotId) { for (var i = 0; i < $scope.model.currentViewLots.length; i++) { if ($scope.model.currentViewLots[i].row_id === lotId) { return i; } } return null; } $scope.successHandlerSpincar = function(spincarURL) { $scope.spincarUrl = spincarURL; } $scope.viewLotDetail = function (lotIndex, viewLotDetailOnSamePage) { //this code should stop queueing calls when the server is sluggish if (viewVars.currentRouteName !== 'live-auction-mobile' || viewLotDetailOnSamePage) { if(lotIndex != $scope.model.currentLotClicked){ return; } var url = ""; url = viewVars.endpoints.lotAjax + $scope.model.currentViewLots[lotIndex].row_id; $scope.spincarUrl = ''; var promise = $http.get(url).success(function(data){ if (viewVars.currentRouteName === 'live-auction' && viewVars.features.enableSpincar && data.response.spincar_url) { LotService.getSpincarView(data.response.spincar_url, $scope.successHandlerSpincar); } $scope.model.lotDetailIndex = lotIndex; // Extend the model.currentViewLots so that any changes made inside the view lot detail section (such as watching it), will // be reflected in lots list view (such as displaying a watch ribbon). $scope.model.lotDetail = $.extend($scope.model.currentViewLots[lotIndex], $scope.WMService.afterLot(data.response)); // Reset the bid mode in case the lot previously has any value for the _bidMode if ($scope.model.lotDetail._bidMode && viewVars.me) { // This method will reset the bid box so that the fresh bid box state will be served to the user LotViewState.exitEditBidMode($scope.model.lotDetail); } // Sync the global lot list as well. $scope.lots.result_page = $scope.syncLots($scope.lots.result_page, [$scope.model.lotDetail]); $scope.model.lotDetailImageIndex = 0; $scope.model.currentLotView = 'detail'; $scope.model.currentLotClicked = -1; !viewVars.features.liveAuctionTheme3 || LotService.initAbsenteeBidBox($scope.model.lotDetail); }); promise.error(WMService.handleError); } else { // we need to redirect to the lot number var url = '' selectedLot = $scope.model.currentViewLots[lotIndex]; if (selectedLot.hasOwnProperty('_slug') && (selectedLot._slug !== null && selectedLot._slug !== '')) { url = WMService.generateUrl('lotDetailSlug', {'lotId': selectedLot.row_id, 'slug': selectedLot._slug}); } else { url = WMService.generateUrl('lotDetail', {'lotId': selectedLot.row_id}); } window.open(url, '_blank'); } }; $scope.viewInternalLotList = function(callback){ var promise = $http.get(viewVars.endpoints.lotsAjax + $scope.liveLot.item.auction.row_id, {params: {limit: 100, offset: $scope.liveLot.item.query_offset}}).success(function(data){ $scope.lots = data; $scope.lots.result_page = WMService.afterLots($scope.lots.result_page); callback(); }); promise.error(WMService.silentHandleError); } $scope.viewWatchedLots = function(){ $scope.viewInternalLotList(function(){ $scope.exitLotDetail(); $scope.model.currentLotsView = 'watched'; $scope.model.lotsSearch = ''; var lots = $scope.lots.result_page; var watchedLots = []; var j = 0; for (var i=0; i parseInt($scope.filterNumPages())){ $scope.model.lotListPage = $scope.filterNumPages() } }); $scope.filterNumPages = function(){ if(typeof $scope.lotList[$scope.model.currentTab] == 'undefined'){ return 1; } if (viewVars.brand == 'phillips' || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5){ if($scope.lotList[$scope.model.currentTab].list){ return Math.ceil(parseFloat($scope.lotList[$scope.model.currentTab].list.query_info.total_num_results) / parseFloat($scope.lotList[$scope.model.currentTab].list.query_info.page_size)); } } else { if($scope.lotList[$scope.model.currentTab].list){ //console.log($scope.lotList[$scope.model.currentTab].list.query_info.total_num_results) //console.log('pages ' +parseFloat($scope.lotList[$scope.model.currentTab].list.query_info.total_num_results), parseFloat($scope.model.lotListLimit) ); return Math.ceil(parseFloat($scope.lotList[$scope.model.currentTab].list.query_info.total_num_results) / parseFloat($scope.model.lotListLimit)); } } } $scope.lotListRepeat = function(){ //console.log("lot in lotList[$scope.model.currentTab].list.result_page") return "lot in lotList[$scope.model.currentTab].list.result_page"; } $scope.pageStartOffset = function(){ return parseInt(($scope.model.lotListPage-1) * $scope.model.lotListLimit); } $scope.getFilteredLotList = function(lotList){ if(typeof lotList == 'undefined') return null; //console.log(lotList) //console.log("start " + $scope.pageStartOffset(), "end "+ parseInt($scope.model.lotListLimit)); if (viewVars.brand == 'phillips' || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5){ return lotList; } return lotList.slice($scope.pageStartOffset(), parseInt($scope.model.lotListLimit)+parseInt($scope.pageStartOffset())); } $scope.disablePaging = function(direction){ if(direction > 0){ return parseInt($scope.model.lotListPage)+1 > $scope.filterNumPages(); }else{ return parseInt($scope.model.lotListPage)-1 < 1; } } $scope.isPageOffsetDisabled = $scope.disablePaging; $scope.goPage = function(offset){ if($scope.WMService.hasGoogleAnalytics()){ sendGoogleAnalyticsData('send', 'event', 'LiveAuction', 'Button', 'GoPage', offset); } $scope.model.lotListPage += offset; // Phillips actually refreshes the lot list with an ajax query whenever the page is changed. Core simply uses an internally stored list of lots to paginate from. // So here, for phillips, we make an ajax query to get the page requested. if (viewVars.brand == 'phillips' || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme5){ var params = {page: $scope.model.lotListPage}; if ($scope.lotsPagination.indexOf(viewVars.brand) != -1) { params.limit = $scope.model.lotListLimit; } if ($scope.model.currentTab == 'bids'){ $scope.showMyBids(params); } else if ($scope.model.currentTab == 'watching'){ $scope.showWatching(params); } } } $scope.offsetPage = $scope.goPage; $scope.setLocalStateMessage = function(message, duration){ if(typeof duration == 'undefined'){ duration = viewVars.brand == 'n4-dorotheum' ? 2000 : 5000; } $scope.model.localStateMessage = message; if (viewVars.brand === 'n4-dorotheum') { // setting liveLot.state.message null to display localStateMessage, only liveLot.state.message was displayed while there are both messages available $scope.liveLot.state.message = null; } $timeout(function(){ $scope.model.localStateMessage = null; $scope.localStateMessageExclamationMark = false; },duration); } $scope.jumpToLot = function(){ if($scope.WMService.hasGoogleAnalytics()){ sendGoogleAnalyticsData('send', 'event', 'LiveAuction', 'Button', 'JumpToLot', $scope.model.jumpToLot); } var url = viewVars.endpoints.jumpToLot + $scope.model.jumpToLot + "/"+ $scope.liveLot.item.auction.row_id; var promise = $http.get(url, {params: {fieldset_additions: 'detail'}}).success(function(data){ var lot = WMService.afterLot(data.result_page[0]); $scope.model.lotDetail = lot; $scope.model.lotDetailImageIndex = 0; $scope.model.currentLotView = 'detail'; $scope.model.currentLotClicked = -1; $scope.model.jumpToLot = ""; }); promise.error(WMService.silentHandleError); } $( window ).resize(function() { $scope.lotInfoWidth(); $scope.bidHistoryHeight(); $scope.mainAreaHeight(); $scope.customContentHeight('.main-area-content'); if(!$scope.$$phase){ $scope.$apply(); } }); $(function(){ if(viewVars.featureVersions.liveAuction == 2 || viewVars.brand == 'phillips' || viewVars.features.liveAuctionTheme2 || viewVars.features.liveAuctionTheme4 || viewVars.features.liveAuctionTheme3 || viewVars.features.liveAuctionTheme5){ $scope.mainAreaScroll = function(){ $('.main-area-content').scroll(function(){ if(typeof viewVars._bidSuggestionsElement != 'undefined'){ if(viewVars._bidSuggestionsElement.is(':visible')){ WMService.positionBidSuggestions(); } } }); } } }); $scope.muteToggle = function() { if ($scope.model.audioStreamMute === 'ON') { $scope.model.audioStreamMute = 'OFF'; $scope.OTsubscriber.subscribeToAudio(false); } else { $scope.model.audioStreamMute = 'ON'; $scope.OTsubscriber.subscribeToAudio(true); } localStorage.setItem('audioStreamStatus', $scope.model.audioStreamMute) } $scope.customStateMessage = function(){ //this function will return true if we have a state message being broadcast that is not fair warning or last call. This way we can show the state message in different places return (($scope.liveLot.state.message != 'Fair Warning' && $scope.liveLot.state.message != 'Last Call') && $scope.liveLot.state.message != null && $scope.liveLot.state.message != ' '); } $scope.messageInPaddle = function(){ //this function will return true if the message should appear in the paddle area. return ($scope.liveLot.state.message == 'Fair Warning' || $scope.liveLot.state.message == 'Last Call' || $scope.model.localStateMessage); } $scope.liveBiddingMessage = function() { $scope.model.biddingMessage=""; if (!$scope.model.connected) { $scope.model.biddingMessage = "DISCONNECTED"; } if ($scope.model.localStateMessage == "You've been outbid!") { $scope.model.biddingMessage = $scope.model.localStateMessage; } if (LiveLotViewState.isWinning($scope.liveLot, $scope.currentLotDetail, $scope.model.connected)) { $scope.model.biddingMessage = "HIGHEST BIDDER"; } if (($scope.liveLot.state.message == 'Fair Warning' || $scope.liveLot.state.message == 'Last Call')) { $scope.model.biddingMessage = $scope.liveLot.state.message + "!"; } if (LiveLotViewState.isWon($scope.liveLot, $scope.currentLotDetail, $scope.model.connected)) { var lotNumber = $scope.currentLotDetail.lot_number+$scope.currentLotDetail.lot_number_extension; $scope.model.biddingMessage = (WMService.hasProduct('realEstateHybrid') === false) ? $filter('translate')("YOU WON LOT "+lotNumber+"!") : $filter('translate')("YOU WON ITEM "+lotNumber+"!") ; } if (LiveLotViewState.isPaused($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) || LiveLotViewState.isClosed($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) || LiveLotViewState.isPassed($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) || LiveLotViewState.isAuctionStarting($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) || LiveLotViewState.isPreparing($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) || (LotService.isSold($scope.currentLotDetail) && !LotService.isWin($scope.currentLotDetail, $scope.liveLot.state) && !LotService.isNow($scope.currentLotDetail, $scope.liveLot.state)) || $scope.model.showReconnectLoader ) { $scope.model.biddingMessage = "PLEASE WAIT"; } return $scope.model.biddingMessage; } /** * the purpose of this function is to tell us if we should show the message or the current bid in the line 1 of the mobile footer * @return {[type]} [description] */ $scope.messageInMobileFooter = function () { var showMessage = false; if (($scope.liveLot.hasOwnProperty('state') === true && typeof $scope.liveLot.state !== 'undefined' && $scope.liveLot.state !== 'null' && $scope.liveLot.state.hasOwnProperty('message') === true && typeof $scope.liveLot.state.message !== 'undefined' && $scope.liveLot.state.message !== null && $scope.liveLot.state.message.trim() !== '') && ($scope.model.localStateMessage === null || typeof $scope.model.localStateMessage === 'undefined' || $scope.model.localStateMessage.trim() === '')){ showMessage = true; } return showMessage; } $scope.phillipsLiveLotListTab = function(tab){ if (tab && viewVars.features.enlargeThumbnailImagesPopup) { $rootScope.popoverThumbnailLots(200); } return $scope.liveLotListTab(tab); } $scope.liveLotListTab = function(tab){ if (typeof tab == 'undefined' || !tab){ return $scope.model.liveLotListTab; } $scope.model.liveLotListTab = tab; if (tab == 'past' && viewVars.brand !== 'n4-dorotheum'){ var currentLotIndex = $scope.lotIdToIndex($scope.liveLot.item.row_id); var limit = 100; var offset = 0; // Determine the offset and limit dynamically based on the currentLotIndex if (currentLotIndex < limit) { limit = currentLotIndex; // Load only past lots } else { offset = currentLotIndex - limit; } // Make the request only if there are lots to load if (limit > 0) { $scope.showMoreLots(offset, limit); } } }; $scope.toggleSalesNoticeText = function(){ $scope.salesNoticeExpanded = !$scope.salesNoticeExpanded; if($scope.salesNoticeExpanded && viewVars.currentRouteName === 'live-auction-mobile') { $scope.scrollToSalesNotice(); } }; $scope.salesNoticeButtonText = function(){ return $scope.salesNoticeExpanded ? ('- ' + $filter('translate')('Less')) : ('+ ' + $filter('translate')('More')); }; $scope.salesRoomNoticeText = function(text){ $scope.showSalesNoticeExpanded = text.length > 50; return $scope.salesNoticeExpanded ? text : (text.length > 50 ? text.slice(0,50) + '...' : text); }; $scope.getLotSelectionTime = function () { // we need to get this into milliseconds return parseFloat($scope.groupVars.endTime / 1000); } $scope.lotSelectionTimerCountdownCallback = function () { // callback no longer needed for closing the selection modal. leaving in case we need to do anything with it though. //$scope.closeLotSelectionModal(); } $scope.manualLotSelection = function (lotId) { var idx = $scope.groupVars.selectedLots.indexOf(lotId); if ($scope.liveLot.mode === 'take_only_one') { $scope.groupVars.selectedLots = []; } if (idx > -1) { // Is currently selected $scope.groupVars.selectedLots.splice(idx, 1); } else { // Is newly selected $scope.groupVars.selectedLots.push(lotId); } } $scope.isLotSelected = function (lotId) { if ($scope.groupVars.selectedLots.indexOf(lotId) === -1) { return false; } else { return true; } } $scope.declineGroup = function () { var data = { type: 'lot-group-select-lots', message: { lots: [], not_interested: true, }, }, jsonData = JSON.stringify(data); $scope.connection.send(jsonData); // todo we want to show the confirmation screen once that is possible clearInterval($scope.groupVars.lotSelectionCountdownInterval); $scope.closeLotSelectionModal(); } $scope.confirmGroup = function () { var data = { type: 'lot-group-select-lots', message: { lots: $scope.groupVars.selectedLots, subject_to: $scope.groupVars.isSubjectTo }, }, jsonData = JSON.stringify(data); $scope.connection.send(jsonData); } $scope.authenticateWebsocket = function () { var data = { "type": "register", "message": { "access_token": viewVars.accessToken, } }, jsonData = JSON.stringify(data); $scope.connection.send(jsonData); } $scope.calculateGroupTotal = function (groupMode, finalBid, timesTheMoney) { var groupTotal = 0, numLots = $scope.groupVars.confirmedSelectedLots.length > 0 ? $scope.groupVars.confirmedSelectedLots.length : $scope.groupVars.selectedLots.length; if (typeof timesTheMoney === 'undefined' || timesTheMoney === null) { timesTheMoney = false; } switch (groupMode) { case 'take_any_quantity': if (timesTheMoney === false) { groupTotal = finalBid; } else { groupTotal = numLots * finalBid; } break; case 'take_between_x_and_y': if (timesTheMoney === false) { groupTotal = finalBid; } else { groupTotal = numLots * finalBid; } break; case 'one_bid_take_all': groupTotal = finalBid; break; case 'take_only_one': groupTotal = finalBid; break; default: groupTotal = finalBid; break; } return groupTotal; } $scope.selectAll = function () { for (var i = 0; i < $scope.liveLot.item.items.length; i++) { var lotId = $scope.liveLot.item.items[i].row_id; if ($scope.groupVars.selectedLots.indexOf(lotId) === -1) { $scope.groupVars.selectedLots.push(lotId); } } if(!$scope.$$phase){ $scope.$apply(); } } $scope.unselectAll = function () { $scope.groupVars.selectedLots = []; } $scope.lotDescriptionTab = function(tab) { if (tab == 'price' && viewVars.brand == 'stacksbowers' && viewVars.me && !WMService.auctionIsPast($scope.currentLotDetail.auction) && $scope.isPriceGuideLoadAgain) { $scope.getStaticGuide('price-guide',$scope.currentLotDetail.row_id); $scope.getStaticGuide('population-guide',$scope.currentLotDetail.row_id); $scope.getStaticGuide('pcgs-guide', $scope.currentLotDetail.row_id); $scope.getStaticGuide('ngc-guide', $scope.currentLotDetail.row_id); $scope.getStaticGuide('cac-guide', $scope.currentLotDetail.row_id, 1); } return $scope.model.lotDescriptionTab = tab; } $scope.showSlider = function () { return ((LiveLotViewState.isPendingBid($scope.liveLot, $scope.currentLotDetail, $scope.model.connected, $scope.model.bidSentShowStatus) === false || typeof LiveLotViewState.isPendingBid($scope.liveLot, $scope.currentLotDetail, $scope.model.connected, $scope.model.bidSentShowStatus) === 'undefined') && LiveLotViewState.isWinning($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isPaused($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isPreparing($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isAuctionStarting($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isClosed($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isSold($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isPassed($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isWon($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && WMService.auctionRegistration($scope.currentLotDetail.auction) && !WMService.isPendingAuctionRegistration($scope.currentLotDetail.auction.row_id) && !WMService.isDeclinedAuctionRegistration($scope.currentLotDetail.auction) && LiveLotViewState.isSubjectToWinner($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && LiveLotViewState.isSubjectTo($scope.liveLot, $scope.currentLotDetail, $scope.model.connected) === false && $scope.model.connected === true); } $scope.sortedCurrenciesList = function(currenciesList) { var sortedCurrenciesList = []; // Look for the auction currency code first and add to sorted list. for (var i=0; i < currenciesList.length; i++ ){ if (currenciesList[i].currency_code === $scope.liveLot.auction.currency_code) { sortedCurrenciesList.push(currenciesList[i]); } } // Add remaining currencies. for (var i=0; i < currenciesList.length; i++) { if (currenciesList[i].currency_code !== $scope.liveLot.auction.currency_code) { sortedCurrenciesList.push(currenciesList[i]); } } return sortedCurrenciesList; } $scope.currencyConversions = function(value, valueCurrencyCode, currenciesList) { var USDValue = value; // Convert to USD value first. if (valueCurrencyCode != 'USD') { for (var i=0; i < currenciesList.length; i++){ if (currenciesList[i].currency_code == valueCurrencyCode){ USDValue /= currenciesList[i].usd_per_unit; } } } var currencyConversions = []; var convertedValue; for (var i=0; i < currenciesList.length; i++) { convertedValue = USDValue * currenciesList[i].usd_per_unit; currencyConversions.push({currency_code: currenciesList[i].currency_code, value: convertedValue}); } return currencyConversions; } $scope.bulletinCurrencyConversions = function() { if ($scope.liveLot.hasOwnProperty('state') && $scope.liveLot.state.hasOwnProperty('highbid') && $scope.liveLot.state.highbid.hasOwnProperty('bid')) { if ($scope.model.hasOwnProperty('bulletinCurrencyConversions') && $scope.model.bulletinCurrencyConversions.hasOwnProperty($scope.liveLot.state.highbid.bid)) { return $scope.model.bulletinCurrencyConversions[$scope.liveLot.state.highbid.bid]; } var bulletinCurrencyConversions = []; if (viewVars.hasOwnProperty('currencyConversion') && viewVars.currencyConversion && viewVars.currencyConversion.hasOwnProperty('result_page')){ bulletinCurrencyConversions = $scope.currencyConversions($scope.liveLot.state.highbid.bid, $scope.liveLot.auction.currency_code, $scope.sortedCurrenciesList(viewVars.currencyConversion.result_page)); } else { bulletinCurrencyConversions = [{currency_code: $scope.liveLot.auction.currency_code, value: $scope.liveLot.state.highbid.bid}]; } if ($scope.model.hasOwnProperty('bulletinCurrencyConversions') === false) { $scope.model.bulletinCurrencyConversions = {}; } $scope.model.bulletinCurrencyConversions[$scope.liveLot.state.highbid.bid] = bulletinCurrencyConversions; return bulletinCurrencyConversions; } return []; } $scope.videoFullScreen = function() { var player = document.querySelector('#live-video'); var video = player.querySelector('.OT_root'); if (video.requestFullScreen) { video.requestFullScreen(); } else if (video.webkitRequestFullScreen) { video.webkitRequestFullScreen(); } else if (video.mozRequestFullScreen) { video.mozRequestFullScreen(); } $scope.model.isFullScreenVideo = true; $('.video-show-sound-wrap').appendTo(video); } /* Close fullscreen */ $scope.closeFullscreen = function() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { /* Safari */ document.webkitExitFullscreen(); } else if (document.msExitFullscreen) { /* IE11 */ document.msExitFullscreen(); } $scope.model.isFullScreenVideo = false; } // Toggles audio on and off $scope.toggleAudio = function() { if (typeof(OT) != 'undefined') { if($scope.isSubscriberAudioBlocked) { $scope.OTsubscriber.subscribeToAudio(true); $scope.isSubscriberAudioBlocked=false; } else { $scope.OTsubscriber.subscribeToAudio(false); $scope.isSubscriberAudioBlocked=true; } } } $scope.videoOn = function() { $scope.model.isVideoOn = !$scope.model.isVideoOn; $scope.model.isVideoDisabled = !$scope.model.isVideoDisabled; $scope.OTsession.on("streamDestroyed", function (event) { $scope.model.isVideoDisabled=true; }); } $scope.getStaticGuide = function(type,itemId,guideLastCall) { var promise = $http.get(viewVars.endpoints.lotPriceGuide + itemId + '/' + type); promise.success(function(response) { $scope.staticGuides[type] = response; if(type == 'pcgs-guide') { $scope.isPriceGuideLoading = false; $scope.isPriceGuideError = false; } if (guideLastCall) { angular.forEach($scope.staticGuides, function(value, key) { if (value.result_page.length === 0) { $scope.isPriceGuideEmpty = $scope.isPriceGuideEmpty + 1; } }); } $scope.isPriceGuideLoadAgain = false; }); promise.error(function(error) { $scope.isPriceGuideLoading = false; $scope.isPriceGuideLoadAgain = true; $scope.isPriceGuideError = true; }); } $scope.orderedStaticGuideFieldNames = function(row, type) { var columns = Object.keys(row); var filteredColumns = []; for (var i=0; i -1) { filteredColumns.push(ordered[i]); } } } if (type == 'price-guide') { var ordered = ['grade','cdn','cpg','cdncac','trends',/*'cacmarketvalue',*/'pcgsprice','pcgspriceplus','ngcprice','ngcpriceplus']; filteredColumns = []; for (var i=0; i -1) { filteredColumns.push(ordered[i]); } } } else { filteredColumns.sort(function(a,b) { if (a == 'type') return -1; if (b == 'type') return 1; if (a == 'total') return 1; if (b == 'total') return -1; // Set 'g4' as earlier than 'g35' for example if (a.length > 1 && a[0] == 'g' && !isNaN(parseFloat(a[1])) && isFinite(a[1]) && b.length > 1 && b[0] == 'g' && !isNaN(parseFloat(b[1])) && isFinite(b[1])) { var aFiltered = a.replace("+","").replace("p",""); var bFiltered = b.replace("+","").replace("p",""); if (aFiltered.length < bFiltered.length) { return -1; } else if (aFiltered.length > bFiltered.length) { return 1; } else { // Comparing the "68" in g68 to "69" in g69 if (parseInt(aFiltered.slice(1)) < parseInt(bFiltered.slice(1))) { return -1; } else if (parseInt(aFiltered.slice(1)) > parseInt(bFiltered.slice(1))) { return 1; } else { if (a.slice(1).length > b.slice(1).length) { return 1; } else { return -1; } } } } if (type == 'price-guide') { if (a == 'grade') return -1; if (b == 'grade') return 1; } }); } return filteredColumns; } $scope.staticGuideFieldLabel = function(fieldname, type) { var mapping = { 'gradetype': "Grade", 'grade': "Grade", 'type': "Grade", 'cdncac': 'CAC Market Review', 'proofmint': 'Proof/Mint', 'cacmarketvalue': 'CAC Market Values', 'pophigher': 'Population in Higher Grade', 'population': 'Population in this Grade', 'ccdn_ngc': 'ngc +', 'ccdn_pcgs': 'pcgs +', 'ngcprice': 'NGC Price Guide', 'pcgsprice': 'PCGS Price Guide', 'ngcpriceplus': 'NGC +', 'pcgspriceplus': 'PCGS +', 'trends': 'Coin World Trends', 'cpg': 'Collector\'s Price Guide', 'cdn': 'Greysheet' } if (mapping.hasOwnProperty(fieldname)) { return mapping[fieldname]; } else if (fieldname.length > 1 && fieldname.search(/g[0-9]+(p)?$/i) > -1) { return fieldname.slice(1).replace("p","+"); } return fieldname; } $scope.staticGuideFieldValues = function(row, type) { var fieldNames = $scope.orderedStaticGuideFieldNames(row, type); var orderedValues = {}; for (var i=0; i 1) { currValue = row; for (var j=0; j= $scope.model.numReconnectAttempts) { clearInterval($scope.model.reconnectInterval); } }, 3000); } if (newVal === true) { // kill the interval $scope.model.reconnectAttemptCount = 0; clearInterval($scope.model.reconnectInterval); } }); } $scope.flashGroupInfo = function () { $scope.groupVars.flashGroupInfoTimeout = $timeout(function() { $scope.groupVars.groupModeFlashCt++; $scope.flashGroupInfo(); }, 500); if ($scope.groupVars.groupModeFlashCt > 5) { $timeout.cancel($scope.groupVars.flashGroupInfoTimeout); $scope.groupVars.groupModeFlashCt = 0; } } $scope.changeLimit = function () { if ($scope.model.currentTab === "bids") { $scope.showMyBids(); } else if ($scope.model.currentTab === "watching") { $scope.showWatching(); } }; $scope.lotsStripScrollToNext = function (noAnimate) { $timeout(function () { var $theSelector = $($scope.lotsStripSelector).find('.lots-strip.auto-scroll'); if (viewVars.features.liveAuctionTheme3 && $theSelector.get(0).scrollHeight > $theSelector.height() ) { $theSelector.animate({ scrollTop: $theSelector.find('>:first-child').height() }, noAnimate === true ? 0 : 'slow'); } }, 0); }; $scope.isLocalStateMessage = function () { var localStateMessage = $scope.model.localStateMessage; return ((localStateMessage == 'You\'ve been outbid!' || localStateMessage == 'La tua offerta è stata superata!' || localStateMessage == 'Sie wurden überboten!' || $scope.model.localStateMessage == 'Another bid was taken' || $scope.liveLot.state.message == 'Fair Warning' || $scope.liveLot.state.message == 'Last Call' || (viewVars.brand === 'n4-dorotheum' && $scope.isLiveLotStateMessage()))); }; $scope.checkItemSlide = function () { if ($scope.currentLotDetail.images.length <= 1) { return false; } let $this = $("#myCarousel"); if ($("#myCarousel .carousel-inner .item:first").hasClass("active")) { $this.children(".left").removeClass("left-arrow"); $this.children(".right").addClass("right-arrow"); } else if ($("#myCarousel .carousel-inner .item:last").hasClass("active")) { $this.children(".right").removeClass("right-arrow"); $this.children(".left").addClass("left-arrow"); } else { $this.children(".left").addClass("left-arrow"); $this.children(".right").addClass("right-arrow"); } return true; }; $scope.checkItemLotReviewSlide = function () { if ($scope.currentLotDetail.images.length <= 1) { return false; } let $this = $("#myCarouseDetail"); if ($("#myCarouseDetail .carousel-inner .item:first").hasClass("active")) { $this.children(".left").removeClass("left-arrow"); $this.children(".right").addClass("right-arrow"); } else if ($("#myCarouseDetail .carousel-inner .item:last").hasClass("active")) { $this.children(".right").removeClass("right-arrow"); $this.children(".left").addClass("left-arrow"); } else { $this.children(".left").addClass("left-arrow"); $this.children(".right").addClass("right-arrow"); } return true; }; $scope.startFairWarningCountDown = function () { // stops any running interval to avoid two intervals running at the same time $scope.stopFairWarningCountDown(); var fairWarningTimeLeft = $scope.liveLot.state.fair_warning_time_left ? $scope.liveLot.state.fair_warning_time_left : 0; $scope.liveLot.state.fairWarningSecLeft = 0; if (fairWarningTimeLeft > 0) { $scope.liveLot.state.fairWarningSecLeft = Math.round((fairWarningTimeLeft / 1000) % 60); } // store the interval promise showFairWarningCountDown(); promiseFairWarningCounter = $interval(showFairWarningCountDown, 1000); }; $scope.stopFairWarningCountDown = function () { $interval.cancel(promiseFairWarningCounter); }; function showFairWarningCountDown() { if ($scope.liveLot.state.message === "Fair Warning") { if ($scope.liveLot.state.fairWarningSecLeft < 1) { $scope.safeApply(function () { $scope.liveLot.state.message = null; $scope.liveLot.state.fairWarningTimer = ""; }); $scope.stopFairWarningCountDown(); } else { $scope.safeApply(function () { $scope.liveLot.state.fairWarningTimer = " (" + $scope.liveLot.state.fairWarningSecLeft + " sec)"; }); $scope.liveLot.state.fairWarningSecLeft = $scope.liveLot.state.fairWarningSecLeft - 1; } } }; $scope.safeApply = function (fn) { var phase = this.$root.$$phase; if (phase == "$apply" || phase == "$digest") { if (fn && typeof fn === "function") { fn(); } } else { this.$apply(fn); } }; $scope.init(); $(document).ready(function(){ if($(window).width() <= 1024){ $('.lang-dropdown').click(function(){ $('.lang-dropdown ul').toggle(); }); } $(document).on("slid.bs.carousel", "#myCarousel", $scope.checkItemSlide); $(document).on("slid.bs.carousel", "#myCarouseDetail", $scope.checkItemLotReviewSlide); }); /** * checkSequenceNumber function is used to check the state_sequence_number * whether it is greater or not from the locally store state_sequence_number of current lot * @param {Object} liveLot locally stored lot object data * @param {Object} message event data object * @return {boolean} */ var checkSequenceNumber = function(liveLot, envelope) { var isValidEvent = true; let lotId = envelope.message.lot_id ? envelope.message.lot_id : (envelope.message.state && envelope.message.state.lot_id ? envelope.message.state.lot_id : null) ; var eventStateSequenceNumber = (envelope.type == "state") ? envelope.message.state_sequence_number : envelope.message.state.state_sequence_number; if ( lotId && liveLot.item && lotId === liveLot.item.row_id && eventStateSequenceNumber && liveLot.state.state_sequence_number && eventStateSequenceNumber <= liveLot.state.state_sequence_number ) { isValidEvent = false; } if (isValidEvent || (new Date(envelope.send_at) > new Date($rootScope.eventSendAt))) { $rootScope.eventSendAt = envelope.send_at; } return isValidEvent; } /** * checkBrowserMediaAutoPlayPermission function is used to check * whether the user's browser has media auto-play permission or not * * @return {boolean} */ $scope.checkBrowserMediaAutoPlayPermission = function() { var hasAutoPlayPermission = false; try { if (navigator.userAgent.indexOf("Firefox") != -1) { hasAutoPlayPermission = (!$scope.isMobile && navigator.getAutoplayPolicy("mediaelement") == "allowed") ? true : false; } else { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); hasAutoPlayPermission = (audioContext.state == "suspended") ? false : true; } } catch (error) { if (Boolean(localStorage.getItem('showMessagesInConsole')) == true) { AMLogger.logToConsole(error); } } return hasAutoPlayPermission; }; }]);