diff --git a/pootle/static/css/editor.css b/pootle/static/css/editor.css index 8acb3c3c4c8..be6dbbbf5e8 100644 --- a/pootle/static/css/editor.css +++ b/pootle/static/css/editor.css @@ -158,6 +158,20 @@ textarea.translation, margin: 180px auto; } +.editor-progress-container +{ + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 99; +} + +.editor-progress +{ + height: 5px; +} + .editor-overlay { background-color: rgba(255, 255, 255, 0.8); diff --git a/pootle/static/js/editor/app.js b/pootle/static/js/editor/app.js index 69996a9885c..b8e9becc114 100644 --- a/pootle/static/js/editor/app.js +++ b/pootle/static/js/editor/app.js @@ -26,6 +26,8 @@ import cx from 'classnames'; import Levenshtein from 'levenshtein'; import mousetrap from 'mousetrap'; import assign from 'object-assign'; +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; import UnitAPI from 'api/UnitAPI'; import cookie from 'utils/cookie'; @@ -51,6 +53,10 @@ import ReactEditor from './index'; // be the actual entry point, entirely superseding the `app` module. PTL.reactEditor = ReactEditor; +NProgress.configure({ + parent: '#js-editor-progress', + showSpinner: false, +}); const CTX_STEP = 1; @@ -122,20 +128,20 @@ PTL.editor = { this.setActiveUnit = debounce((body, newUnit) => { this.fetchUnits().always(() => { - UnitAPI.fetchUnit(newUnit.id, body) - .then( - (data) => { - this.setEditUnit(data); - this.renderUnit(); - }, - this.error - ); + this.unitAPI({ + method: 'fetchUnit', + params: { uId: newUnit.id, body }, + success: (data) => { + this.setEditUnit(data); + this.renderUnit(); + }, + error: this.error, + }); }); }, 250); /* Cached elements */ this.backToBrowserEl = q('.js-back-to-browser'); - this.$editorActivity = $('#js-editor-act'); this.$editorBody = $('.js-editor-body'); this.editorTableEl = q('.js-editor-table'); this.$filterStatus = $('#js-filter-status'); @@ -166,8 +172,8 @@ PTL.editor = { this.isUnitDirty = false; - this.isLoading = true; this.showActivity(); + this.isLoading = true; this.fetchingOffsets = []; @@ -398,24 +404,6 @@ PTL.editor = { this.unitIndex(e); }); - /* XHR activity indicator */ - $(document).ajaxStart(() => { - clearTimeout(this.delayedActivityTimer); - if (this.isLoading) { - return; - } - - this.delayedActivityTimer = setTimeout(() => { - this.showActivity(); - }, 3000); - }); - $(document).ajaxStop(() => { - clearTimeout(this.delayedActivityTimer); - if (!this.isLoading) { - this.hideActivity(); - } - }); - /* Load MT providers */ this.settings.mt.forEach((provider) => { require.ensure([], () => { @@ -642,7 +630,6 @@ PTL.editor = { this.isUnitDirty = false; this.keepState = false; this.isLoading = false; - this.hideActivity(); }, /* Things to do when no results are returned */ @@ -663,6 +650,11 @@ PTL.editor = { return true; }, + unitAPI({ method, params, success, error, always }) { + this.showActivity(); + return UnitAPI[method](params).then(success, error).always(always, () => this.hideActivity()); + }, + /* * Text utils @@ -1028,11 +1020,18 @@ PTL.editor = { showActivity() { this.hideMsg(); - this.$editorActivity.spin().fadeIn(300); + if (this.isLoading) { + return; + } + clearTimeout(this.delayedActivityTimer); + this.delayedActivityTimer = setTimeout(() => NProgress.start(), 2000); }, hideActivity() { - this.$editorActivity.spin(false).fadeOut(300); + if (!this.isLoading) { + clearTimeout(this.delayedActivityTimer); + NProgress.done(); + } }, /* Displays an informative message */ @@ -1482,11 +1481,13 @@ PTL.editor = { if (previousUids.length > 0) { reqData.previous_uids = previousUids; } - return UnitAPI.fetchUnits(reqData) - .then( - (data) => this.storeUnitData(data, { isInitial: initial }), - this.error - ).always(() => this.markAsFetched(offsetToFetch)); + return this.unitAPI({ + method: 'fetchUnits', + params: { body: reqData }, + success: (data) => this.storeUnitData(data, { isInitial: initial }), + error: this.error, + always: () => this.markAsFetched(offsetToFetch), + }); } /* eslint-disable new-cap */ return $.Deferred((deferred) => deferred.reject(false)); @@ -1627,11 +1628,12 @@ PTL.editor = { }; assign(body, suggData); } - UnitAPI.addTranslation(this.units.getCurrent().id, body) - .then( - (data) => this.processSubmission(data), - this.error - ); + this.unitAPI({ + method: 'addTranslation', + params: { uId: this.units.getCurrent().id, body }, + success: (data) => this.processSubmission(data), + error: this.error, + }); }, processSubmission(data) { @@ -1666,11 +1668,12 @@ PTL.editor = { const body = assign({}, this.getValueStateData(ReactEditor.stateValues), this.getReqData(), captchaCallbacks); - UnitAPI.addSuggestion(this.units.getCurrent().id, body) - .then( - (data) => this.processSuggestion(data), - this.error - ); + this.unitAPI({ + method: 'addSuggestion', + params: { uId: this.units.getCurrent().id, body }, + success: (data) => this.processSuggestion(data), + error: this.error, + }); }, processSuggestion() { @@ -1951,14 +1954,15 @@ PTL.editor = { /* Gets more context units */ moreContext(amount = CTX_STEP) { - return ( - UnitAPI.getContext(this.units.getCurrent().id, - { gap: this.ctxGap, qty: amount }) - .then( - (data) => this.handleContextSuccess(data), - this.error - ) - ); + return this.unitAPI({ + method: 'getContext', + params: { + uId: this.units.getCurrent().id, + body: { gap: this.ctxGap, qty: amount }, + }, + success: (data) => this.handleContextSuccess(data), + error: this.error, + }); }, /* Shrinks context lines */ @@ -2047,11 +2051,12 @@ PTL.editor = { e.preventDefault(); this.updateCommentDefaultProperties(); - UnitAPI.addComment(this.units.getCurrent().id, $(e.target).serializeObject()) - .then( - (data) => this.processAddComment(data), - this.error - ); + this.unitAPI({ + method: 'addComment', + params: { uId: this.units.getCurrent().id, body: $(e.target).serializeObject() }, + success: (data) => this.processAddComment(data), + error: this.error, + }); }, processAddComment(data) { @@ -2072,11 +2077,12 @@ PTL.editor = { removeComment(e) { e.preventDefault(); - UnitAPI.removeComment(this.units.getCurrent().id) - .then( - () => $('.js-comment-first').fadeOut(200), - this.error - ); + this.unitAPI({ + method: 'removeComment', + params: { uId: this.units.getCurrent().id }, + success: () => $('.js-comment-first').fadeOut(200), + error: this.error, + }); }, @@ -2092,15 +2098,12 @@ PTL.editor = { return; } - const $node = $('.translate-container'); - $node.spin(); - - UnitAPI.getTimeline(this.units.getCurrent().id) - .then( - (data) => this.renderTimeline(data), - this.error - ) - .always(() => $node.spin(false)); + this.unitAPI({ + method: 'getTimeline', + params: { uId: this.units.getCurrent().id }, + success: (data) => this.renderTimeline(data), + error: this.error, + }); }, renderTimeline(data) { @@ -2268,11 +2271,12 @@ PTL.editor = { }, rejectSuggestion(suggId, { requestData = {} } = {}) { - UnitAPI.rejectSuggestion(this.units.getCurrent().id, suggId, requestData) - .then( - (data) => this.processRejectSuggestion(data, suggId), - this.error - ); + this.unitAPI({ + method: 'rejectSuggestion', + params: { uId: this.units.getCurrent().id, suggId, body: requestData }, + success: (data) => this.processRejectSuggestion(data, suggId), + error: this.error, + }); }, processRejectSuggestion(data, suggId) { @@ -2301,11 +2305,12 @@ PTL.editor = { }, acceptSuggestion(suggId, { requestData = {}, skipToNext = false } = {}) { - UnitAPI.acceptSuggestion(this.units.getCurrent().id, suggId, requestData) - .then( - (data) => this.processAcceptSuggestion(data, suggId, skipToNext), - this.error - ); + this.unitAPI({ + method: 'acceptSuggestion', + params: { uId: this.units.getCurrent().id, suggId, body: requestData }, + success: (data) => this.processAcceptSuggestion(data, suggId, skipToNext), + error: this.error, + }); }, processAcceptSuggestion(data, suggId, skipToNext) { @@ -2344,11 +2349,12 @@ PTL.editor = { const isFalsePositive = $check.hasClass('false-positive'); const opts = isFalsePositive ? null : { mute: 1 }; - UnitAPI.toggleCheck(this.units.getCurrent().id, checkId, opts) - .then( - () => this.processToggleCheck(checkId, isFalsePositive), - this.error - ); + this.unitAPI({ + method: 'toggleCheck', + params: { uId: this.units.getCurrent().id, checkId, body: opts }, + success: () => this.processToggleCheck(checkId, isFalsePositive), + error: this.error, + }); }, processToggleCheck(checkId, isFalsePositive) { diff --git a/pootle/static/js/package.json b/pootle/static/js/package.json index f88b5283615..104c50e3b46 100644 --- a/pootle/static/js/package.json +++ b/pootle/static/js/package.json @@ -42,6 +42,7 @@ "diff-match-patch": "^1.0.0", "imports-loader": "^0.6.3", "mousetrap": "^1.5.3", + "nprogress": "^0.2.0", "object-assign": "^2.0.0", "react": "^0.14.6", "react-addons-pure-render-mixin": "^0.14.6", diff --git a/pootle/static/js/shared/api/UnitAPI.js b/pootle/static/js/shared/api/UnitAPI.js index 0923efb20f6..c35c786f6da 100644 --- a/pootle/static/js/shared/api/UnitAPI.js +++ b/pootle/static/js/shared/api/UnitAPI.js @@ -14,14 +14,14 @@ const UnitAPI = { apiRoot: PTL.unitApiRoot, - fetchUnits(body) { + fetchUnits({ body }) { return fetch({ body, url: this.apiRoot, }); }, - fetchUnit(uId, body = {}) { + fetchUnit({ uId, body = {} }) { return fetch({ body, queue: 'unitWidget', @@ -29,7 +29,7 @@ const UnitAPI = { }); }, - addTranslation(uId, body) { + addTranslation({ uId, body }) { return fetch({ body, method: 'POST', @@ -37,20 +37,20 @@ const UnitAPI = { }); }, - getContext(uId, body) { + getContext({ uId, body }) { return fetch({ body, url: `${this.apiRoot}${uId}/context/`, }); }, - getTimeline(uId) { + getTimeline({ uId }) { return fetch({ url: `${this.apiRoot}${uId}/timeline/`, }); }, - addComment(uId, body) { + addComment({ uId, body }) { return fetch({ body, method: 'POST', @@ -58,7 +58,7 @@ const UnitAPI = { }); }, - removeComment(uId) { + removeComment({ uId }) { return fetch({ method: 'DELETE', url: `${this.apiRoot}${uId}/comment/`, @@ -67,7 +67,7 @@ const UnitAPI = { /* Unit suggestions */ - addSuggestion(uId, body) { + addSuggestion({ uId, body }) { return fetch({ body, method: 'POST', @@ -75,7 +75,7 @@ const UnitAPI = { }); }, - acceptSuggestion(uId, suggId, body) { + acceptSuggestion({ uId, suggId, body }) { return fetch({ body, method: 'POST', @@ -83,7 +83,7 @@ const UnitAPI = { }); }, - rejectSuggestion(uId, suggId, body) { + rejectSuggestion({ uId, suggId, body }) { return fetch({ body, method: 'DELETE', @@ -93,7 +93,7 @@ const UnitAPI = { /* Quality checks */ - toggleCheck(uId, checkId, body = {}) { + toggleCheck({ uId, checkId, body = {} }) { return fetch({ body, method: 'POST', diff --git a/pootle/templates/editor/_editor.html b/pootle/templates/editor/_editor.html index e1b6ec83152..22c4e089cf9 100644 --- a/pootle/templates/editor/_editor.html +++ b/pootle/templates/editor/_editor.html @@ -1,8 +1,6 @@ {% load locale %}