From 92302bd0b2399e2eaf8392177118f3e76f3e9c69 Mon Sep 17 00:00:00 2001 From: Matthieu Moquet Date: Sun, 28 Dec 2014 17:01:57 +0100 Subject: [PATCH] Optimize translation controller In order to only fetch group of translations one by one. Thus it doesn't be slower with many resources. See #8 --- CHANGELOG.md | 4 + src/config/routing.js | 22 +- src/controllers/TranslateController.js | 271 +++++++++++--------- src/entities/TranslationCommitRepository.js | 2 +- src/entities/TranslationRepository.js | 2 +- src/models/TranslationCommitCollection.js | 155 ----------- src/models/TranslationCommitList.js | 82 ++++++ src/models/TranslationsGroup.js | 25 ++ src/styles/layouts/translate-sidebar.scss | 2 + src/views/translate-phrase.html | 76 +----- src/views/translate.html | 26 +- 11 files changed, 289 insertions(+), 378 deletions(-) delete mode 100644 src/models/TranslationCommitCollection.js create mode 100644 src/models/TranslationCommitList.js create mode 100644 src/models/TranslationsGroup.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 070bc3e..b4ad884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## Unreleased + +- Optimize editor load for project with many resources: translations calls are now done one by one + ## 0.0.1 - 2014-12-12 Initial version. diff --git a/src/config/routing.js b/src/config/routing.js index d05ac27..589b0fd 100644 --- a/src/config/routing.js +++ b/src/config/routing.js @@ -97,17 +97,17 @@ app.config(function($stateProvider, $urlRouterProvider) { }, } }) - // .state('translate.source.target.phrase', { - // url: '/{id:[0-9]+}', - // menu: 'translate', - // templateUrl: 'views/translate-phrase.html', - // controller: 'TranslatePhraseController', - // resolve: { - // translationId: function($stateParams) { - // return +$stateParams.id; - // }, - // } - // }) + .state('translate.source.target.phrase', { + url: '/{id:[0-9]+}', + menu: 'translate', + templateUrl: 'views/translate-phrase.html', + controller: 'TranslatePhraseController', + resolve: { + translationId: function($stateParams) { + return +$stateParams.id; + }, + } + }) .state('glossary', { url: '/glossary', menu: 'glossary', diff --git a/src/controllers/TranslateController.js b/src/controllers/TranslateController.js index b440183..8ae083a 100644 --- a/src/controllers/TranslateController.js +++ b/src/controllers/TranslateController.js @@ -20,93 +20,88 @@ angular .controller('TranslateController', TranslateController) .controller('TranslateSourceController', TranslateSourceController) .controller('TranslateTargetController', TranslateTargetController) + .controller('TranslatePhraseController', TranslatePhraseController) ; -function ResourceWrapper(resource, context, filters, translationCommitRepository) { - // Keep orginal object & services - this.resource = resource; - this.context = context; - this.filters = filters; - this.translationCommitRepository = translationCommitRepository; - - // Map object attributes - this.pathname = this.resource.pathname; - this.translationCommits = []; - - // Handle paging - this.page = 0; - this.perPage = 40; - this.complete = false; - this.busy = false; -} - -ResourceWrapper.prototype.reset = function() { - this.translationCommits = []; - this.complete = false; - this.page = 0; -} - -ResourceWrapper.prototype.loadMore = function() { - if (this.busy || this.complete) - return; - - if (!this.context.source || !this.context.target) - return; - - this.busy = true; - this.page++; - this.translationCommitRepository.findBy(this.context.source, this.context.target, { - resource: this.resource.id, - page: this.page, - per_page: this.perPage, - text: this.filters.text - }).then(function(translationCommits) { - if (translationCommits.length < this.perPage) { - console.log('fetching finished') - this.complete = true; - } - - for (var i = 0; i < translationCommits.length; i++) { - var translationCommit = translationCommits[i]; - translationCommit.edit_phrase = translationCommit.target_phrase; - this.translationCommits.push(translationCommit); - } - - this.busy = false; - }.bind(this)); - -} - -function TranslateController($document, $scope, $state, project, languages, resources, TranslationRepository, TranslationCommitRepository) { - console.log('TranslateController'); - - // if (!target) { - // $state.transitionTo('translate', {locale: 'en'}, { location: true, inherit: true, relative: $state.$current, notify: false }) - // } +/** + * @ngInject + */ +function TranslateController($scope, $state, $timeout, project, languages, resources, TranslationsGroup, TranslationRepository) { + // Models $scope.project = project; $scope.languages = languages; + $scope.resources = resources; $scope.context = { source: project.default_locale, target: null }; $scope.filters = { text: '' }; $scope.searchQuery = ''; + $scope.translationsGroups = []; - $scope.resources = []; - angular.forEach(resources, function(resource) { - $scope.resources.push( - new ResourceWrapper(resource, $scope.context, $scope.filters, TranslationCommitRepository) - ); - }); + // Utilities + $scope.activedTranslation = null; + $scope.loadingTranslations = false; + /** + * Reinitialize translations with current context & filters. + */ + $scope.reset = function () { + $scope.translationsGroups = []; - $scope.activedTranslation = null; + angular.forEach($scope.resources, function(resource) { + var filters = $scope.filters; + filters = { resource: resource.id }; + + var group = new TranslationsGroup(resource, $scope.context, filters); + + $scope.translationsGroups.push(group); + }); + } + + /** + * Load more translations. + * + * This happens on infinite scroll, to lazy load the translations. + */ + $scope.loadMore = function() { + $scope.loadingTranslations = true; + var groupToLoad = null; + + // Find a resource to load + for (var i = 0; i < $scope.translationsGroups.length; i++) { + var group = $scope.translationsGroups[i]; + if (!group.translations.complete) { + var groupToLoad = group; + break; + } + } + + // Load that resource + if (groupToLoad) { + groupToLoad.translations + .loadMore() + .then(function() { + $scope.loadingTranslations = false; + }); + } + } + + /** + * Select a translation. + */ $scope.activateTranslation = function(translation) { if (null !== $scope.activedTranslation) $scope.activedTranslation.active = false; $scope.activedTranslation = translation; $scope.activedTranslation.active = true; + + $scope.updateRoute(); } + + /** + * Unselect a translation/ + */ $scope.deactivateTranslation = function(translation, $event) { if ($event) $event.stopPropagation(); @@ -116,27 +111,38 @@ function TranslateController($document, $scope, $state, project, languages, reso $scope.activedTranslation.active = false; $scope.activedTranslation = null; + + $scope.updateRoute(); } - $scope.updateRoute = updateRoute; - - function updateRoute() { - if ($scope.context.source && $scope.context.target) { - $state.go('translate.source.target', { - source: $scope.context.source, - target: $scope.context.target - }, {location: "replace", inherit: true, relative: $state.$current, notify: true}); - } else if ($scope.context.source) { - $state.go('translate.source', { - source: $scope.context.source, - }, {location: "replace", inherit: true, relative: $state.$current, notify: true}); - } else { - $state.go('translate.source', { - source: project.default_locale, - }, {location: "replace", inherit: true, relative: $state.$current, notify: true}); + /** + * Update the route according to the current state of the controller. + */ + $scope.updateRoute = function() { + var path = 'translate.source'; + var params = {source: project.default_locale}; + var options = {location: "replace", inherit: true, relative: $state.$current, notify: true}; + + if ($scope.context.source) { + params.source = $scope.context.source; + } + + if ($scope.context.target) { + path += '.target'; + params.target = $scope.context.target; + } + + if ($scope.activedTranslation) { + path += '.phrase'; + params.id = $scope.activedTranslation.id; } + + $state.go(path, params, options); } + /** + * Save a translation. + */ $scope.saveTranslation = function(translationCommit) { var originalPhrase = translationCommit.target_phrase; translationCommit.target_phrase = translationCommit.edit_phrase; @@ -152,6 +158,9 @@ function TranslateController($document, $scope, $state, project, languages, reso // TODO : update original data } + /** + * Approve a translation. + */ $scope.approveTranslation = function(translationCommit) { var originalPhrase = translationCommit.target_phrase; translationCommit.target_phrase = translationCommit.edit_phrase; @@ -168,11 +177,17 @@ function TranslateController($document, $scope, $state, project, languages, reso // TODO : update original data } + /** + * Cancel an editing + */ $scope.cancelEdit = function(translationCommit) { translationCommit.edit_phrase = translationCommit.target_phrase; // TODO : update original data } + /** + * Submit a search query + */ $scope.search = function(text) { $scope.filters.text = text; $scope.resources.forEach(function(resource) { @@ -180,47 +195,58 @@ function TranslateController($document, $scope, $state, project, languages, reso resource.loadMore(); }) } + + // Init + $scope.reset(); + $timeout(function() { + $scope.loadMore(); + }, 500); } +/** + * @ngInject + */ function TranslateSourceController($scope, source) { console.log('TranslateSourceController'); $scope.context.source = source; } +/** + * @ngInject + */ function TranslateTargetController($scope, hotkeys, resources, target, TranslationCommitRepository) { console.log('TranslateTargetController'); $scope.context.target = target; + $scope.reset(); + $scope.loadMore(); - console.log($scope.context); - - $scope.resources.forEach(function(resource) { - resource.reset(); - resource.loadMore(); - }) - + /** + * Select the next translation + */ $scope.selectNext = function() { - var i, j, resource, translationCommit; + var i, j, group, translations, translationCommit; if (null === $scope.activedTranslation) { - if ($scope.resources.length > 0 && $scope.resources[0].translationCommits.length > 0) { - $scope.activateTranslation($scope.resources[0].translationCommits[0]); + if ($scope.translationsGroups.length > 0 && $scope.translationsGroups[0].translations.items.length > 0) { + $scope.activateTranslation($scope.translationsGroups[0].translations.items[0]); } return; } - for (i = 0; i < $scope.resources.length; i++) { - resource = $scope.resources[i]; + for (i = 0; i < $scope.translationsGroups.length; i++) { + group = $scope.translationsGroups[i]; + translations = group.translations.items; - for (j = 0; j < resource.translationCommits.length; j++) { - translationCommit = resource.translationCommits[j]; + for (j = 0; j < translations.length; j++) { + translationCommit = translations[j]; if (translationCommit.active) { - if (resource.translationCommits[j + 1]) { - $scope.activateTranslation(resource.translationCommits[j + 1]); - } else if ($scope.resources[i + 1] && $scope.resources[i + 1].translationCommits.length > 0) { - $scope.activateTranslation($scope.resources[i + 1].translationCommits[0]); + if (translations[j + 1]) { + $scope.activateTranslation(translations[j + 1]); + } else if ($scope.translationsGroups[i + 1] && $scope.translationsGroups[i + 1].translations.items.length > 0) { + $scope.activateTranslation($scope.translationsGroups[i + 1].translations.items[0]); } return; @@ -229,21 +255,24 @@ function TranslateTargetController($scope, hotkeys, resources, target, Translati } } + /** + * Select the previous translation + */ $scope.selectPrevious = function() { - var i, j, resource, translationCommit; + var i, j, group, translationCommit; - for (i = 0; i < $scope.resources.length; i++) { - resource = $scope.resources[i]; + for (i = 0; i < $scope.translationsGroups.length; i++) { + group = $scope.translationsGroups[i]; - for (j = 0; j < resource.translationCommits.length; j++) { - translationCommit = resource.translationCommits[j]; + for (j = 0; j < group.translations.items.length; j++) { + translationCommit = group.translations.items[j]; if (translationCommit.active) { - if (j > 0 && resource.translationCommits[j - 1]) { - $scope.activateTranslation(resource.translationCommits[j - 1]); - } else if (i > 0 && $scope.resources[i - 1] && $scope.resources[i - 1].translationCommits.length > 0) { - var last = $scope.resources[i - 1].translationCommits.length - 1; - $scope.activateTranslation($scope.resources[i - 1].translationCommits[last]); + if (j > 0 && group.translations.items[j - 1]) { + $scope.activateTranslation(group.translations.items[j - 1]); + } else if (i > 0 && $scope.translationsGroups[i - 1] && $scope.translationsGroups[i - 1].translations.items.length > 0) { + var last = $scope.translationsGroups[i - 1].translations.items.length - 1; + $scope.activateTranslation($scope.translationsGroups[i - 1].translations.items[last]); } return; @@ -252,16 +281,6 @@ function TranslateTargetController($scope, hotkeys, resources, target, Translati } } - // $scope.selectNext = _.throttle(function() { - // $scope.translationCommits.selectNext(); - // $scope.updateRoute(); - // }, 100); - - // $scope.selectPrevious = _.throttle(function() { - // $scope.translationCommits.selectPrevious(); - // $scope.updateRoute(); - // }, 100); - hotkeys .bindTo($scope) .add({ @@ -302,8 +321,12 @@ function TranslateTargetController($scope, hotkeys, resources, target, Translati return; $scope.saveTranslation($scope.activedTranslation); } - }) - ; + }); +} + + +function TranslatePhraseController(translationId) { + console.log('Selected translation #' + translationId); } })(); diff --git a/src/entities/TranslationCommitRepository.js b/src/entities/TranslationCommitRepository.js index a68799e..2ceab45 100644 --- a/src/entities/TranslationCommitRepository.js +++ b/src/entities/TranslationCommitRepository.js @@ -28,7 +28,7 @@ function TranslationCommitRepository($http, $q, Configuration) { $http.get(url).success(function(data) { deferred.resolve(data); - }, function() { + }).error(function() { deferred.reject(); }); diff --git a/src/entities/TranslationRepository.js b/src/entities/TranslationRepository.js index b5f428c..e61c626 100644 --- a/src/entities/TranslationRepository.js +++ b/src/entities/TranslationRepository.js @@ -32,7 +32,7 @@ function TranslationRepository($http, $q, Configuration) { data: angular.extend({ text: phrase, approved: false }, params) }).success(function(data) { deferred.resolve(data); - }, function() { + }).error(function() { deferred.reject(); }); diff --git a/src/models/TranslationCommitCollection.js b/src/models/TranslationCommitCollection.js deleted file mode 100644 index cd0aa2b..0000000 --- a/src/models/TranslationCommitCollection.js +++ /dev/null @@ -1,155 +0,0 @@ -(function() { - -'use strict'; - -angular - .module('app') - .factory('TranslationCommitCollection', TranslationCommitCollection); - -/** - * @ngInject - */ -function TranslationCommitCollection(TranslationCommit) { - - var Collection = function (translations, resources, context) { - // this.selectedResource = null; - this.selectedTranslation = null; - - this.resources = _.chain(resources) - .sortBy('pathname') - .value(); - - this.translations = _.chain(translations) - .map(function(translation) { - return new TranslationCommit(translation, context); - }) - .groupBy(function(translation) { - return translation.resourceId; - }) - .value(); - } - - Collection.prototype.select = function (itemId) { - if (this.selectedTranslation) { - this.selectedTranslation.selected = false; - } - - for (var i in this.translations) { - var list = this.translations[i]; - this.selectedTranslation = _.find(list, {id: itemId}); - - if (this.selectedTranslation) { - this.selectedTranslation.selected = true; - break; - } - } - - return this.selectedTranslation; - } - - Collection.prototype.selectFirst = function () { - for (var i = 0; i < this.resources.length; i++) { - var resource = this.resources[i]; - var translations = this.translations[resource.id] - - if (0 === translations.length) { - continue; - } - - var translation = _.first(translations); - - return this.select(translation.id); - } - } - - Collection.prototype.selectLast = function () { - for (var i = this.resources.length - 1; i >= 0; i--) { - var resource = this.resources[i]; - var translations = this.translations[resource.id] - - if (0 === translations.length) { - continue; - } - - var translation = _.last(translations); - - return this.select(translation.id); - } - } - - Collection.prototype.selectNext = function () { - if (!this.selectedTranslation) { - return this.selectFirst(); - } - - for (var i = 0; i < this.resources.length; i++) { - var resource = this.resources[i]; - var translations = this.translations[resource.id] - var current = _.findIndex(translations, this.selectedTranslation); - - if (0 > current) { - continue; - } - - var nextTranslation; - if (translations.length > current + 1) { - nextTranslation = translations[current + 1]; - } else { - var j = 0; - // Find in next (non empty) resources - do { - j++; - var nextResource = this.resources[i + j]; - } while (nextResource && this.translations[nextResource.id].length < 1); - - if (!nextResource) { - return; - } - - nextTranslation = _.first(this.translations[nextResource.id]); - } - - return this.select(nextTranslation.id); - } - } - - Collection.prototype.selectPrevious = function () { - if (!this.selectedTranslation) { - return this.selectLast() - } - - for (var i = 0; i < this.resources.length; i++) { - var resource = this.resources[i]; - var translations = this.translations[resource.id] - var current = _.findIndex(translations, this.selectedTranslation); - - if (0 > current) { - continue; - } - - var previousTranslation; - if (0 <= current - 1) { - previousTranslation = translations[current - 1]; - } else { - var j = 0; - // Find in next (non empty) resources - do { - j++; - var previousResource = this.resources[i - j]; - } while (previousResource && this.translations[previousResource.id].length < 1); - - if (!previousResource) { - return; - } - - previousTranslation = _.last(this.translations[previousResource.id]); - } - - return this.select(previousTranslation.id); - } - } - - return Collection; -} - -})(); diff --git a/src/models/TranslationCommitList.js b/src/models/TranslationCommitList.js new file mode 100644 index 0000000..cdaf22e --- /dev/null +++ b/src/models/TranslationCommitList.js @@ -0,0 +1,82 @@ +(function() { + +'use strict'; + +angular + .module('app') + .factory('TranslationCommitList', TranslationCommitList); + +/** + * @ngInject + */ +function TranslationCommitList($q, TranslationCommitRepository) { + + var List = function (context, filters) { + this.filters = filters; + this.context = context; + + this.items = []; + + // Handle paging + this.page = 0; + this.perPage = 20; + + // Loading state + this.loading = false; + this.complete = false; + } + + List.prototype.reset = function() { + this.items = []; + this.complete = false; + this.page = 0; + } + + List.prototype.loadMore = function() { + var deferred = $q.defer(); + + // Throttle calls & avoid loading when complete + if (this.loading || this.complete) + return deferred.promise; + + // Ensure context is correctly defined + if (!this.context.source || !this.context.target) + return deferred.promise; + + this.loading = true; + + this.busy = true; + this.page++; + + this.filters.page = this.page; + this.filters.per_page = this.perPage; + + TranslationCommitRepository + .findBy(this.context.source, this.context.target, this.filters) + .then(function(items) { + if (items.length < this.perPage) { + this.complete = true; + } + + for (var i = 0; i < items.length; i++) { + var translationCommit = items[i]; + translationCommit.edit_phrase = translationCommit.target_phrase; + this.items.push(translationCommit); + } + + this.loading = false; + deferred.resolve(); + }.bind(this), + function() { + this.complete = true; + this.loading = false; + deferred.resolve(); + }.bind(this)); + + return deferred.promise; + } + + return List; +} + +})(); diff --git a/src/models/TranslationsGroup.js b/src/models/TranslationsGroup.js new file mode 100644 index 0000000..b559f87 --- /dev/null +++ b/src/models/TranslationsGroup.js @@ -0,0 +1,25 @@ +(function() { + +'use strict'; + +angular + .module('app') + .factory('TranslationsGroup', TranslationsGroup); + +/** + * @ngInject + */ +function TranslationsGroup(TranslationCommitList) { + + var Group = function (resource, context, filters) { + this.resource = resource; + this.translations = new TranslationCommitList(context, filters); + }; + + // Group.prototype.fn = function() { + // }; + + return Group; +} + +})(); diff --git a/src/styles/layouts/translate-sidebar.scss b/src/styles/layouts/translate-sidebar.scss index 97751c3..314e983 100644 --- a/src/styles/layouts/translate-sidebar.scss +++ b/src/styles/layouts/translate-sidebar.scss @@ -1,4 +1,6 @@ .l-translate-sidebar { // background: $color-gray-200; height: 100%; + padding-top: 80px; + position: relative; } diff --git a/src/views/translate-phrase.html b/src/views/translate-phrase.html index ce01da2..6a9d23e 100644 --- a/src/views/translate-phrase.html +++ b/src/views/translate-phrase.html @@ -1,75 +1 @@ -
-
- -
- -
-
- Original phrase -
- -

- {{ translationCommit.getSourcePhrase() }} - - - - -

-
- -
- - - - - - -
-
+TRANSLATION PHRASE!! diff --git a/src/views/translate.html b/src/views/translate.html index a59ff0b..0b45f3d 100644 --- a/src/views/translate.html +++ b/src/views/translate.html @@ -40,23 +40,25 @@

-
+

- {{ resource.pathname }} + {{ group.resource.pathname }}

-
    +
-
  • +
  • @@ -172,7 +174,9 @@

  • +
    +