Adding functionlity to upload/remove profile image.
TNL-1538
This commit is contained in:
committed by
Andy Armstrong
parent
938dcce061
commit
adf111e4d5
@@ -70,6 +70,10 @@ def learner_profile_context(logged_in_username, profile_username, user_is_staff)
|
||||
'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'],
|
||||
'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}),
|
||||
'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}),
|
||||
'profile_image_upload_url': reverse('profile_image_upload', kwargs={'username': profile_username}),
|
||||
'profile_image_remove_url': reverse('profile_image_remove', kwargs={'username': profile_username}),
|
||||
'profile_image_max_bytes': settings.PROFILE_IMAGE_MAX_BYTES,
|
||||
'profile_image_min_bytes': settings.PROFILE_IMAGE_MIN_BYTES,
|
||||
'account_settings_page_url': reverse('account_settings'),
|
||||
'has_preferences_access': (logged_in_username == profile_username or user_is_staff),
|
||||
'own_profile': (logged_in_username == profile_username),
|
||||
|
||||
@@ -3,6 +3,8 @@ define(['underscore'], function(_) {
|
||||
|
||||
var USER_ACCOUNTS_API_URL = '/api/user/v0/accounts/student';
|
||||
var USER_PREFERENCES_API_URL = '/api/user/v0/preferences/student';
|
||||
var IMAGE_UPLOAD_API_URL = '/api/profile_images/v0/staff/upload';
|
||||
var IMAGE_REMOVE_API_URL = '/api/profile_images/v0/staff/remove';
|
||||
|
||||
var USER_ACCOUNTS_DATA = {
|
||||
username: 'student',
|
||||
@@ -27,9 +29,12 @@ define(['underscore'], function(_) {
|
||||
['0', 'Option 0'],
|
||||
['1', 'Option 1'],
|
||||
['2', 'Option 2'],
|
||||
['3', 'Option 3'],
|
||||
['3', 'Option 3']
|
||||
];
|
||||
|
||||
var IMAGE_MAX_BYTES = 1024 * 1024;
|
||||
var IMAGE_MIN_BYTES = 100;
|
||||
|
||||
var expectLoadingIndicatorIsVisible = function (view, visible) {
|
||||
if (visible) {
|
||||
expect($('.ui-loading-indicator')).not.toHaveClass('is-hidden');
|
||||
@@ -92,6 +97,10 @@ define(['underscore'], function(_) {
|
||||
return {
|
||||
USER_ACCOUNTS_API_URL: USER_ACCOUNTS_API_URL,
|
||||
USER_PREFERENCES_API_URL: USER_PREFERENCES_API_URL,
|
||||
IMAGE_UPLOAD_API_URL: IMAGE_UPLOAD_API_URL,
|
||||
IMAGE_REMOVE_API_URL: IMAGE_REMOVE_API_URL,
|
||||
IMAGE_MAX_BYTES: IMAGE_MAX_BYTES,
|
||||
IMAGE_MIN_BYTES: IMAGE_MIN_BYTES,
|
||||
USER_ACCOUNTS_DATA: USER_ACCOUNTS_DATA,
|
||||
USER_PREFERENCES_DATA: USER_PREFERENCES_DATA,
|
||||
FIELD_OPTIONS: FIELD_OPTIONS,
|
||||
|
||||
@@ -6,11 +6,12 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
'js/student_account/models/user_preferences_model',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields'
|
||||
'js/student_account/views/account_settings_fields',
|
||||
'js/views/message_banner'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews,
|
||||
UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
|
||||
AccountSettingsFieldViews) {
|
||||
AccountSettingsFieldViews, MessageBannerView) {
|
||||
'use strict';
|
||||
|
||||
describe("edx.user.LearnerProfileView", function () {
|
||||
@@ -46,6 +47,21 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
accountSettingsPageUrl: '/account/settings/'
|
||||
});
|
||||
|
||||
var messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
|
||||
var profileImageFieldView = new FieldsView.ImageFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "profile_image",
|
||||
editable: editable,
|
||||
messageView: messageView,
|
||||
imageMaxBytes: Helpers.IMAGE_MAX_BYTES,
|
||||
imageMinBytes: Helpers.IMAGE_MIN_BYTES,
|
||||
imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL,
|
||||
imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL
|
||||
});
|
||||
|
||||
var usernameFieldView = new FieldViews.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "username",
|
||||
@@ -107,10 +123,12 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="wrapper-profile"><div class="ui-loading-indicator"><p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">Loading</span></p></div><div class="ui-loading-error is-hidden"><i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i><span class="copy">An error occurred. Please reload the page.</span></div></div>');
|
||||
setFixtures('<div class="message-banner"></div><div class="wrapper-profile"><div class="ui-loading-indicator"><p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">Loading</span></p></div><div class="ui-loading-error is-hidden"><i class="fa fa-exclamation-triangle message-error" aria-hidden=true></i><span class="copy">An error occurred. Please reload the page.</span></div></div>');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_readonly');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_dropdown');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_textarea');
|
||||
TemplateHelpers.installTemplate('templates/fields/field_image');
|
||||
TemplateHelpers.installTemplate('templates/message_banner');
|
||||
TemplateHelpers.installTemplate('templates/student_profile/learner_profile');
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
bio: null,
|
||||
language_proficiencies: [],
|
||||
requires_parental_consent: true,
|
||||
profile_image: null,
|
||||
default_public_account_fields: []
|
||||
},
|
||||
|
||||
@@ -39,6 +40,15 @@
|
||||
return response;
|
||||
},
|
||||
|
||||
hasProfileImage: function () {
|
||||
var profile_image = this.get('profile_image');
|
||||
return (_.isObject(profile_image) && profile_image['has_image'] === true);
|
||||
},
|
||||
|
||||
profileImageUrl: function () {
|
||||
return this.get('profile_image')['image_url_large'];
|
||||
},
|
||||
|
||||
isAboveMinimumAge: function() {
|
||||
var isBirthDefined = !(_.isUndefined(this.get('year_of_birth')) || _.isNull(this.get('year_of_birth')));
|
||||
return isBirthDefined && !(this.get("requires_parental_consent"));
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
'js/views/fields',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields'
|
||||
], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView,
|
||||
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews) {
|
||||
'js/student_account/views/account_settings_fields',
|
||||
'js/views/message_banner'
|
||||
], function (gettext, $, _, Backbone, AccountSettingsModel, AccountPreferencesModel, FieldsView,
|
||||
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageBannerView) {
|
||||
|
||||
return function (options) {
|
||||
|
||||
@@ -25,6 +26,10 @@
|
||||
|
||||
var editable = options.own_profile ? 'toggle' : 'never';
|
||||
|
||||
var messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
|
||||
var accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({
|
||||
model: accountPreferencesModel,
|
||||
required: true,
|
||||
@@ -40,6 +45,17 @@
|
||||
accountSettingsPageUrl: options.account_settings_page_url
|
||||
});
|
||||
|
||||
var profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "profile_image",
|
||||
editable: editable === 'toggle',
|
||||
messageView: messageView,
|
||||
imageMaxBytes: options['profile_image_max_bytes'],
|
||||
imageMinBytes: options['profile_image_min_bytes'],
|
||||
imageUploadUrl: options['profile_image_upload_url'],
|
||||
imageRemoveUrl: options['profile_image_remove_url']
|
||||
});
|
||||
|
||||
var usernameFieldView = new FieldsView.ReadonlyFieldView({
|
||||
model: accountSettingsModel,
|
||||
valueAttribute: "username",
|
||||
@@ -47,7 +63,6 @@
|
||||
});
|
||||
|
||||
var sectionOneFieldViews = [
|
||||
usernameFieldView,
|
||||
new FieldsView.DropdownFieldView({
|
||||
model: accountSettingsModel,
|
||||
required: true,
|
||||
@@ -94,6 +109,7 @@
|
||||
accountSettingsModel: accountSettingsModel,
|
||||
preferencesModel: accountPreferencesModel,
|
||||
accountPrivacyFieldView: accountPrivacyFieldView,
|
||||
profileImageFieldView: profileImageFieldView,
|
||||
usernameFieldView: usernameFieldView,
|
||||
sectionOneFieldViews: sectionOneFieldViews,
|
||||
sectionTwoFieldViews: sectionTwoFieldViews
|
||||
|
||||
@@ -48,6 +48,64 @@
|
||||
}
|
||||
});
|
||||
|
||||
LearnerProfileFieldViews.ProfileImageFieldView = FieldViews.ImageFieldView.extend({
|
||||
|
||||
imageUrl: function () {
|
||||
return this.model.profileImageUrl();
|
||||
},
|
||||
|
||||
imageAltText: function () {
|
||||
return interpolate_text(gettext("Profile image for {username}"), {username: this.model.get('username')});
|
||||
},
|
||||
|
||||
imageChangeSucceeded: function (e, data) {
|
||||
var view = this;
|
||||
// Update model to get the latest urls of profile image.
|
||||
this.model.fetch().done(function () {
|
||||
view.setCurrentStatus('');
|
||||
}).fail(function () {
|
||||
view.showErrorMessage(view.errorMessage);
|
||||
});
|
||||
},
|
||||
|
||||
imageChangeFailed: function (e, data) {
|
||||
this.setCurrentStatus('');
|
||||
if (_.contains([400, 404], data.jqXHR.status)) {
|
||||
try {
|
||||
var errors = JSON.parse(data.jqXHR.responseText);
|
||||
this.showErrorMessage(errors.user_message);
|
||||
} catch (error) {
|
||||
this.showErrorMessage(this.errorMessage);
|
||||
}
|
||||
} else {
|
||||
this.showErrorMessage(this.errorMessage);
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
|
||||
showErrorMessage: function (message) {
|
||||
this.options.messageView.showMessage(message);
|
||||
},
|
||||
|
||||
isEditingAllowed: function () {
|
||||
return this.model.isAboveMinimumAge();
|
||||
},
|
||||
|
||||
isShowingPlaceholder: function () {
|
||||
return !this.model.hasProfileImage();
|
||||
},
|
||||
|
||||
clickedRemoveButton: function (e, data) {
|
||||
this.options.messageView.hideMessage();
|
||||
this._super(e, data);
|
||||
},
|
||||
|
||||
fileSelected: function (e, data) {
|
||||
this.options.messageView.hideMessage();
|
||||
this._super(e, data);
|
||||
}
|
||||
});
|
||||
|
||||
return LearnerProfileFieldViews;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
username: this.options.accountSettingsModel.get('username'),
|
||||
profilePhoto: 'http://www.teachthought.com/wp-content/uploads/2012/07/edX-120x120.jpg',
|
||||
ownProfile: this.options.ownProfile,
|
||||
showFullProfile: this.showFullProfile()
|
||||
}));
|
||||
@@ -48,6 +47,11 @@
|
||||
|
||||
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
|
||||
|
||||
var imageView = this.options.profileImageFieldView;
|
||||
imageView.undelegateEvents();
|
||||
this.$('.profile-image-field').append(imageView.render().el);
|
||||
imageView.delegateEvents();
|
||||
|
||||
if (this.showFullProfile()) {
|
||||
_.each(this.options.sectionOneFieldViews, function (fieldView) {
|
||||
fieldView.undelegateEvents();
|
||||
|
||||
@@ -499,6 +499,207 @@
|
||||
}
|
||||
});
|
||||
|
||||
FieldViews.ImageFieldView = FieldViews.FieldView.extend({
|
||||
|
||||
fieldType: 'image',
|
||||
|
||||
templateSelector: '#field_image-tpl',
|
||||
uploadButtonSelector: '.upload-button-input',
|
||||
|
||||
titleAdd: gettext("Upload an image"),
|
||||
titleEdit: gettext("Change image"),
|
||||
titleRemove: gettext("Remove"),
|
||||
|
||||
titleUploading: gettext("Uploading"),
|
||||
titleRemoving: gettext("Removing"),
|
||||
|
||||
titleImageAlt: '',
|
||||
|
||||
iconUpload: '<i class="icon fa fa-camera" aria-hidden="true"></i>',
|
||||
iconRemove: '<i class="icon fa fa-remove" aria-hidden="true"></i>',
|
||||
iconProgress: '<i class="icon fa fa-spinner fa-pulse fa-spin" aria-hidden="true"></i>',
|
||||
|
||||
errorMessage: gettext("An error has occurred. Refresh the page, and then try again."),
|
||||
|
||||
events: {
|
||||
'click .u-field-upload-button': 'clickedUploadButton',
|
||||
'click .u-field-remove-button': 'clickedRemoveButton'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this._super(options);
|
||||
_.bindAll(this, 'render', 'imageChangeSucceeded', 'imageChangeFailed', 'fileSelected',
|
||||
'watchForPageUnload', 'onBeforeUnload');
|
||||
this.listenTo(this.model, "change:" + this.options.valueAttribute, this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
id: this.options.valueAttribute,
|
||||
imageUrl: _.result(this, 'imageUrl'),
|
||||
imageAltText: _.result(this, 'imageAltText'),
|
||||
uploadButtonIcon: _.result(this, 'iconUpload'),
|
||||
uploadButtonTitle: _.result(this, 'uploadButtonTitle'),
|
||||
removeButtonIcon: _.result(this, 'iconRemove'),
|
||||
removeButtonTitle: _.result(this, 'removeButtonTitle')
|
||||
}));
|
||||
this.updateButtonsVisibility();
|
||||
this.watchForPageUnload();
|
||||
return this;
|
||||
},
|
||||
|
||||
showErrorMessage: function () {
|
||||
},
|
||||
|
||||
imageUrl: function () {
|
||||
return '';
|
||||
},
|
||||
|
||||
uploadButtonTitle: function () {
|
||||
if (this.isShowingPlaceholder()) {
|
||||
return _.result(this, 'titleAdd')
|
||||
} else {
|
||||
return _.result(this, 'titleEdit')
|
||||
}
|
||||
},
|
||||
|
||||
removeButtonTitle: function () {
|
||||
return this.titleRemove;
|
||||
},
|
||||
|
||||
isEditingAllowed: function () {
|
||||
return true
|
||||
},
|
||||
|
||||
isShowingPlaceholder: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
setUploadButtonVisibility: function (state) {
|
||||
this.$('.u-field-upload-button').css('display', state);
|
||||
},
|
||||
|
||||
setRemoveButtonVisibility: function (state) {
|
||||
this.$('.u-field-remove-button').css('display', state);
|
||||
},
|
||||
|
||||
updateButtonsVisibility: function () {
|
||||
if (!this.isEditingAllowed() || !this.options.editable) {
|
||||
this.setUploadButtonVisibility('none');
|
||||
}
|
||||
|
||||
if (this.isShowingPlaceholder() || !this.options.editable) {
|
||||
this.setRemoveButtonVisibility('none');
|
||||
}
|
||||
},
|
||||
|
||||
clickedUploadButton: function () {
|
||||
$(this.uploadButtonSelector).fileupload({
|
||||
url: this.options.imageUploadUrl,
|
||||
type: 'POST',
|
||||
add: this.fileSelected,
|
||||
done: this.imageChangeSucceeded,
|
||||
fail: this.imageChangeFailed
|
||||
});
|
||||
},
|
||||
|
||||
clickedRemoveButton: function () {
|
||||
var view = this;
|
||||
this.setCurrentStatus('removing');
|
||||
this.setUploadButtonVisibility('none');
|
||||
this.showRemovalInProgressMessage();
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.options.imageRemoveUrl,
|
||||
success: function (data, status, xhr) {
|
||||
view.imageChangeSucceeded();
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
view.imageChangeFailed();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
imageChangeSucceeded: function (e, data) {
|
||||
this.render();
|
||||
},
|
||||
|
||||
imageChangeFailed: function (e, data) {
|
||||
},
|
||||
|
||||
fileSelected: function (e, data) {
|
||||
if (this.validateImageSize(data.files[0].size)) {
|
||||
data.formData = {file: data.files[0]};
|
||||
this.setCurrentStatus('uploading');
|
||||
this.setRemoveButtonVisibility('none');
|
||||
this.showUploadInProgressMessage();
|
||||
data.submit();
|
||||
}
|
||||
},
|
||||
|
||||
validateImageSize: function (imageBytes) {
|
||||
var humanReadableSize;
|
||||
if (imageBytes < this.options.imageMinBytes) {
|
||||
humanReadableSize = this.bytesToHumanReadable(this.options.imageMinBytes);
|
||||
this.showErrorMessage(interpolate_text(gettext("Your image must be at least {size} in size."), {size: humanReadableSize}));
|
||||
return false;
|
||||
} else if (imageBytes > this.options.imageMaxBytes) {
|
||||
humanReadableSize = this.bytesToHumanReadable(this.options.imageMaxBytes);
|
||||
this.showErrorMessage(interpolate_text(gettext("Your image must be smaller than {size} in size."), {size: humanReadableSize}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
showUploadInProgressMessage: function () {
|
||||
this.$('.u-field-upload-button').css('opacity', 1);
|
||||
this.$('.upload-button-icon').html(this.iconProgress);
|
||||
this.$('.upload-button-title').html(this.titleUploading);
|
||||
},
|
||||
|
||||
showRemovalInProgressMessage: function () {
|
||||
this.$('.u-field-remove-button').css('opacity', 1);
|
||||
this.$('.remove-button-icon').html(this.iconProgress);
|
||||
this.$('.remove-button-title').html(this.titleRemoving);
|
||||
},
|
||||
|
||||
setCurrentStatus: function (status) {
|
||||
this.$('.image-wrapper').attr('data-status', status);
|
||||
},
|
||||
|
||||
getCurrentStatus: function () {
|
||||
return this.$('.image-wrapper').attr('data-status');
|
||||
},
|
||||
|
||||
inProgress: function() {
|
||||
var status = this.getCurrentStatus();
|
||||
return _.isUndefined(status) ? false : true;
|
||||
},
|
||||
|
||||
watchForPageUnload: function () {
|
||||
$(window).on('beforeunload', this.onBeforeUnload);
|
||||
},
|
||||
|
||||
onBeforeUnload: function () {
|
||||
var status = this.getCurrentStatus();
|
||||
if (status === 'uploading') {
|
||||
return gettext("Upload is in progress. To avoid errors, stay on this page until the process is complete.");
|
||||
} else if (status === 'removing') {
|
||||
return gettext("Removal is in progress. To avoid errors, stay on this page until the process is complete.");
|
||||
}
|
||||
},
|
||||
|
||||
bytesToHumanReadable: function (size) {
|
||||
var units = ['Bytes', 'KB', 'MB'];
|
||||
var i = 0;
|
||||
while(size >= 1024) {
|
||||
size /= 1024;
|
||||
++i;
|
||||
}
|
||||
return size.toFixed(1)*1 + ' ' + units[i];
|
||||
}
|
||||
});
|
||||
|
||||
return FieldViews;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
32
lms/static/js/views/message_banner.js
Normal file
32
lms/static/js/views/message_banner.js
Normal file
@@ -0,0 +1,32 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'jquery', 'underscore', 'backbone'
|
||||
], function (gettext, $, _, Backbone) {
|
||||
|
||||
var MessageBannerView = Backbone.View.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
this.template = _.template($('#message_banner-tpl').text());
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template({
|
||||
message: this.message
|
||||
}));
|
||||
return this;
|
||||
},
|
||||
|
||||
showMessage: function (message) {
|
||||
this.message = message;
|
||||
this.render();
|
||||
},
|
||||
|
||||
hideMessage: function () {
|
||||
this.$el.html('');
|
||||
}
|
||||
});
|
||||
|
||||
return MessageBannerView;
|
||||
})
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -7,7 +7,7 @@
|
||||
// * +Settings Section
|
||||
|
||||
.view-profile {
|
||||
$profile-photo-dimension: 120px;
|
||||
$profile-image-dimension: 120px;
|
||||
|
||||
.content-wrapper {
|
||||
background-color: $white;
|
||||
@@ -23,6 +23,75 @@
|
||||
width: ($baseline*5);
|
||||
}
|
||||
|
||||
.profile-image-field {
|
||||
@include float(left);
|
||||
|
||||
button {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-field-image {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
width: $profile-image-dimension;
|
||||
position: relative;
|
||||
|
||||
.image-frame {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.u-field-upload-button {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
|
||||
i {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-button-icon, .upload-button-title {
|
||||
text-align: center;
|
||||
transform: translateY(45px);
|
||||
display: block;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.upload-button-input {
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.u-field-remove-button {
|
||||
width: 120px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.u-field-upload-button, .u-field-remove-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-profile {
|
||||
min-height: 200px;
|
||||
|
||||
@@ -77,14 +146,6 @@
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
margin-top: ($baseline*1.5);
|
||||
|
||||
.profile-photo {
|
||||
@include float(left);
|
||||
height: $profile-photo-dimension;
|
||||
width: $profile-photo-dimension;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-section-one-fields {
|
||||
|
||||
15
lms/templates/fields/field_image.underscore
Normal file
15
lms/templates/fields/field_image.underscore
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="image-wrapper">
|
||||
<img class="image-frame" src="<%- gettext(imageUrl) %>" alt="<%=imageAltText%>"/>
|
||||
<div class="u-field-actions">
|
||||
<label class="u-field-upload-button">
|
||||
<span class="upload-button-icon" aria-hidden="true"><%= uploadButtonIcon %></span>
|
||||
<span class="upload-button-title" aria-live="polite"><%= uploadButtonTitle %></span>
|
||||
<input class="upload-button-input" type="file" name="<%= id %>"/>
|
||||
</label>
|
||||
|
||||
<button class="u-field-remove-button" type="button">
|
||||
<span class="remove-button-icon" aria-hidden="true"><%= removeButtonIcon %></span>
|
||||
<span class="remove-button-title" aria-live="polite"><%= removeButtonTitle %></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
9
lms/templates/message_banner.underscore
Normal file
9
lms/templates/message_banner.underscore
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="wrapper-msg urgency-high">
|
||||
<div class="msg">
|
||||
<div class="msg-content">
|
||||
<div class="copy">
|
||||
<p><%-message%></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,27 +10,36 @@
|
||||
<%block name="bodyclass">view-profile</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["field_dropdown", "field_textarea", "field_readonly"]:
|
||||
% for template_name in ["field_dropdown", "field_image", "field_textarea", "field_readonly"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="fields/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["learner_profile",]:
|
||||
% for template_name in ["learner_profile"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_profile/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["message_banner"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<div class="message-banner" aria-live="polite"></div>
|
||||
<div class="wrapper-profile">
|
||||
<div class="ui-loading-indicator">
|
||||
<p><span class="spin"><i class="icon fa fa-refresh"></i></span> <span class="copy">${_("Loading")}</span></p>
|
||||
<p><span class="spin"><i class="icon fa fa-refresh" aria-hidden="true"></i></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course'/>
|
||||
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jQuery-File-Upload/js/jquery.fileupload.js')}"></script>
|
||||
|
||||
<script>
|
||||
(function (require) {
|
||||
require(['js/student_profile/views/learner_profile_factory'], function(setupLearnerProfile) {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
<div class="wrapper-profile-sections account-settings-container">
|
||||
<div class="wrapper-profile-section-one">
|
||||
<div class="profile-photo">
|
||||
<img src="<%- profilePhoto %>" alt="Profile image for <%- username %>">
|
||||
<div class="profile-image-field">
|
||||
</div>
|
||||
|
||||
<div class="profile-section-one-fields">
|
||||
|
||||
Reference in New Issue
Block a user