implemented new profile photo upload with cropping function
This commit is contained in:
parent
03ee954c10
commit
d7abaaced0
16 changed files with 387 additions and 63 deletions
|
|
@ -2,7 +2,8 @@
|
|||
"env": {
|
||||
"browser": true,
|
||||
"jasmine": true,
|
||||
"jquery": true
|
||||
"jquery": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"globals": {
|
||||
|
|
@ -96,7 +97,7 @@
|
|||
"no-catch-shadow": 0,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-confusing-arrow": 2,
|
||||
"no-confusing-arrow": [2, {"allowParens": true}],
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-constant-condition": 2,
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -89,6 +89,7 @@ gem "entypo-rails", "3.0.0"
|
|||
|
||||
# JavaScript
|
||||
|
||||
gem "sprockets-es6", "0.9.2"
|
||||
gem "handlebars_assets", "0.23.2"
|
||||
gem "jquery-rails", "4.3.1"
|
||||
gem "js-routes", "1.4.1"
|
||||
|
|
@ -110,6 +111,7 @@ source "https://rails-assets.org" do
|
|||
gem "rails-assets-backbone", "1.3.3"
|
||||
gem "rails-assets-bootstrap-markdown", "2.10.0"
|
||||
gem "rails-assets-corejs-typeahead", "1.1.1"
|
||||
gem "rails-assets-cropperjs", "1.2.1"
|
||||
gem "rails-assets-fine-uploader", "5.13.0"
|
||||
|
||||
# jQuery plugins
|
||||
|
|
|
|||
13
Gemfile.lock
13
Gemfile.lock
|
|
@ -62,6 +62,10 @@ GEM
|
|||
attr_required (1.0.1)
|
||||
autoprefixer-rails (7.1.4.1)
|
||||
execjs
|
||||
babel-source (5.8.35)
|
||||
babel-transpiler (0.7.0)
|
||||
babel-source (>= 4.0, < 6)
|
||||
execjs (~> 2.0)
|
||||
bcrypt (3.1.11)
|
||||
bindata (2.4.1)
|
||||
bootstrap-sass (3.3.7)
|
||||
|
|
@ -516,6 +520,7 @@ GEM
|
|||
rails-assets-bootstrap (~> 3)
|
||||
rails-assets-corejs-typeahead (1.1.1)
|
||||
rails-assets-jquery (>= 1.11)
|
||||
rails-assets-cropperjs (1.2.1)
|
||||
rails-assets-diaspora_jsxc (0.1.5.develop.7)
|
||||
rails-assets-emojione (~> 2.0.1)
|
||||
rails-assets-favico.js (>= 0.3.10, < 0.4)
|
||||
|
|
@ -670,6 +675,10 @@ GEM
|
|||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-es6 (0.9.2)
|
||||
babel-source (>= 5.8.11)
|
||||
babel-transpiler
|
||||
sprockets (>= 3.0.0)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
|
|
@ -848,6 +857,7 @@ DEPENDENCIES
|
|||
rails-assets-blueimp-gallery (= 2.27.0)!
|
||||
rails-assets-bootstrap-markdown (= 2.10.0)!
|
||||
rails-assets-corejs-typeahead (= 1.1.1)!
|
||||
rails-assets-cropperjs (= 1.2.1)!
|
||||
rails-assets-diaspora_jsxc (= 0.1.5.develop.7)!
|
||||
rails-assets-fine-uploader (= 5.13.0)!
|
||||
rails-assets-highlightjs (= 9.12.0)!
|
||||
|
|
@ -888,6 +898,7 @@ DEPENDENCIES
|
|||
spring (= 2.0.2)
|
||||
spring-commands-cucumber (= 1.0.1)
|
||||
spring-commands-rspec (= 1.0.4)
|
||||
sprockets-es6 (= 0.9.2)
|
||||
sprockets-rails (= 3.2.1)
|
||||
string-direction (= 1.2.0)
|
||||
timecop (= 0.9.1)
|
||||
|
|
@ -904,4 +915,4 @@ DEPENDENCIES
|
|||
will_paginate (= 3.1.6)
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.4
|
||||
1.16.1
|
||||
|
|
|
|||
252
app/assets/javascripts/helpers/profile_photo_uploader.es6
Normal file
252
app/assets/javascripts/helpers/profile_photo_uploader.es6
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
Diaspora.ProfilePhotoUploader = class {
|
||||
/**
|
||||
* Initializes a new instance of ProfilePhotoUploader
|
||||
*/
|
||||
constructor() {
|
||||
// get several elements we will use a few times
|
||||
this.fileInput = document.querySelector("#file-upload");
|
||||
this.picture = document.querySelector("#profile_photo_upload .avatar");
|
||||
this.info = document.querySelector("#fileInfo");
|
||||
this.cropContainer = document.querySelector(".crop-container");
|
||||
this.spinner = document.querySelector("#file-upload-spinner");
|
||||
|
||||
/**
|
||||
* Creates a button
|
||||
* @param {string} icon - The entypo icon class.
|
||||
* @param {function} onClick - Is called when button has been clicked.
|
||||
*/
|
||||
this.createButton = (icon, onClick) =>
|
||||
($(`<button class="btn btn-default" type="button"><i class="entypo-${icon}"></i></button>`)
|
||||
.on("click", onClick));
|
||||
|
||||
/**
|
||||
* Shows a message using flash messages or alert for mobile.
|
||||
* @param {string} type - The type of the message, e.g. "error" or "success".
|
||||
* @param text - The text to display.
|
||||
*/
|
||||
this.showMessage = (type, text) => (app.flashMessages ? app.flashMessages[type](text) : alert(text));
|
||||
|
||||
this.initFineUploader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fine uploader component
|
||||
*/
|
||||
initFineUploader() {
|
||||
this.fineUploader = new qq.FineUploaderBasic({
|
||||
element: this.fileInput,
|
||||
validation: {
|
||||
allowedExtensions: ["jpg", "jpeg", "png"]
|
||||
},
|
||||
request: {
|
||||
endpoint: Routes.photos(),
|
||||
params: {
|
||||
/* eslint-disable camelcase */
|
||||
authenticity_token: $("meta[name='csrf-token']").attr("content"),
|
||||
/* eslint-enable camelcase */
|
||||
photo: {"pending": true, "aspect_ids": "all", "set_profile_photo": true}
|
||||
}
|
||||
},
|
||||
button: this.fileInput,
|
||||
autoUpload: false,
|
||||
|
||||
messages: {
|
||||
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
|
||||
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
|
||||
emptyError: Diaspora.I18n.t("photo_uploader.empty")
|
||||
},
|
||||
|
||||
callbacks: {
|
||||
onProgress: (id, fileName, loaded, total) => {
|
||||
(this.info.innerText = `${fileName} ${Math.round(loaded / total * 100)}%`);
|
||||
},
|
||||
onSubmit: (id, name) => this.onPictureSelected(id, name),
|
||||
onComplete: (id, name, responseJSON) => this.onUploadCompleted(id, name, responseJSON),
|
||||
onError: (id, name) => this.showMessage("error", Diaspora.I18n.t("photo_uploader.error", {file: name}))
|
||||
},
|
||||
|
||||
text: {
|
||||
fileInputTitle: ""
|
||||
},
|
||||
|
||||
scaling: {
|
||||
sendOriginal: false,
|
||||
|
||||
sizes: [
|
||||
{maxSize: 1600}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a picture from user's device has been selected.
|
||||
* @param {number} id - The current file's id.
|
||||
* @param {string} name - The current file's name.
|
||||
*/
|
||||
onPictureSelected(id, name) {
|
||||
this.setLoading(true);
|
||||
this.fileName = name;
|
||||
const file = this.fileInput.querySelector("input").files[0];
|
||||
|
||||
// ensure browser's file reader support
|
||||
if (FileReader && file) {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => this.initCropper(fileReader.result);
|
||||
fileReader.readAsDataURL(file);
|
||||
} else {
|
||||
this.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the cropper and all controls.
|
||||
* @param {object|string} imageData - The base64 image data
|
||||
*/
|
||||
initCropper(imageData) {
|
||||
// cache the current picture source if the user cancels
|
||||
this.previousPicture = this.picture.getAttribute("src");
|
||||
|
||||
this.mimeType = imageData.split(";base64")[0].substring(5);
|
||||
|
||||
this.picture.onload = () => {
|
||||
// set the preferred size style of the cropper based on picture orientation
|
||||
const isPortrait = this.picture.naturalHeight > this.picture.naturalWidth;
|
||||
this.picture.setAttribute("style", (isPortrait ? "max-height:600px;max-width:none;" : "max-width:600px;"));
|
||||
this.buildControls();
|
||||
|
||||
this.setLoading(false);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
this.cropper = new Cropper(this.picture, {
|
||||
aspectRatio: 1,
|
||||
zoomable: false,
|
||||
autoCropArea: 1,
|
||||
preview: ".preview"
|
||||
});
|
||||
};
|
||||
this.picture.setAttribute("src", imageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates image manipulation controls and previews.
|
||||
*/
|
||||
buildControls() {
|
||||
this.controls = {
|
||||
rotateLeft: this.createButton("ccw", () => this.cropper.rotate(-45)),
|
||||
rotateRight: this.createButton("cw", () => this.cropper.rotate(45)),
|
||||
reset: this.createButton("cycle", () => this.cropper.reset()),
|
||||
accept: this.createButton("check", () => this.cropImage()),
|
||||
cancel: this.createButton("trash", () => this.cancel())
|
||||
};
|
||||
|
||||
this.controlRow = $("<div class='controls'>").appendTo(this.cropContainer);
|
||||
|
||||
// rotation buttons on the left
|
||||
this.controlRow.append($("<div class='btn-group buttons-left' role='group'>").append([
|
||||
this.controls.rotateLeft,
|
||||
this.controls.rotateRight
|
||||
]));
|
||||
|
||||
// preview images in the middle
|
||||
this.controlRow.append("<div class='preview'>");
|
||||
|
||||
// main buttons on the right
|
||||
this.controlRow.append($("<div class='btn-group buttons-right' role='group'>").append([
|
||||
this.controls.reset,
|
||||
this.controls.cancel,
|
||||
this.controls.accept
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicked accept button. Sets file data and triggers file upload.
|
||||
*/
|
||||
cropImage() {
|
||||
const canvas = this.cropper.getCroppedCanvas();
|
||||
|
||||
// replace the stored file with the new canvas
|
||||
this.fineUploader.clearStoredFiles();
|
||||
this.fineUploader.addFiles([{
|
||||
canvas: canvas,
|
||||
name: this.fileName,
|
||||
quality: 100,
|
||||
type: this.mimeType
|
||||
}]);
|
||||
|
||||
// reset all controls
|
||||
this.cancel();
|
||||
this.picture.setAttribute("src", canvas.toDataURL(this.mimeType));
|
||||
|
||||
// finally start uploading
|
||||
this.setLoading(true);
|
||||
this.fineUploader.uploadStoredFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called after the file upload has been completed and the profile photo changed.
|
||||
* @param {number} id - The current file's id.
|
||||
* @param {string} fileName - The current file's name.
|
||||
* @param {object} responseJSON - The server's json response.
|
||||
*/
|
||||
onUploadCompleted(id, fileName, responseJSON) {
|
||||
this.setLoading(false);
|
||||
this.fileInput.classList.remove("hidden");
|
||||
|
||||
if (responseJSON.data !== undefined) {
|
||||
/* flash message prompt */
|
||||
this.showMessage("success", Diaspora.I18n.t("photo_uploader.looking_good"));
|
||||
|
||||
this.info.innerText = Diaspora.I18n.t("photo_uploader.completed", {"file": fileName});
|
||||
|
||||
const photoId = responseJSON.data.photo.id;
|
||||
const url = responseJSON.data.photo.unprocessed_image.url;
|
||||
const oldPhoto = $("#photo_id");
|
||||
if (oldPhoto.length === 0) {
|
||||
$("#update_profile_form")
|
||||
.prepend(`<input type="hidden" value="${photoId}" id="photo_id" name="photo_id"/>`);
|
||||
} else {
|
||||
oldPhoto.val(photoId);
|
||||
}
|
||||
|
||||
this.picture.setAttribute("src", url);
|
||||
$(`.avatar[alt="${gon.user.diaspora_id}"]`).attr("src", url);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles loading state by hiding or showing several elements
|
||||
* @param {boolean} loading - True if loading state should be enabled.
|
||||
*/
|
||||
setLoading(loading) {
|
||||
if (loading) {
|
||||
this.fileInput.classList.add("hidden");
|
||||
this.picture.classList.add("hidden");
|
||||
this.spinner.classList.remove("hidden");
|
||||
} else {
|
||||
this.picture.classList.remove("hidden");
|
||||
this.spinner.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the cropper and resets all elements to initial state.
|
||||
*/
|
||||
cancel() {
|
||||
this.cropper.destroy();
|
||||
this.picture.onload = null;
|
||||
this.picture.setAttribute("style", "");
|
||||
this.picture.setAttribute("src", this.previousPicture);
|
||||
this.controlRow.remove();
|
||||
this.fileInput.classList.remove("hidden");
|
||||
this.info.innerText = "";
|
||||
|
||||
this.mimeType = null;
|
||||
this.name = null;
|
||||
}
|
||||
};
|
||||
// @license-end
|
||||
|
|
@ -44,3 +44,4 @@
|
|||
//= require bootstrap-markdown/bootstrap-markdown
|
||||
//= require helpers/markdown_editor
|
||||
//= require jquery.are-you-sure
|
||||
//= require cropperjs/dist/cropper.js
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
//= require bootstrap
|
||||
//= require diaspora
|
||||
//= require helpers/i18n
|
||||
//= require helpers/profile_photo_uploader
|
||||
//= require helpers/tags_autocomplete
|
||||
//= require bootstrap-markdown/bootstrap-markdown
|
||||
//= require helpers/markdown_editor
|
||||
|
|
@ -32,4 +31,5 @@
|
|||
//= require mobile/mobile_conversations
|
||||
//= require mobile/mobile_post_actions
|
||||
//= require mobile/mobile_drawer
|
||||
//= require mobile/mobile_profile_photo_uploader
|
||||
// @license-end
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
// profile and settings pages
|
||||
@import 'settings';
|
||||
@import 'cropperjs/dist/cropper';
|
||||
|
||||
// new SPV
|
||||
@import 'header';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
#hello-there {
|
||||
#profile_photo_upload .avatar {
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.well .avatar {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -631,6 +631,12 @@ h1.session {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
margin-top: 10px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
#birth-date {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// scss-lint:enable SelectorFormat
|
||||
|
||||
.enclosed-checkbox label {
|
||||
|
|
@ -28,9 +29,31 @@
|
|||
max-width: 200px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.crop-container {
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-visibility { margin-left: 10px; }
|
||||
.preview {
|
||||
border-radius: 4px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-visibility {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.page-profiles.action-edit textarea {
|
||||
max-width: 100%;
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
# the COPYRIGHT file.
|
||||
|
||||
class PhotosController < ApplicationController
|
||||
before_action :authenticate_user!, except: %i(show index)
|
||||
before_action :authenticate_user!, except: %i[show index]
|
||||
respond_to :html, :json
|
||||
|
||||
def show
|
||||
@photo = if user_signed_in?
|
||||
current_user.photos_from(Person.find_by_guid(params[:person_id])).where(id: params[:id]).first
|
||||
current_user.photos_from(Person.find_by(guid: params[:person_id])).where(id: params[:id]).first
|
||||
else
|
||||
Photo.where(id: params[:id], public: true).first
|
||||
end
|
||||
|
|
@ -20,7 +20,7 @@ class PhotosController < ApplicationController
|
|||
|
||||
def index
|
||||
@post_type = :photos
|
||||
@person = Person.find_by_guid(params[:person_id])
|
||||
@person = Person.find_by(guid: params[:person_id])
|
||||
authenticate_user! if @person.try(:remote?) && !user_signed_in?
|
||||
@presenter = PersonPresenter.new(@person, current_user)
|
||||
|
||||
|
|
@ -35,10 +35,10 @@ class PhotosController < ApplicationController
|
|||
render "people/show", layout: "with_header"
|
||||
end
|
||||
format.mobile { render "people/show" }
|
||||
format.json{ render_for_api :backbone, :json => @posts, :root => :photos }
|
||||
format.json { render_for_api :backbone, json: @posts, root: :photos }
|
||||
end
|
||||
else
|
||||
flash[:error] = I18n.t 'people.show.does_not_exist'
|
||||
flash[:error] = I18n.t "people.show.does_not_exist"
|
||||
redirect_to people_path
|
||||
end
|
||||
end
|
||||
|
|
@ -51,21 +51,23 @@ class PhotosController < ApplicationController
|
|||
|
||||
def make_profile_photo
|
||||
author_id = current_user.person_id
|
||||
@photo = Photo.where(:id => params[:photo_id], :author_id => author_id).first
|
||||
@photo = Photo.where(id: params[:photo_id], author_id: author_id).first
|
||||
|
||||
if @photo
|
||||
profile_hash = {:image_url => @photo.url(:thumb_large),
|
||||
:image_url_medium => @photo.url(:thumb_medium),
|
||||
:image_url_small => @photo.url(:thumb_small)}
|
||||
profile_hash = {image_url: @photo.url(:thumb_large),
|
||||
image_url_medium: @photo.url(:thumb_medium),
|
||||
image_url_small: @photo.url(:thumb_small)}
|
||||
|
||||
if current_user.update_profile(profile_hash)
|
||||
respond_to do |format|
|
||||
format.js{ render :json => { :photo_id => @photo.id,
|
||||
:image_url => @photo.url(:thumb_large),
|
||||
:image_url_medium => @photo.url(:thumb_medium),
|
||||
:image_url_small => @photo.url(:thumb_small),
|
||||
:author_id => author_id},
|
||||
:status => 201}
|
||||
format.js {
|
||||
render json: {photo_id: @photo.id,
|
||||
image_url: @photo.url(:thumb_large),
|
||||
image_url_medium: @photo.url(:thumb_medium),
|
||||
image_url_small: @photo.url(:thumb_small),
|
||||
author_id: author_id},
|
||||
status: 201
|
||||
}
|
||||
end
|
||||
else
|
||||
head :unprocessable_entity
|
||||
|
|
@ -76,7 +78,7 @@ class PhotosController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
photo = current_user.photos.where(:id => params[:id]).first
|
||||
photo = current_user.photos.where(id: params[:id]).first
|
||||
|
||||
if photo
|
||||
current_user.retract(photo)
|
||||
|
|
@ -84,16 +86,16 @@ class PhotosController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
format.html do
|
||||
flash[:notice] = I18n.t 'photos.destroy.notice'
|
||||
if StatusMessage.find_by_guid(photo.status_message_guid)
|
||||
respond_with photo, :location => post_path(photo.status_message)
|
||||
flash[:notice] = I18n.t "photos.destroy.notice"
|
||||
if StatusMessage.find_by(guid: photo.status_message_guid)
|
||||
respond_with photo, location: post_path(photo.status_message)
|
||||
else
|
||||
respond_with photo, :location => person_photos_path(current_user.person)
|
||||
respond_with photo, location: person_photos_path(current_user.person)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
respond_with photo, :location => person_photos_path(current_user.person)
|
||||
respond_with photo, location: person_photos_path(current_user.person)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -105,19 +107,23 @@ class PhotosController < ApplicationController
|
|||
|
||||
def file_handler(params)
|
||||
# For XHR file uploads, request.params[:qqfile] will be the path to the temporary file
|
||||
# For regular form uploads (such as those made by Opera), request.params[:qqfile] will be an UploadedFile which can be returned unaltered.
|
||||
if not request.params[:qqfile].is_a?(String)
|
||||
params[:qqfile]
|
||||
# For regular form uploads (such as those made by Opera), request.params[:qqfile] will be an
|
||||
# UploadedFile which can be returned unaltered.
|
||||
if !request.params[:qqfile].is_a?(String)
|
||||
qqfile = params[:qqfile]
|
||||
# Cropped or manipulated files have their real filename only in qqfilename. Take care of this.
|
||||
qqfile.original_filename = params[:qqfilename] if qqfile.original_filename == "blob"
|
||||
qqfile
|
||||
else
|
||||
######################## dealing with local files #############
|
||||
# get file name
|
||||
file_name = params[:qqfile]
|
||||
# get file content type
|
||||
att_content_type = (request.content_type.to_s == "") ? "application/octet-stream" : request.content_type.to_s
|
||||
att_content_type = request.content_type.to_s == "" ? "application/octet-stream" : request.content_type.to_s
|
||||
# create tempora##l file
|
||||
file = Tempfile.new(file_name, {:encoding => 'BINARY'})
|
||||
file = Tempfile.new(file_name, encoding: "BINARY")
|
||||
# put data into this file from raw post request
|
||||
file.print request.raw_post.force_encoding('BINARY')
|
||||
file.print request.raw_post.force_encoding("BINARY")
|
||||
|
||||
# create several required methods for this temporal file
|
||||
Tempfile.send(:define_method, "content_type") { return att_content_type }
|
||||
|
|
@ -149,36 +155,35 @@ class PhotosController < ApplicationController
|
|||
end
|
||||
|
||||
if photo_params[:set_profile_photo]
|
||||
profile_params = {:image_url => @photo.url(:thumb_large),
|
||||
:image_url_medium => @photo.url(:thumb_medium),
|
||||
:image_url_small => @photo.url(:thumb_small)}
|
||||
profile_params = {image_url: @photo.url(:thumb_large),
|
||||
image_url_medium: @photo.url(:thumb_medium),
|
||||
image_url_small: @photo.url(:thumb_small)}
|
||||
current_user.update_profile(profile_params)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json{ render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
|
||||
format.html{ render(:layout => false , :json => {"success" => true, "data" => @photo}.to_json )}
|
||||
format.json { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
|
||||
format.html { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
|
||||
end
|
||||
else
|
||||
respond_with @photo, :location => photos_path, :error => message
|
||||
respond_with @photo, location: photos_path, error: message
|
||||
end
|
||||
end
|
||||
|
||||
def rescuing_photo_errors
|
||||
begin
|
||||
yield
|
||||
rescue TypeError
|
||||
message = I18n.t 'photos.create.type_error'
|
||||
respond_with @photo, :location => photos_path, :error => message
|
||||
|
||||
return_photo_error I18n.t "photos.create.type_error"
|
||||
rescue CarrierWave::IntegrityError
|
||||
message = I18n.t 'photos.create.integrity_error'
|
||||
respond_with @photo, :location => photos_path, :error => message
|
||||
return_photo_error I18n.t "photos.create.integrity_error"
|
||||
rescue RuntimeError
|
||||
return_photo_error I18n.t "photos.create.runtime_error"
|
||||
end
|
||||
|
||||
rescue RuntimeError => e
|
||||
message = I18n.t 'photos.create.runtime_error'
|
||||
respond_with @photo, :location => photos_path, :error => message
|
||||
raise e
|
||||
def return_photo_error(message)
|
||||
respond_to do |format|
|
||||
format.json { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
|
||||
format.html { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
-# the COPYRIGHT file.
|
||||
|
||||
.profile-photo-upload#profile_photo_upload
|
||||
.crop-container
|
||||
= owner_image_tag(:thumb_large)
|
||||
.small-horizontal-spacer
|
||||
.clearfix
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
#file-upload.btn.btn-primary
|
||||
=t('.upload')
|
||||
|
||||
= image_tag('mobile-spinner.gif', :class => 'hidden', :style => "z-index:-1", :id => 'file-upload-spinner')
|
||||
.spinner.hidden#file-upload-spinner
|
||||
|
||||
%p
|
||||
#fileInfo
|
||||
|
|
|
|||
|
|
@ -44,12 +44,31 @@ Feature: editing your profile
|
|||
And I press "update_profile"
|
||||
Then I should see "#kamino" within "ul#as-selections-tags"
|
||||
And I should see "#starwars" within "ul#as-selections-tags"
|
||||
And I should see a ".crop-container" within "#profile_photo_upload"
|
||||
And the "#profile_public_details" bootstrap-switch should be on
|
||||
|
||||
When I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload"
|
||||
Then I should see a flash message indicating failure
|
||||
|
||||
When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
|
||||
Then I should see a ".cropper-container" within ".crop-container"
|
||||
And I should see a ".controls" within ".crop-container"
|
||||
And I should see a ".preview" within ".controls"
|
||||
And I should see 2 ".btn" within ".buttons-left"
|
||||
And I should see 3 ".btn" within ".buttons-right"
|
||||
|
||||
When I press the 2nd ".btn" within ".buttons-right"
|
||||
Then I should see a ".avatar" within ".crop-container"
|
||||
But I should not see a ".cropper-container" within ".crop-container"
|
||||
|
||||
When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
|
||||
Then I should see a ".cropper-container" within ".crop-container"
|
||||
And I should see a ".controls" within ".crop-container"
|
||||
And I should see a ".preview" within ".controls"
|
||||
And I should see 2 ".btn" within ".buttons-left"
|
||||
And I should see 3 ".btn" within ".buttons-right"
|
||||
|
||||
When I press the 3rd ".btn" within ".buttons-right"
|
||||
Then I should see "button.png completed"
|
||||
And I should see a "img" within "#profile_photo_upload"
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,12 @@ Then /^(?:|I )should see a "([^\"]*)"(?: within "([^\"]*)")?$/ do |selector, sco
|
|||
end
|
||||
end
|
||||
|
||||
Then /^I should see (\d+) "([^\"]*)"(?: within "([^\"]*)")?$/ do |count, selector, scope_selector|
|
||||
with_scope(scope_selector) do
|
||||
expect(current_scope).to have_selector(selector, count: count)
|
||||
end
|
||||
end
|
||||
|
||||
Then /^(?:|I )should not see a "([^\"]*)"(?: within "([^\"]*)")?$/ do |selector, scope_selector|
|
||||
with_scope(scope_selector) do
|
||||
current_scope.should have_no_css(selector, :visible => true)
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ When /^(?:|I )attach the file "([^"]*)" to (?:hidden )?"([^"]*)"(?: within "([^"
|
|||
attach_file(field, Rails.root.join(path).to_s)
|
||||
end
|
||||
# wait for the image to be ready
|
||||
page.assert_selector(".loading", count: 0)
|
||||
page.assert_no_selector(".loading")
|
||||
page.assert_no_selector("#file-upload-spinner")
|
||||
end
|
||||
|
||||
Then /^(?:|I )should see (\".+?\"[\s]*)(?:[\s]+within[\s]* "([^"]*)")?$/ do |vars, selector|
|
||||
|
|
|
|||
Loading…
Reference in a new issue