diff --git a/css/style.scss b/css/style.scss index 70a16bd136e..452f17185b6 100644 --- a/css/style.scss +++ b/css/style.scss @@ -83,6 +83,9 @@ * It is assumed that the icon will have the standard width for buttons in * inputs of 34px. However, further adjustments may be needed for the input and * the padding depending on the context where they are used. + * + * The confirm icon can have a sibling loading icon to switch to (by hiding one + * icon and showing the other) while the operation is in progress. */ input[type="text"], input[type="password"] { @@ -114,6 +117,32 @@ input[type="password"] { &:active:not(:disabled) { opacity: 1; } + + + .icon-loading-small { + /* Mimic size set in server for confirm button. */ + width: 34px; + height: 34px; + padding: 7px 6px; + margin-top: 3px; + margin-bottom: 3px; + + position: absolute; + top: 0; + right: 3px; + } + } +} + +.menuitem input[type="text"], +.menuitem input[type="password"] { + & + .icon-confirm + .icon-loading-small { + /* Mimic size set in server for inputs in menu items. */ + min-width: 44px; + max-height: 40px; + margin: 2px 0; + + /* Override padding set in server for icons in menu items. */ + padding: 7px 6px; } } @@ -762,12 +791,10 @@ input[type="password"] { .input-wrapper { position: relative; - input[type="text"] { - & + .icon-confirm { - /* Needed to override an important rule set in the - * server. */ - background-color: transparent !important; - } + .icon-confirm { + /* Needed to override an important rule set in the + * server. */ + background-color: transparent !important; } } .label { @@ -849,18 +876,17 @@ input[type="password"] { text-overflow: ellipsis; } - .icon-confirm.confirm-button { - padding: 12px 21px; - margin: 0; - } - input[type="text"] { padding-right: 44px; + } - & + .icon-confirm { - top: 4px; - right: 0; - } + .icon-confirm, + .icon-loading-small { + top: 4px; + right: 0; + + padding: 12px 21px; + margin: 0; } } } @@ -913,6 +939,18 @@ input[type="password"] { .link-checkbox-label { white-space: nowrap; padding: 12px; + + /* If ".icon-loading-small" is set hide the checkbox and show a + * loading icon instead */ + &.icon-loading-small:before { + background-image: none !important; + background-color: transparent !important; + border-color: transparent !important; + } + &.icon-loading-small:after { + top: 21px; + left: 23px; + } } .button { cursor: pointer; @@ -941,11 +979,14 @@ input[type="password"] { .password-form { position: relative; - .password-confirm { + .password-confirm, + .password-loading { /* Inputs in menu items do not have a right margin, so * it does not need to be compensated. */ right: 0; + } + .password-confirm { /* Needed to override an important rule set in the * server. */ background-color: transparent !important; diff --git a/js/views/callinfoview.js b/js/views/callinfoview.js index 90ca480b329..9108e09b857 100644 --- a/js/views/callinfoview.js +++ b/js/views/callinfoview.js @@ -65,6 +65,7 @@ 'shareLinkOptions': '.share-link-options', 'clipboardButton': '.clipboard-button', 'linkCheckbox': '.link-checkbox', + 'linkCheckboxLabel': '.link-checkbox-label', 'callButton': 'div.call-button', @@ -74,6 +75,7 @@ 'passwordOption': '.password-option', 'passwordInput': '.password-input', 'passwordConfirm': '.password-confirm', + 'passwordLoading': '.password-loading', 'menu': '.password-menu', }, @@ -120,6 +122,12 @@ this._nameEditableTextLabel = new OCA.SpreedMe.Views.EditableTextLabel({ model: this.model, modelAttribute: nameAttribute, + modelSaveOptions: { + wait: true, + error: function() { + OC.Notification.show(t('spreed', 'Error occurred while renaming the room'), {type: 'error'}); + } + }, extraClassNames: 'room-name', labelTagName: 'h2', @@ -243,9 +251,25 @@ * Share link */ toggleLinkCheckbox: function() { - var isPublic = this.ui.linkCheckbox.attr('checked') === 'checked'; + var isPublic = this.ui.linkCheckbox.prop('checked'); - this.model.setPublic(isPublic); + this.ui.linkCheckbox.prop('disabled', true); + this.ui.linkCheckboxLabel.addClass('icon-loading-small'); + + this.model.setPublic(isPublic, { + wait: true, + error: function() { + this.ui.linkCheckbox.prop('checked', !isPublic); + this.ui.linkCheckbox.prop('disabled', false); + this.ui.linkCheckboxLabel.removeClass('icon-loading-small'); + + if (isPublic) { + OC.Notification.show(t('spreed', 'Error occurred while making the room public'), {type: 'error'}); + } else { + OC.Notification.show(t('spreed', 'Error occurred while making the room private'), {type: 'error'}); + } + }.bind(this) + }); }, /** @@ -256,14 +280,28 @@ var newPassword = this.ui.passwordInput.val().trim(); + this.ui.passwordInput.prop('disabled', true); + this.ui.passwordConfirm.addClass('hidden'); + this.ui.passwordLoading.removeClass('hidden'); + + var restoreState = function() { + this.ui.passwordInput.prop('disabled', false); + this.ui.passwordConfirm.removeClass('hidden'); + this.ui.passwordLoading.addClass('hidden'); + }.bind(this); + this.model.setPassword(newPassword, { + wait: true, success: function() { this.ui.passwordInput.val(''); + restoreState(); OC.hideMenus(); }.bind(this), error: function() { + restoreState(); + OC.Notification.show(t('spreed', 'Error occurred while setting password'), {type: 'error'}); - } + }.bind(this) }); }, diff --git a/js/views/editabletextlabel.js b/js/views/editabletextlabel.js index f3e5fe42726..8031a401a31 100644 --- a/js/views/editabletextlabel.js +++ b/js/views/editabletextlabel.js @@ -51,6 +51,11 @@ * "inputPlaceholder" and "buttonTitle" can be used to customize some * elements of the view. * + * It is recommended, although not strictly needed, to wait for the server + * response before setting the new attribute value in the model; otherwise, + * in case of failure the label will show the new value of the attribute + * even if it was not set in the server. + * * After initialization, and once the view has been rendered, the * "modelAttribute" and "labelPlaceholder" options can be updated using the * "setModelAttribute" and "setLabelPlaceholder" methods. @@ -72,6 +77,7 @@ inputWrapper: '.input-wrapper', input: 'input.username', confirmButton: '.confirm-button', + loadingIcon: '.icon-loading-small', }, events: { @@ -208,10 +214,21 @@ return; } + this.ui.input.prop('disabled', true); + this.ui.confirmButton.addClass('hidden'); + this.ui.loadingIcon.removeClass('hidden'); + + var restoreState = function() { + this.ui.input.prop('disabled', false); + this.ui.confirmButton.removeClass('hidden'); + this.ui.loadingIcon.addClass('hidden'); + }.bind(this); + // TODO This should show the error message instead of just hiding // the input without changes. var hideInputOnValidationError = function(/*model, error*/) { this.hideInput(); + restoreState(); }.bind(this); this.model.listenToOnce(this.model, 'invalid', hideInputOnValidationError); @@ -220,6 +237,7 @@ this.model.stopListening(this.model, 'invalid', hideInputOnValidationError); this.hideInput(); + restoreState(); if (this.modelSaveOptions && _.isFunction(this.modelSaveOptions.success)) { this.modelSaveOptions.success.apply(this, arguments); @@ -229,6 +247,11 @@ this.model.stopListening(this.model, 'invalid', hideInputOnValidationError); this.hideInput(); + restoreState(); + + if (this.modelSaveOptions && _.isFunction(this.modelSaveOptions.error)) { + this.modelSaveOptions.error.apply(this, arguments); + } }, this); this.model.save(this.modelAttribute, newText, options); diff --git a/js/views/templates.js b/js/views/templates.js index f88adf8c804..a6295e8d3bc 100644 --- a/js/views/templates.js +++ b/js/views/templates.js @@ -71,9 +71,9 @@ templates['callinfoview'] = template({"1":function(container,depth0,helpers,part + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasPassword : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(10, data, 0),"data":data})) != null ? stack1 : "") + "\">\n
\n \n"; + + "\">\n \n \n \n \n \n \n \n \n"; },"8":function(container,depth0,helpers,partials,data) { return "icon-password"; },"10":function(container,depth0,helpers,partials,data) { @@ -210,7 +210,7 @@ templates['editabletextlabel'] = template({"1":function(container,depth0,helpers + container.escapeExpression(((helper = (helper = helpers.inputValue || (depth0 != null ? depth0.inputValue : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"inputValue","hash":{},"data":data}) : helper))) + "\" " + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.inputPlaceholder : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ">\n \n\n"; + + ">\n \n \n\n"; },"5":function(container,depth0,helpers,partials,data) { var helper; diff --git a/js/views/templates/callinfoview.handlebars b/js/views/templates/callinfoview.handlebars index b39c4cd07b7..0af592581fc 100644 --- a/js/views/templates/callinfoview.handlebars +++ b/js/views/templates/callinfoview.handlebars @@ -23,9 +23,10 @@