diff --git a/client/app/sprites.css b/client/app/sprites.css index 856ce984..66a79319 100644 --- a/client/app/sprites.css +++ b/client/app/sprites.css @@ -11,7 +11,7 @@ Icon classes can be used entirely standalone. They are named after their origina */ .icon-Error_16x16 { background-image: url(../assets/images/spritesheet.png); - background-position: -336px -52px; + background-position: -336px -72px; width: 16px; height: 16px; } @@ -35,7 +35,7 @@ Icon classes can be used entirely standalone. They are named after their origina } .icon-Neon_16x16 { background-image: url(../assets/images/spritesheet.png); - background-position: -320px -52px; + background-position: -320px -72px; width: 16px; height: 16px; } @@ -52,6 +52,12 @@ Icon classes can be used entirely standalone. They are named after their origina height: 34px; } .icon-Translated_20x20 { + background-image: url(../assets/images/spritesheet.png); + background-position: -320px -52px; + width: 20px; + height: 20px; +} +.icon-Translation_Failed_20x20 { background-image: url(../assets/images/spritesheet.png); background-position: -320px -32px; width: 20px; @@ -65,79 +71,79 @@ Icon classes can be used entirely standalone. They are named after their origina } .icon-BarChart64 { background-image: url(../assets/images/spritesheet.png); - background-position: -192px 0px; + background-position: -192px -64px; width: 64px; height: 64px; } .icon-Count64 { background-image: url(../assets/images/spritesheet.png); - background-position: -192px -64px; + background-position: -192px -128px; width: 64px; height: 64px; } .icon-CreateFilter64 { background-image: url(../assets/images/spritesheet.png); - background-position: -192px -128px; + background-position: 0px -192px; width: 64px; height: 64px; } .icon-Gantt64 { background-image: url(../assets/images/spritesheet.png); - background-position: 0px -192px; + background-position: -64px -192px; width: 64px; height: 64px; } .icon-Graph64 { background-image: url(../assets/images/spritesheet.png); - background-position: -64px -192px; + background-position: -128px -192px; width: 64px; height: 64px; } .icon-LineChart64 { background-image: url(../assets/images/spritesheet.png); - background-position: -128px -192px; + background-position: 0px 0px; width: 64px; height: 64px; } .icon-Map64 { background-image: url(../assets/images/spritesheet.png); - background-position: 0px 0px; + background-position: -256px 0px; width: 64px; height: 64px; } .icon-News64 { background-image: url(../assets/images/spritesheet.png); - background-position: -256px 0px; + background-position: -256px -64px; width: 64px; height: 64px; } .icon-OPS64 { background-image: url(../assets/images/spritesheet.png); - background-position: -256px -64px; + background-position: -256px -128px; width: 64px; height: 64px; } .icon-OpsClock64 { background-image: url(../assets/images/spritesheet.png); - background-position: -256px -128px; + background-position: -256px -192px; width: 64px; height: 64px; } .icon-PieChart64 { background-image: url(../assets/images/spritesheet.png); - background-position: -256px -192px; + background-position: 0px -256px; width: 64px; height: 64px; } .icon-ScatterPlot64 { background-image: url(../assets/images/spritesheet.png); - background-position: 0px -256px; + background-position: -64px -256px; width: 64px; height: 64px; } .icon-Sunburst64 { background-image: url(../assets/images/spritesheet.png); - background-position: -64px -256px; + background-position: -192px 0px; width: 64px; height: 64px; } diff --git a/client/app/translation.service.js b/client/app/translation.service.js index 288feed3..4a03079a 100644 --- a/client/app/translation.service.js +++ b/client/app/translation.service.js @@ -172,10 +172,15 @@ angular.module("neonDemo.services") }); deferred.resolve(response); }, function(response) { - deferred.reject({ - message: response.data.error.message, - reason: concatErrorResponses(response.data.error.errors) - }); + var rejection = { + message: "", + reasion: "" + }; + if(response && response.data && response.data.error) { + rejection.message = response.data.error.message; + rejection.reason = concatErrorResponses(response.data.error.errors); + } + deferred.reject(rejection); }); return deferred.promise; diff --git a/client/assets/images/Translation_Failed_20x20.png b/client/assets/images/Translation_Failed_20x20.png new file mode 100644 index 00000000..0f99bc46 Binary files /dev/null and b/client/assets/images/Translation_Failed_20x20.png differ diff --git a/client/assets/images/spritesheet.png b/client/assets/images/spritesheet.png index 47a016c7..45e6e21e 100644 Binary files a/client/assets/images/spritesheet.png and b/client/assets/images/spritesheet.png differ diff --git a/client/components/newsFeed/newsFeed.controller.js b/client/components/newsFeed/newsFeed.controller.js index 5a76b74c..b284aa1e 100644 --- a/client/components/newsFeed/newsFeed.controller.js +++ b/client/components/newsFeed/newsFeed.controller.js @@ -35,6 +35,8 @@ angular.module('neonDemo.controllers').controller('newsFeedController', ['$scope // Prevents translation api calls from getting too long and returning an error var TRANSLATION_INTERVAL = 10; + var MAXIMUM_TRANSLATION_QUERY_LENGTH = 10000; + // The data in this newsfeed from a news event or an empty array if the data in this newsfeed is from a query. var newsEventData = []; @@ -378,6 +380,36 @@ angular.module('neonDemo.controllers').controller('newsFeedController', ['$scope sliceStart = sliceStart || 0; sliceEnd = sliceEnd || sliceStart + TRANSLATION_INTERVAL; + var getLengthOfField = function(index, key) { + if($scope.active.data[index] && _.isString($scope.active.data[index][key])) { + return encodeURIComponent($scope.active.data[index][key]).length; + } else { + return 0; + } + }; + + var queryLength = 0; + for(var i = sliceStart; i < sliceEnd; ++i) { + queryLength += getLengthOfField(i, "primaryTitle"); + queryLength += getLengthOfField(i, "secondaryTitle"); + queryLength += getLengthOfField(i, "content"); + if(queryLength > MAXIMUM_TRANSLATION_QUERY_LENGTH) { + // If this item causes us to go over the maximum length, then stop before this + // item... + if(i > sliceStart) { + sliceEnd = i; + break; + } else { + // ...unless this was the first item, in which case this item is too long to + // translate on its own, so just mark its translation as failed and skip it. + $scope.active.data[i].isTranslated = false; + $scope.active.data[i].translationFailed = true; + sliceStart = i + 1; + queryLength = 0; + } + } + } + runTranslation("primaryTitle", sliceStart, sliceEnd, function() { runTranslation("secondaryTitle", sliceStart, sliceEnd, function() { runTranslation("content", sliceStart, sliceEnd, function() { @@ -395,37 +427,43 @@ angular.module('neonDemo.controllers').controller('newsFeedController', ['$scope * @param {String} newsProperty * @param {Integer} sliceStart * @param {Integer} sliceEnd - * @param {Function} successCallback + * @param {Function} finishedCallback * @method runTranslation * @private */ - var runTranslation = function(newsProperty, sliceStart, sliceEnd, successCallback) { + var runTranslation = function(newsProperty, sliceStart, sliceEnd, finishedCallback) { var dataText = _.pluck($scope.active.data, newsProperty).map(function(data) { return $.isNumeric(data) ? "" : data; }); var translationSuccessCallback = function(translations) { var index = sliceStart; - translations.forEach(function(item) { - while(!$scope.active.data[index][newsProperty] && index < sliceEnd) { - index++; - } - if(index < sliceEnd) { - var newsItem = $scope.active.data[index]; - newsItem[newsProperty + "Translated"] = item.translatedText; - newsItem.isTranslated = newsItem.isTranslated || newsItem[newsProperty] !== newsItem[newsProperty + "Translated"]; - index++; - } - }); - successCallback(); + try { + translations.forEach(function(item) { + if(index < sliceEnd) { + if(!($scope.active.data[index] && $scope.active.data[index][newsProperty])) { + index++; + } else { + var newsItem = $scope.active.data[index]; + newsItem[newsProperty + "Translated"] = item.translatedText; + newsItem.isTranslated = !newsItem.translationFailed && (newsItem.isTranslated || newsItem[newsProperty] !== newsItem[newsProperty + "Translated"]); + index++; + } + } + }); + } catch(err) { + } + finishedCallback(); }; var translationFailureCallback = function() { for(var i = sliceStart; i < sliceEnd; ++i) { $scope.active.data[i][newsProperty + "Translated"] = $scope.active.data[i][newsProperty]; $scope.active.data[i].isTranslated = false; + $scope.active.data[i].translationFailed = true; } $scope.loadingTranslations = false; + finishedCallback(); }; $scope.functions.runTranslation(dataText.slice(sliceStart, sliceEnd), translationSuccessCallback, translationFailureCallback); diff --git a/client/components/newsFeed/newsFeedDisplay.html b/client/components/newsFeed/newsFeedDisplay.html index 751f935e..9b668905 100644 --- a/client/components/newsFeed/newsFeedDisplay.html +++ b/client/components/newsFeed/newsFeedDisplay.html @@ -3,6 +3,7 @@