Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
64e0089960
|
|
@ -77,4 +77,5 @@ whats_next.sql
|
|||
add_user_bio.sql
|
||||
users_geocoding.sql
|
||||
recordings_public_launch.sql
|
||||
notification_band_invite.sql
|
||||
notification_band_invite.sql
|
||||
band_photo_filepicker.sql
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE bands ADD COLUMN original_fpfile_photo VARCHAR(8000) DEFAULT NULL;
|
||||
ALTER TABLE bands ADD COLUMN cropped_fpfile_photo VARCHAR(8000) DEFAULT NULL;
|
||||
ALTER TABLE bands ADD COLUMN cropped_s3_path_photo VARCHAR(512) DEFAULT NULL;
|
||||
ALTER TABLE bands ADD COLUMN crop_selection_photo VARCHAR(256) DEFAULT NULL;
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
module JamRuby
|
||||
class Band < ActiveRecord::Base
|
||||
|
||||
attr_accessible :name, :website, :biography, :city, :state, :country
|
||||
attr_accessible :name, :website, :biography, :city, :state,
|
||||
:country, :original_fpfile_photo, :cropped_fpfile_photo,
|
||||
:cropped_s3_path_photo, :crop_selection_photo, :photo_url
|
||||
|
||||
attr_accessor :updating_photo
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
before_save :stringify_photo_info , :if => :updating_photo
|
||||
validate :validate_photo_info
|
||||
validates :biography, no_profanity: true
|
||||
|
||||
# musicians
|
||||
|
|
@ -57,6 +63,14 @@ module JamRuby
|
|||
loc
|
||||
end
|
||||
|
||||
def validate_photo_info
|
||||
if updating_photo
|
||||
# we want to mak sure that original_fpfile and cropped_fpfile seems like real fpfile info objects (i.e, json objects from filepicker.io)
|
||||
errors.add(:original_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.original_fpfile_photo.nil? || self.original_fpfile_photo["key"].nil? || self.original_fpfile_photo["url"].nil?
|
||||
errors.add(:cropped_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.cropped_fpfile_photo.nil? || self.cropped_fpfile_photo["key"].nil? || self.cropped_fpfile_photo["url"].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def add_member(user_id, admin)
|
||||
BandMusician.create(:band_id => self.id, :user_id => user_id, :admin => admin)
|
||||
end
|
||||
|
|
@ -187,6 +201,40 @@ module JamRuby
|
|||
return band
|
||||
end
|
||||
|
||||
def update_photo(original_fpfile, cropped_fpfile, crop_selection, aws_bucket)
|
||||
self.updating_photo = true
|
||||
|
||||
cropped_s3_path = cropped_fpfile["key"]
|
||||
|
||||
return self.update_attributes(
|
||||
:original_fpfile_photo => original_fpfile,
|
||||
:cropped_fpfile_photo => cropped_fpfile,
|
||||
:cropped_s3_path_photo => cropped_s3_path,
|
||||
:crop_selection_photo => crop_selection,
|
||||
:photo_url => S3Util.url(aws_bucket, cropped_s3_path, :secure => false)
|
||||
)
|
||||
end
|
||||
|
||||
def delete_photo(aws_bucket)
|
||||
|
||||
Band.transaction do
|
||||
|
||||
unless self.cropped_s3_path.nil?
|
||||
S3Util.delete(aws_bucket, File.dirname(self.cropped_s3_path) + '/cropped.jpg')
|
||||
S3Util.delete(aws_bucket, self.cropped_s3_path)
|
||||
end
|
||||
|
||||
return self.update_attributes(
|
||||
:original_fpfile_photo => nil,
|
||||
:cropped_fpfile_photo => nil,
|
||||
:cropped_s3_path_photo => nil,
|
||||
:crop_selection_photo => nil,
|
||||
:photo_url => nil
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def self.validate_genres(genres, is_nil_ok)
|
||||
if is_nil_ok && genres.nil?
|
||||
|
|
@ -205,5 +253,15 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stringify_photo_info
|
||||
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
|
||||
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
|
||||
# later, when serving this data out to the REST API, we currently just leave it as a string and make a JSON capable
|
||||
# client parse it, because it's very rare when it's needed at all
|
||||
self.original_fpfile_photo = original_fpfile_photo.to_json if !original_fpfile_photo.nil?
|
||||
self.cropped_fpfile_photo = cropped_fpfile_photo.to_json if !cropped_fpfile_photo.nil?
|
||||
self.crop_selection_photo = crop_selection_photo.to_json if !crop_selection_photo.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
var self = this;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var userId;
|
||||
var user = {};
|
||||
var tmpUploadPath = null;
|
||||
var userDetail = null;
|
||||
|
|
@ -18,7 +17,6 @@
|
|||
var userDropdown;
|
||||
|
||||
function beforeShow(data) {
|
||||
userId = data.id;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -150,7 +148,7 @@
|
|||
var avatar = $('img.preview_profile_avatar', avatarSpace);
|
||||
|
||||
var spinner = $('<div class="spinner spinner-large"></div>')
|
||||
if(avatar.length == 0) {
|
||||
if(avatar.length === 0) {
|
||||
avatarSpace.prepend(spinner);
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@
|
|||
userNames = [];
|
||||
userIds = [];
|
||||
userPhotoUrls = [];
|
||||
//bandId = "1158c8b6-4c92-47dc-82bf-1e390c4f9b2c";
|
||||
bandId = $("#hdn-band-id").val();
|
||||
resetForm();
|
||||
}
|
||||
|
|
@ -259,6 +260,9 @@
|
|||
|
||||
$("#band-setup-title").html("set up band");
|
||||
$("#btn-band-setup-save").html("CREATE BAND");
|
||||
|
||||
$("#band-change-photo").unbind('click');
|
||||
$("#band-change-photo").html('Set up band and then add photo.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,10 +272,14 @@
|
|||
$("#band-website").val(band.website);
|
||||
$("#band-biography").val(band.biography);
|
||||
|
||||
if (band.photo_url) {
|
||||
$("#band-avatar").attr('src', band.photo_url);
|
||||
}
|
||||
|
||||
loadGenres(band.genres);
|
||||
|
||||
loadCountries(band.country, function() {
|
||||
loadRegions(band.region, function() {
|
||||
loadRegions(band.state, function() {
|
||||
loadCities(band.city);
|
||||
});
|
||||
});
|
||||
|
|
@ -492,6 +500,13 @@
|
|||
return false;
|
||||
});
|
||||
|
||||
$('#band-change-photo').click(function(evt) {
|
||||
evt.stopPropagation();
|
||||
$("#hdn-band-id").val(bandId);
|
||||
context.location = '#/band/setup/photo';
|
||||
return false;
|
||||
});
|
||||
|
||||
$('div[layout-id="band/setup"] .btn-email-invitation').click(function() {
|
||||
invitationDialog.showEmailDialog();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,431 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.BandSetupPhotoScreen = function(app) {
|
||||
var self = this;
|
||||
var logger = context.JK.logger;
|
||||
var rest = context.JK.Rest();
|
||||
var bandId;
|
||||
var band = {};
|
||||
var tmpUploadPath = null;
|
||||
var bandDetail = null;
|
||||
var bandPhoto;
|
||||
var selection = null;
|
||||
var targetCropSize = 88;
|
||||
var updatingBandPhoto = false;
|
||||
|
||||
function beforeShow(data) {
|
||||
bandId = $("#hdn-band-id").val();
|
||||
logger.debug("bandId=" + bandId);
|
||||
if (!bandId) {
|
||||
context.location = '#/home';
|
||||
}
|
||||
}
|
||||
|
||||
function afterShow(data) {
|
||||
resetForm();
|
||||
renderBandPhotoScreen()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
// remove all display errors
|
||||
$('#band-setup-photo-content-scroller form .error-text').remove()
|
||||
$('#band-setup-photo-content-scroller form .error').removeClass("error")
|
||||
}
|
||||
|
||||
function populateBandPhoto(bandDetail) {
|
||||
self.bandDetail = bandDetail;
|
||||
rest.getBandPhotoFilepickerPolicy({ id:bandId })
|
||||
.done(function(filepicker_policy) {
|
||||
var template= context.JK.fillTemplate($('#template-band-setup-photo').html(), {
|
||||
"fp_apikey" : gon.fp_apikey,
|
||||
"data-fp-store-path" : createStorePath(bandDetail) + createOriginalFilename(bandDetail),
|
||||
"fp_policy" : filepicker_policy.policy,
|
||||
"fp_signature" : filepicker_policy.signature
|
||||
});
|
||||
$('#band-setup-photo-content-scroller').html(template);
|
||||
|
||||
|
||||
var currentFpfile = determineCurrentFpfile();
|
||||
var currentCropSelection = determineCurrentSelection(bandDetail);
|
||||
renderBandPhoto(currentFpfile, currentCropSelection ? JSON.parse(currentCropSelection) : null);
|
||||
})
|
||||
.error(app.ajaxError);
|
||||
|
||||
}
|
||||
|
||||
// events for main screen
|
||||
function events() {
|
||||
// wire up main panel clicks
|
||||
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-upload', function(evt) { evt.stopPropagation(); handleFilePick(); return false; } );
|
||||
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-delete', function(evt) { evt.stopPropagation(); handleDeleteBandPhoto(); return false; } );
|
||||
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-cancel', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } );
|
||||
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-submit', function(evt) { evt.stopPropagation(); handleUpdateBandPhoto(); return false; } );
|
||||
//$('#band-setup-photo-content-scroller').on('change', 'input[type=filepicker-dragdrop]', function(evt) { evt.stopPropagation(); afterImageUpload(evt.originalEvent.fpfile); return false; } );
|
||||
}
|
||||
|
||||
function handleDeleteBandPhoto() {
|
||||
|
||||
if(self.updatingBandPhoto) {
|
||||
// protect against concurrent update attempts
|
||||
return;
|
||||
}
|
||||
|
||||
self.updatingBandPhoto = true;
|
||||
renderBandPhotoSpinner();
|
||||
|
||||
rest.deleteBandPhoto({ id: bandId })
|
||||
.done(function() {
|
||||
removeBandPhotoSpinner({ delete:true });
|
||||
deleteBandPhotoSuccess(arguments);
|
||||
selection = null;
|
||||
})
|
||||
.fail(function() {
|
||||
app.ajaxError(arguments);
|
||||
$.cookie('original_fpfile_band_photo', null);
|
||||
self.updatingBandPhoto = false;
|
||||
})
|
||||
.always(function() {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function deleteBandPhotoSuccess(response) {
|
||||
|
||||
renderBandPhoto(null, null);
|
||||
|
||||
rest.getBand(bandId)
|
||||
.done(function(bandDetail) {
|
||||
self.bandDetail = bandDetail;
|
||||
})
|
||||
.error(app.ajaxError)
|
||||
.always(function() {
|
||||
self.updatingBandPhoto = false;
|
||||
})
|
||||
}
|
||||
|
||||
function handleFilePick() {
|
||||
rest.getBandPhotoFilepickerPolicy({ id: bandId })
|
||||
.done(function(filepickerPolicy) {
|
||||
renderBandPhotoSpinner();
|
||||
logger.debug("rendered spinner");
|
||||
filepicker.setKey(gon.fp_apikey);
|
||||
filepicker.pickAndStore({
|
||||
mimetype: 'image/*',
|
||||
maxSize: 10000*1024,
|
||||
policy: filepickerPolicy.policy,
|
||||
signature: filepickerPolicy.signature
|
||||
},
|
||||
{ path: createStorePath(self.bandDetail), access: 'public' },
|
||||
function(fpfiles) {
|
||||
removeBandPhotoSpinner();
|
||||
afterImageUpload(fpfiles[0]);
|
||||
}, function(fperror) {
|
||||
removeBandPhotoSpinner();
|
||||
|
||||
if(fperror.code != 101) { // 101 just means the user closed the dialog
|
||||
alert("unable to upload file: " + JSON.stringify(fperror))
|
||||
}
|
||||
})
|
||||
})
|
||||
.fail(app.ajaxError);
|
||||
|
||||
}
|
||||
function renderBandPhotoScreen() {
|
||||
|
||||
rest.getBand(bandId)
|
||||
.done(populateBandPhoto)
|
||||
.error(app.ajaxError)
|
||||
}
|
||||
|
||||
function navToEditProfile() {
|
||||
resetForm();
|
||||
$("#hdn-band-id").val(bandId);
|
||||
context.location = '#/band/setup';
|
||||
}
|
||||
|
||||
function renderBandPhotoSpinner() {
|
||||
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
|
||||
// if there is already an image tag, we only obscure it.
|
||||
|
||||
var bandPhoto = $('img.preview_profile_avatar', bandPhotoSpace);
|
||||
|
||||
var spinner = $('<div class="spinner spinner-large"></div>')
|
||||
if(bandPhoto.length === 0) {
|
||||
bandPhotoSpace.prepend(spinner);
|
||||
}
|
||||
else {
|
||||
// in this case, just style the spinner to obscure using opacity, and center it
|
||||
var jcropHolder = $('.jcrop-holder', bandPhotoSpace);
|
||||
spinner.width(jcropHolder.width());
|
||||
spinner.height(jcropHolder.height());
|
||||
spinner.addClass('op50');
|
||||
var jcrop = bandPhoto.data('Jcrop');
|
||||
if(jcrop) {
|
||||
jcrop.disable();
|
||||
}
|
||||
bandPhotoSpace.append(spinner);
|
||||
}
|
||||
}
|
||||
|
||||
function removeBandPhotoSpinner(options) {
|
||||
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
|
||||
|
||||
if(options && options.delete) {
|
||||
bandPhotoSpace.children().remove();
|
||||
}
|
||||
|
||||
var spinner = $('.spinner-large', bandPhotoSpace);
|
||||
spinner.remove();
|
||||
var bandPhoto = $('img.preview_profile_avatar', bandPhotoSpace);
|
||||
var jcrop = bandPhoto.data('Jcrop')
|
||||
if(jcrop) {
|
||||
jcrop.enable();
|
||||
}
|
||||
}
|
||||
|
||||
function renderBandPhoto(fpfile, storedSelection) {
|
||||
|
||||
// clear out
|
||||
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
|
||||
|
||||
if(!fpfile) {
|
||||
renderNoBandPhoto(bandPhotoSpace);
|
||||
}
|
||||
else {
|
||||
rest.getBandPhotoFilepickerPolicy({handle: fpfile.url, id: bandId})
|
||||
.done(function(filepickerPolicy) {
|
||||
bandPhotoSpace.children().remove();
|
||||
renderBandPhotoSpinner();
|
||||
|
||||
var photo_url = fpfile.url + '?signature=' + filepickerPolicy.signature + '&policy=' + filepickerPolicy.policy;
|
||||
bandPhoto = new Image();
|
||||
$(bandPhoto)
|
||||
.load(function(e) {
|
||||
removeBandPhotoSpinner();
|
||||
|
||||
bandPhoto = $(this);
|
||||
bandPhotoSpace.append(bandPhoto);
|
||||
var width = bandPhoto.naturalWidth();
|
||||
var height = bandPhoto.naturalHeight();
|
||||
|
||||
if(storedSelection) {
|
||||
var left = storedSelection.x;
|
||||
var right = storedSelection.x2;
|
||||
var top = storedSelection.y;
|
||||
var bottom = storedSelection.y2;
|
||||
}
|
||||
else {
|
||||
if(width < height) {
|
||||
var left = width * .25;
|
||||
var right = width * .75;
|
||||
var top = (height / 2) - (width / 4);
|
||||
var bottom = (height / 2) + (width / 4);
|
||||
}
|
||||
else {
|
||||
var top = height * .25;
|
||||
var bottom = height * .75;
|
||||
var left = (width / 2) - (height / 4);
|
||||
var right = (width / 2) + (height / 4);
|
||||
}
|
||||
}
|
||||
|
||||
// jcrop only works well with px values (not percentages)
|
||||
// so we get container, and work out a decent % ourselves
|
||||
var container = $('#band-setup-photo-content-scroller');
|
||||
|
||||
bandPhoto.Jcrop({
|
||||
aspectRatio: 1,
|
||||
boxWidth: container.width() * .75,
|
||||
boxHeight: container.height() * .75,
|
||||
// minSelect: [targetCropSize, targetCropSize], unnecessary with scaling involved
|
||||
setSelect: [ left, top, right, bottom ],
|
||||
trueSize: [width, height],
|
||||
onRelease: onSelectRelease,
|
||||
onSelect: onSelect,
|
||||
onChange: onChange
|
||||
});
|
||||
})
|
||||
.error(function() {
|
||||
// default to no avatar look of UI
|
||||
renderNoBandPhoto(bandPhotoSpace);
|
||||
})
|
||||
.attr('src', photo_url)
|
||||
.attr('alt', 'profile avatar')
|
||||
.addClass('preview_profile_avatar');
|
||||
})
|
||||
.fail(app.ajaxError);
|
||||
}
|
||||
}
|
||||
|
||||
function afterImageUpload(fpfile) {
|
||||
$.cookie('original_fpfile_band_photo', JSON.stringify(fpfile));
|
||||
renderBandPhoto(fpfile, null);
|
||||
}
|
||||
|
||||
function renderNoBandPhoto(bandPhotoSpace) {
|
||||
// no photo found for band
|
||||
|
||||
removeBandPhotoSpinner();
|
||||
|
||||
var noAvatarSpace = $('<div></div>');
|
||||
noAvatarSpace.addClass('no-avatar-space');
|
||||
noAvatarSpace.text('Please upload a photo');
|
||||
bandPhotoSpace.append(noAvatarSpace);
|
||||
}
|
||||
|
||||
function handleUpdateBandPhoto(event) {
|
||||
|
||||
if(self.updatingBandPhoto) {
|
||||
// protect against concurrent update attempts
|
||||
return;
|
||||
}
|
||||
|
||||
if(selection) {
|
||||
var currentSelection = selection;
|
||||
self.updatingBandPhoto = true;
|
||||
renderBandPhotoSpinner();
|
||||
|
||||
console.log("Converting...");
|
||||
|
||||
// we convert two times; first we crop to the selected region,
|
||||
// then we scale to 88x88 (targetCropSize X targetCropSize), which is the largest size we use throughout the site.
|
||||
var fpfile = determineCurrentFpfile();
|
||||
rest.getBandPhotoFilepickerPolicy({ handle: fpfile.url, convert: true, id: bandId })
|
||||
.done(function(filepickerPolicy) {
|
||||
filepicker.setKey(gon.fp_apikey);
|
||||
filepicker.convert(fpfile, {
|
||||
crop: [
|
||||
Math.round(currentSelection.x),
|
||||
Math.round(currentSelection.y),
|
||||
Math.round(currentSelection.w),
|
||||
Math.round(currentSelection.w)],
|
||||
fit: 'crop',
|
||||
format: 'jpg',
|
||||
quality: 90,
|
||||
policy: filepickerPolicy.policy,
|
||||
signature: filepickerPolicy.signature
|
||||
}, { path: createStorePath(self.bandDetail) + 'cropped-' + new Date().getTime() + '.jpg', access: 'public' },
|
||||
function(cropped) {
|
||||
logger.debug("converting cropped");
|
||||
rest.getBandPhotoFilepickerPolicy({handle: cropped.url, convert: true, id: bandId})
|
||||
.done(function(filepickerPolicy) {
|
||||
filepicker.convert(cropped, {
|
||||
height: targetCropSize,
|
||||
width: targetCropSize,
|
||||
fit: 'scale',
|
||||
format: 'jpg',
|
||||
quality: 75,
|
||||
policy: filepickerPolicy.policy,
|
||||
signature: filepickerPolicy.signature
|
||||
}, { path: createStorePath(self.bandDetail), access: 'public' },
|
||||
function(scaled) {
|
||||
logger.debug("converted and scaled final image %o", scaled);
|
||||
rest.updateBandPhoto({
|
||||
original_fpfile: determineCurrentFpfile(),
|
||||
cropped_fpfile: scaled,
|
||||
crop_selection: currentSelection,
|
||||
id: bandId
|
||||
})
|
||||
.done(updateBandPhotoSuccess)
|
||||
.fail(app.ajaxError)
|
||||
.always(function() { removeBandPhotoSpinner(); self.updatingBandPhoto = false;})
|
||||
},
|
||||
function(fperror) {
|
||||
alert("unable to scale selection. error code: " + fperror.code);
|
||||
removeBandPhotoSpinner();
|
||||
self.updatingBandPhoto = false;
|
||||
})
|
||||
})
|
||||
.fail(app.ajaxError);
|
||||
},
|
||||
function(fperror) {
|
||||
alert("unable to crop selection. error code: " + fperror.code);
|
||||
removeBandPhotoSpinner();
|
||||
self.updatingBandPhoto = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
.fail(app.ajaxError);
|
||||
}
|
||||
else {
|
||||
app.notify(
|
||||
{ title: "Upload a Band Photo First",
|
||||
text: "To update your band photo, first you must upload an image using the UPLOAD button"
|
||||
},
|
||||
{ no_cancel: true });
|
||||
}
|
||||
}
|
||||
|
||||
function updateBandPhotoSuccess(response) {
|
||||
$.cookie('original_fpfile_band_photo', null);
|
||||
|
||||
self.bandDetail = response;
|
||||
|
||||
app.notify(
|
||||
{ title: "Band Photo Changed",
|
||||
text: "You have updated your band photo successfully."
|
||||
},
|
||||
{ no_cancel: true });
|
||||
}
|
||||
|
||||
function onSelectRelease(event) {
|
||||
}
|
||||
|
||||
function onSelect(event) {
|
||||
selection = event;
|
||||
}
|
||||
|
||||
function onChange(event) {
|
||||
}
|
||||
|
||||
function createStorePath(bandDetail) {
|
||||
return gon.fp_upload_dir + '/' + bandDetail.id + '/'
|
||||
}
|
||||
|
||||
function createOriginalFilename(bandDetail) {
|
||||
// get the s3
|
||||
var fpfile = bandDetail.original_fpfile_photo ? JSON.parse(bandDetail.original_fpfile_photo) : null;
|
||||
return 'original_band_photo.jpg'
|
||||
}
|
||||
|
||||
// retrieves a file that has not yet been used as an band photo (uploaded, but not cropped)
|
||||
function getWorkingFpfile() {
|
||||
return JSON.parse($.cookie('original_fpfile_band_photo'))
|
||||
}
|
||||
|
||||
function determineCurrentFpfile() {
|
||||
// precedence is as follows:
|
||||
// * tempOriginal: if set, then the user is working on a new upload
|
||||
// * storedOriginal: if set, then the user has previously uploaded and cropped a band photo
|
||||
// * null: neither are set above
|
||||
|
||||
var tempOriginal = getWorkingFpfile();
|
||||
var storedOriginal = self.bandDetail.original_fpfile_photo ? JSON.parse(self.bandDetail.original_fpfile_photo) : null;
|
||||
|
||||
return tempOriginal ? tempOriginal : storedOriginal;
|
||||
}
|
||||
|
||||
function determineCurrentSelection(bandDetail) {
|
||||
// if the cookie is set, don't use the storage selection, just default to null
|
||||
return $.cookie('original_fpfile_band_photo') == null ? bandDetail.crop_selection_photo : null;
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow
|
||||
};
|
||||
app.bindScreen('band/setup/photo', screenBindings);
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
this.beforeShow = beforeShow;
|
||||
this.afterShow = afterShow;
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -211,6 +211,12 @@
|
|||
var cropped_fpfile = options['cropped_fpfile'];
|
||||
var crop_selection = options['crop_selection'];
|
||||
|
||||
logger.debug(JSON.stringify({
|
||||
original_fpfile : original_fpfile,
|
||||
cropped_fpfile : cropped_fpfile,
|
||||
crop_selection : crop_selection
|
||||
}));
|
||||
|
||||
var url = "/api/users/" + id + "/avatar";
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
|
|
@ -252,6 +258,60 @@
|
|||
});
|
||||
}
|
||||
|
||||
function updateBandPhoto(options) {
|
||||
var id = getId(options);
|
||||
|
||||
var original_fpfile = options['original_fpfile'];
|
||||
var cropped_fpfile = options['cropped_fpfile'];
|
||||
var crop_selection = options['crop_selection'];
|
||||
|
||||
logger.debug(JSON.stringify({
|
||||
original_fpfile : original_fpfile,
|
||||
cropped_fpfile : cropped_fpfile,
|
||||
crop_selection : crop_selection
|
||||
}));
|
||||
|
||||
var url = "/api/bands/" + id + "/photo";
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url,
|
||||
contentType: 'application/json',
|
||||
processData:false,
|
||||
data: JSON.stringify({
|
||||
original_fpfile : original_fpfile,
|
||||
cropped_fpfile : cropped_fpfile,
|
||||
crop_selection : crop_selection
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function deleteBandPhoto(options) {
|
||||
var id = getId(options);
|
||||
|
||||
var url = "/api/bands/" + id + "/photo";
|
||||
return $.ajax({
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
url: url,
|
||||
contentType: 'application/json',
|
||||
processData:false
|
||||
});
|
||||
}
|
||||
|
||||
function getBandPhotoFilepickerPolicy(options) {
|
||||
var id = getId(options);
|
||||
var handle = options && options["handle"];
|
||||
var convert = options && options["convert"]
|
||||
|
||||
var url = "/api/bands/" + id + "/filepicker_policy";
|
||||
|
||||
return $.ajax(url, {
|
||||
data : { handle : handle, convert: convert },
|
||||
dataType : 'json'
|
||||
});
|
||||
}
|
||||
|
||||
function getFriends(options) {
|
||||
var friends = [];
|
||||
var id = getId(options);
|
||||
|
|
@ -499,6 +559,9 @@
|
|||
this.putTrackSyncChange = putTrackSyncChange;
|
||||
this.createBand = createBand;
|
||||
this.updateBand = updateBand;
|
||||
this.updateBandPhoto = updateBandPhoto;
|
||||
this.deleteBandPhoto = deleteBandPhoto;
|
||||
this.getBandPhotoFilepickerPolicy = getBandPhotoFilepickerPolicy;
|
||||
this.getBand = getBand;
|
||||
this.createBandInvitation = createBandInvitation;
|
||||
this.updateBandInvitation = updateBandInvitation;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,43 @@
|
|||
font-size:14px;
|
||||
}
|
||||
|
||||
.band-setup-photo {
|
||||
|
||||
.avatar-space {
|
||||
color: $color2;
|
||||
margin-bottom: 20px;
|
||||
position:relative;
|
||||
min-height:300px;
|
||||
|
||||
img.preview_profile_avatar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.spinner-large {
|
||||
width:300px;
|
||||
height:300px;
|
||||
line-height: 300px;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
z-index: 2000; // to win over jcrop
|
||||
}
|
||||
|
||||
.no-avatar-space {
|
||||
border:1px dotted $color2;
|
||||
|
||||
color: $color2;
|
||||
width:300px;
|
||||
height:300px;
|
||||
line-height: 300px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
background-color:$ColorTextBoxBackground;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.band-profile-header {
|
||||
padding:20px;
|
||||
height:120px;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ class ApiBandsController < ApiController
|
|||
before_filter :api_signed_in_user, :except => [:index, :show, :follower_index]
|
||||
before_filter :auth_band_member, :only => [:update,
|
||||
:recording_create, :recording_update, :recording_destroy,
|
||||
:invitation_index, :invitation_show, :invitation_create, :invitation_destroy]
|
||||
:invitation_index, :invitation_show, :invitation_create, :invitation_destroy,
|
||||
:update_photo, :delete_photo, :generate_filepicker_policy]
|
||||
|
||||
respond_to :json
|
||||
|
||||
|
|
@ -187,6 +188,56 @@ class ApiBandsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def update_photo
|
||||
original_fpfile = params[:original_fpfile]
|
||||
cropped_fpfile = params[:cropped_fpfile]
|
||||
crop_selection = params[:crop_selection]
|
||||
|
||||
# public bucket to allow images to be available to public
|
||||
@band.update_photo(original_fpfile, cropped_fpfile, crop_selection, Rails.application.config.aws_bucket_public)
|
||||
|
||||
if @band.errors.any?
|
||||
respond_with @band, status: :unprocessable_entity
|
||||
else
|
||||
respond_with @band, responder: ApiResponder, status: 200
|
||||
end
|
||||
end
|
||||
|
||||
def delete_photo
|
||||
@band.delete_photo(Rails.application.config.aws_bucket_public)
|
||||
|
||||
if @band.errors.any?
|
||||
respond_with @band, status: :unprocessable_entity
|
||||
else
|
||||
respond_with @band, responder: ApiResponder, status: 204
|
||||
end
|
||||
end
|
||||
|
||||
def generate_filepicker_policy
|
||||
# generates a soon-expiring filepicker policy so that a band can only upload to their own folder in their bucket
|
||||
|
||||
handle = params[:handle]
|
||||
|
||||
call = 'pick,convert,store'
|
||||
|
||||
policy = { :expiry => (DateTime.now + 5.minutes).to_i(),
|
||||
:call => call
|
||||
#:path => 'avatars/' + @band.id + '/.*jpg'
|
||||
}
|
||||
|
||||
# if the caller specifies a handle, add it to the hash
|
||||
unless handle.nil?
|
||||
start = handle.rindex('/') + 1
|
||||
policy[:handle] = handle[start..-1]
|
||||
end
|
||||
|
||||
policy = Base64.urlsafe_encode64( policy.to_json )
|
||||
digest = OpenSSL::Digest::Digest.new('sha256')
|
||||
signature = OpenSSL::HMAC.hexdigest(digest, Rails.application.config.fp_secret, policy)
|
||||
|
||||
render :json => { :signature => signature, :policy => policy }, :status => :ok
|
||||
end
|
||||
|
||||
#############################################################################
|
||||
protected
|
||||
# ensures user is a member of the band
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
object @band
|
||||
|
||||
attributes :id, :name, :city, :state, :country, :location, :website, :biography, :photo_url, :logo_url, :liker_count, :follower_count, :recording_count, :session_count
|
||||
attributes :id, :name, :city, :state, :country, :location, :website, :biography, :photo_url, :logo_url, :liker_count, :follower_count, :recording_count, :session_count,
|
||||
:original_fpfile_photo, :cropped_fpfile_photo, :crop_selection_photo
|
||||
|
||||
unless @band.users.nil? || @band.users.size == 0
|
||||
child :users => :musicians do
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Account Summary Dialog -->
|
||||
<!-- Account Profile Screen -->
|
||||
<div layout="screen" layout-id="account/profile" class="screen secondary">
|
||||
<!-- header -->
|
||||
<div class="content-head">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Account Summary Dialog -->
|
||||
<!-- Profile Avatar Screen -->
|
||||
<div layout="screen" layout-id="account/profile/avatar" class="screen secondary">
|
||||
<!-- header -->
|
||||
<div class="content-head">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
<!-- end content wrapper -->
|
||||
|
||||
<!-- taken from filepickr.io -->
|
||||
<!-- taken from filepicker.io -->
|
||||
<script type="text/javascript">
|
||||
(function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js?signature={fp_signature}&policy={fp_policy}";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
<%= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } %>
|
||||
</a>
|
||||
<br/><br/>
|
||||
<a href="#" class="small ml20">Upload Band Photo</a><br clear="all"><br/>
|
||||
<a id="band-change-photo" href="#" class="small ml20">Upload Band Photo</a><br clear="all"><br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<!-- Band Photo Setup -->
|
||||
<div layout="screen" layout-id="band/setup/photo" class="screen secondary">
|
||||
<!-- header -->
|
||||
<div class="content-head">
|
||||
<!-- icon -->
|
||||
<div class="content-icon">
|
||||
<%= image_tag "content/icon_bands.png", {:width => 19, :height => 19} %>
|
||||
</div>
|
||||
<!-- section head text -->
|
||||
<h1>band setup</h1>
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
<!-- end header -->
|
||||
|
||||
<!-- profile scrolling area -->
|
||||
<div id="band-setup-photo-content-scroller" class="content-scroller">
|
||||
|
||||
</div>
|
||||
<!-- end content scrolling area -->
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-band-setup-photo">
|
||||
<!-- content wrapper -->
|
||||
<div class="content-wrapper band-setup-photo">
|
||||
<br />
|
||||
|
||||
<div class="avatar-space"></div>
|
||||
|
||||
<form id="band-setup-photo-form">
|
||||
<a href="#" id="band-setup-photo-upload" class="button-orange">UPLOAD</a>
|
||||
</form>
|
||||
|
||||
<br clear="all" />
|
||||
<div class="right"><a id="band-setup-photo-cancel" href="#" class="button-grey">CANCEL</a> <a id="band-setup-photo-delete" href="#" class="button-orange">DELETE PHOTO</a> <a id="band-setup-photo-submit" href="#" class="button-orange">UPDATE PHOTO</a></div>
|
||||
</div>
|
||||
<!-- end content wrapper -->
|
||||
|
||||
<!-- taken from filepicker.io -->
|
||||
<script type="text/javascript">
|
||||
(function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js?signature={fp_signature}&policy={fp_policy}";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
|
||||
</script>
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
<%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %>
|
||||
</div>
|
||||
<h1>session</h1>
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
|
||||
<!-- session controls -->
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<%= render "profile" %>
|
||||
<%= render "bandProfile" %>
|
||||
<%= render "band_setup" %>
|
||||
<%= render "band_setup_photo" %>
|
||||
<%= render "feed" %>
|
||||
<%= render "bands" %>
|
||||
<%= render "musicians" %>
|
||||
|
|
@ -141,6 +142,9 @@
|
|||
var bandSetupScreen = new JK.BandSetupScreen(JK.app);
|
||||
bandSetupScreen.initialize(invitationDialog, friendSelectorDialog);
|
||||
|
||||
var bandSetupPhotoScreen = new JK.BandSetupPhotoScreen(JK.app);
|
||||
bandSetupPhotoScreen.initialize();
|
||||
|
||||
var findSessionScreen = new JK.FindSessionScreen(JK.app);
|
||||
var sessionLatency = null;
|
||||
if ("jamClient" in window) {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ SampleApp::Application.configure do
|
|||
TEST_CONNECT_STATES = false
|
||||
|
||||
# Overloaded value to match production for using cloudfront in dev mode
|
||||
config.cloudfront_host = "d2bc92otq4j4dd.cloudfront.net"
|
||||
config.cloudfront_host = "d48bcgsnmsm6a.cloudfront.net"
|
||||
|
||||
# this is totally awful and silly; the reason this exists is so that if you upload an artifact
|
||||
# through jam-admin, then jam-web can point users at it. I think 99% of devs won't even see or care about this config, and 0% of users
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ SampleApp::Application.configure do
|
|||
|
||||
config.aws_bucket = 'jamkazam'
|
||||
config.aws_bucket_public = 'jamkazam-public'
|
||||
config.aws_fullhost = "#{config.aws_bucket_public}.s3.amazonaws.com"
|
||||
|
||||
# Dev cloudfront hostname
|
||||
config.cloudfront_host = "d34f55ppvvtgi3.cloudfront.net"
|
||||
|
|
|
|||
|
|
@ -204,6 +204,11 @@ SampleApp::Application.routes.draw do
|
|||
match '/bands' => 'api_bands#create', :via => :post
|
||||
match '/bands/:id' => 'api_bands#update', :via => :post
|
||||
|
||||
# photo
|
||||
match '/bands/:id/photo' => 'api_bands#update_photo', :via => :post
|
||||
match '/bands/:id/photo' => 'api_bands#delete_photo', :via => :delete
|
||||
match '/bands/:id/filepicker_policy' => 'api_bands#generate_filepicker_policy', :via => :get
|
||||
|
||||
# band members
|
||||
match '/bands/:id/musicians' => 'api_bands#musician_index', :via => :get
|
||||
match '/bands/:id/musicians' => 'api_bands#musician_create', :via => :post
|
||||
|
|
|
|||
Loading…
Reference in New Issue