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": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jasmine": true,
|
"jasmine": true,
|
||||||
"jquery": true
|
"jquery": true,
|
||||||
|
"es6": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"globals": {
|
"globals": {
|
||||||
|
|
@ -96,7 +97,7 @@
|
||||||
"no-catch-shadow": 0,
|
"no-catch-shadow": 0,
|
||||||
"no-class-assign": 2,
|
"no-class-assign": 2,
|
||||||
"no-cond-assign": 2,
|
"no-cond-assign": 2,
|
||||||
"no-confusing-arrow": 2,
|
"no-confusing-arrow": [2, {"allowParens": true}],
|
||||||
"no-console": 2,
|
"no-console": 2,
|
||||||
"no-const-assign": 2,
|
"no-const-assign": 2,
|
||||||
"no-constant-condition": 2,
|
"no-constant-condition": 2,
|
||||||
|
|
|
||||||
2
Gemfile
2
Gemfile
|
|
@ -89,6 +89,7 @@ gem "entypo-rails", "3.0.0"
|
||||||
|
|
||||||
# JavaScript
|
# JavaScript
|
||||||
|
|
||||||
|
gem "sprockets-es6", "0.9.2"
|
||||||
gem "handlebars_assets", "0.23.2"
|
gem "handlebars_assets", "0.23.2"
|
||||||
gem "jquery-rails", "4.3.1"
|
gem "jquery-rails", "4.3.1"
|
||||||
gem "js-routes", "1.4.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-backbone", "1.3.3"
|
||||||
gem "rails-assets-bootstrap-markdown", "2.10.0"
|
gem "rails-assets-bootstrap-markdown", "2.10.0"
|
||||||
gem "rails-assets-corejs-typeahead", "1.1.1"
|
gem "rails-assets-corejs-typeahead", "1.1.1"
|
||||||
|
gem "rails-assets-cropperjs", "1.2.1"
|
||||||
gem "rails-assets-fine-uploader", "5.13.0"
|
gem "rails-assets-fine-uploader", "5.13.0"
|
||||||
|
|
||||||
# jQuery plugins
|
# jQuery plugins
|
||||||
|
|
|
||||||
13
Gemfile.lock
13
Gemfile.lock
|
|
@ -62,6 +62,10 @@ GEM
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
autoprefixer-rails (7.1.4.1)
|
autoprefixer-rails (7.1.4.1)
|
||||||
execjs
|
execjs
|
||||||
|
babel-source (5.8.35)
|
||||||
|
babel-transpiler (0.7.0)
|
||||||
|
babel-source (>= 4.0, < 6)
|
||||||
|
execjs (~> 2.0)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
bindata (2.4.1)
|
bindata (2.4.1)
|
||||||
bootstrap-sass (3.3.7)
|
bootstrap-sass (3.3.7)
|
||||||
|
|
@ -516,6 +520,7 @@ GEM
|
||||||
rails-assets-bootstrap (~> 3)
|
rails-assets-bootstrap (~> 3)
|
||||||
rails-assets-corejs-typeahead (1.1.1)
|
rails-assets-corejs-typeahead (1.1.1)
|
||||||
rails-assets-jquery (>= 1.11)
|
rails-assets-jquery (>= 1.11)
|
||||||
|
rails-assets-cropperjs (1.2.1)
|
||||||
rails-assets-diaspora_jsxc (0.1.5.develop.7)
|
rails-assets-diaspora_jsxc (0.1.5.develop.7)
|
||||||
rails-assets-emojione (~> 2.0.1)
|
rails-assets-emojione (~> 2.0.1)
|
||||||
rails-assets-favico.js (>= 0.3.10, < 0.4)
|
rails-assets-favico.js (>= 0.3.10, < 0.4)
|
||||||
|
|
@ -670,6 +675,10 @@ GEM
|
||||||
sprockets (3.7.1)
|
sprockets (3.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
|
sprockets-es6 (0.9.2)
|
||||||
|
babel-source (>= 5.8.11)
|
||||||
|
babel-transpiler
|
||||||
|
sprockets (>= 3.0.0)
|
||||||
sprockets-rails (3.2.1)
|
sprockets-rails (3.2.1)
|
||||||
actionpack (>= 4.0)
|
actionpack (>= 4.0)
|
||||||
activesupport (>= 4.0)
|
activesupport (>= 4.0)
|
||||||
|
|
@ -848,6 +857,7 @@ DEPENDENCIES
|
||||||
rails-assets-blueimp-gallery (= 2.27.0)!
|
rails-assets-blueimp-gallery (= 2.27.0)!
|
||||||
rails-assets-bootstrap-markdown (= 2.10.0)!
|
rails-assets-bootstrap-markdown (= 2.10.0)!
|
||||||
rails-assets-corejs-typeahead (= 1.1.1)!
|
rails-assets-corejs-typeahead (= 1.1.1)!
|
||||||
|
rails-assets-cropperjs (= 1.2.1)!
|
||||||
rails-assets-diaspora_jsxc (= 0.1.5.develop.7)!
|
rails-assets-diaspora_jsxc (= 0.1.5.develop.7)!
|
||||||
rails-assets-fine-uploader (= 5.13.0)!
|
rails-assets-fine-uploader (= 5.13.0)!
|
||||||
rails-assets-highlightjs (= 9.12.0)!
|
rails-assets-highlightjs (= 9.12.0)!
|
||||||
|
|
@ -888,6 +898,7 @@ DEPENDENCIES
|
||||||
spring (= 2.0.2)
|
spring (= 2.0.2)
|
||||||
spring-commands-cucumber (= 1.0.1)
|
spring-commands-cucumber (= 1.0.1)
|
||||||
spring-commands-rspec (= 1.0.4)
|
spring-commands-rspec (= 1.0.4)
|
||||||
|
sprockets-es6 (= 0.9.2)
|
||||||
sprockets-rails (= 3.2.1)
|
sprockets-rails (= 3.2.1)
|
||||||
string-direction (= 1.2.0)
|
string-direction (= 1.2.0)
|
||||||
timecop (= 0.9.1)
|
timecop (= 0.9.1)
|
||||||
|
|
@ -904,4 +915,4 @@ DEPENDENCIES
|
||||||
will_paginate (= 3.1.6)
|
will_paginate (= 3.1.6)
|
||||||
|
|
||||||
BUNDLED WITH
|
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 bootstrap-markdown/bootstrap-markdown
|
||||||
//= require helpers/markdown_editor
|
//= require helpers/markdown_editor
|
||||||
//= require jquery.are-you-sure
|
//= require jquery.are-you-sure
|
||||||
|
//= require cropperjs/dist/cropper.js
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
//= require bootstrap
|
//= require bootstrap
|
||||||
//= require diaspora
|
//= require diaspora
|
||||||
//= require helpers/i18n
|
//= require helpers/i18n
|
||||||
//= require helpers/profile_photo_uploader
|
|
||||||
//= require helpers/tags_autocomplete
|
//= require helpers/tags_autocomplete
|
||||||
//= require bootstrap-markdown/bootstrap-markdown
|
//= require bootstrap-markdown/bootstrap-markdown
|
||||||
//= require helpers/markdown_editor
|
//= require helpers/markdown_editor
|
||||||
|
|
@ -32,4 +31,5 @@
|
||||||
//= require mobile/mobile_conversations
|
//= require mobile/mobile_conversations
|
||||||
//= require mobile/mobile_post_actions
|
//= require mobile/mobile_post_actions
|
||||||
//= require mobile/mobile_drawer
|
//= require mobile/mobile_drawer
|
||||||
|
//= require mobile/mobile_profile_photo_uploader
|
||||||
// @license-end
|
// @license-end
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
|
|
||||||
// profile and settings pages
|
// profile and settings pages
|
||||||
@import 'settings';
|
@import 'settings';
|
||||||
|
@import 'cropperjs/dist/cropper';
|
||||||
|
|
||||||
// new SPV
|
// new SPV
|
||||||
@import 'header';
|
@import 'header';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
#hello-there {
|
#hello-there {
|
||||||
#profile_photo_upload .avatar {
|
|
||||||
max-height: 200px;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.well .avatar {
|
.well .avatar {
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -631,6 +631,12 @@ h1.session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#birth-date {
|
#birth-date {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scss-lint:enable SelectorFormat
|
// scss-lint:enable SelectorFormat
|
||||||
|
|
||||||
.enclosed-checkbox label {
|
.enclosed-checkbox label {
|
||||||
|
|
@ -28,9 +29,31 @@
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crop-container {
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-visibility { margin-left: 10px; }
|
.settings-visibility {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.page-profiles.action-edit textarea {
|
.page-profiles.action-edit textarea {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
# the COPYRIGHT file.
|
# the COPYRIGHT file.
|
||||||
|
|
||||||
class PhotosController < ApplicationController
|
class PhotosController < ApplicationController
|
||||||
before_action :authenticate_user!, except: %i(show index)
|
before_action :authenticate_user!, except: %i[show index]
|
||||||
respond_to :html, :json
|
respond_to :html, :json
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@photo = if user_signed_in?
|
@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
|
else
|
||||||
Photo.where(id: params[:id], public: true).first
|
Photo.where(id: params[:id], public: true).first
|
||||||
end
|
end
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound unless @photo
|
raise ActiveRecord::RecordNotFound unless @photo
|
||||||
|
|
@ -20,7 +20,7 @@ class PhotosController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@post_type = :photos
|
@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?
|
authenticate_user! if @person.try(:remote?) && !user_signed_in?
|
||||||
@presenter = PersonPresenter.new(@person, current_user)
|
@presenter = PersonPresenter.new(@person, current_user)
|
||||||
|
|
||||||
|
|
@ -35,10 +35,10 @@ class PhotosController < ApplicationController
|
||||||
render "people/show", layout: "with_header"
|
render "people/show", layout: "with_header"
|
||||||
end
|
end
|
||||||
format.mobile { render "people/show" }
|
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
|
end
|
||||||
else
|
else
|
||||||
flash[:error] = I18n.t 'people.show.does_not_exist'
|
flash[:error] = I18n.t "people.show.does_not_exist"
|
||||||
redirect_to people_path
|
redirect_to people_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -51,21 +51,23 @@ class PhotosController < ApplicationController
|
||||||
|
|
||||||
def make_profile_photo
|
def make_profile_photo
|
||||||
author_id = current_user.person_id
|
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
|
if @photo
|
||||||
profile_hash = {:image_url => @photo.url(:thumb_large),
|
profile_hash = {image_url: @photo.url(:thumb_large),
|
||||||
:image_url_medium => @photo.url(:thumb_medium),
|
image_url_medium: @photo.url(:thumb_medium),
|
||||||
:image_url_small => @photo.url(:thumb_small)}
|
image_url_small: @photo.url(:thumb_small)}
|
||||||
|
|
||||||
if current_user.update_profile(profile_hash)
|
if current_user.update_profile(profile_hash)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.js{ render :json => { :photo_id => @photo.id,
|
format.js {
|
||||||
:image_url => @photo.url(:thumb_large),
|
render json: {photo_id: @photo.id,
|
||||||
:image_url_medium => @photo.url(:thumb_medium),
|
image_url: @photo.url(:thumb_large),
|
||||||
:image_url_small => @photo.url(:thumb_small),
|
image_url_medium: @photo.url(:thumb_medium),
|
||||||
:author_id => author_id},
|
image_url_small: @photo.url(:thumb_small),
|
||||||
:status => 201}
|
author_id: author_id},
|
||||||
|
status: 201
|
||||||
|
}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
head :unprocessable_entity
|
head :unprocessable_entity
|
||||||
|
|
@ -76,7 +78,7 @@ class PhotosController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
photo = current_user.photos.where(:id => params[:id]).first
|
photo = current_user.photos.where(id: params[:id]).first
|
||||||
|
|
||||||
if photo
|
if photo
|
||||||
current_user.retract(photo)
|
current_user.retract(photo)
|
||||||
|
|
@ -84,16 +86,16 @@ class PhotosController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json { head :no_content }
|
format.json { head :no_content }
|
||||||
format.html do
|
format.html do
|
||||||
flash[:notice] = I18n.t 'photos.destroy.notice'
|
flash[:notice] = I18n.t "photos.destroy.notice"
|
||||||
if StatusMessage.find_by_guid(photo.status_message_guid)
|
if StatusMessage.find_by(guid: photo.status_message_guid)
|
||||||
respond_with photo, :location => post_path(photo.status_message)
|
respond_with photo, location: post_path(photo.status_message)
|
||||||
else
|
else
|
||||||
respond_with photo, :location => person_photos_path(current_user.person)
|
respond_with photo, location: person_photos_path(current_user.person)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
respond_with photo, :location => person_photos_path(current_user.person)
|
respond_with photo, location: person_photos_path(current_user.person)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -105,23 +107,27 @@ class PhotosController < ApplicationController
|
||||||
|
|
||||||
def file_handler(params)
|
def file_handler(params)
|
||||||
# For XHR file uploads, request.params[:qqfile] will be the path to the temporary file
|
# 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.
|
# For regular form uploads (such as those made by Opera), request.params[:qqfile] will be an
|
||||||
if not request.params[:qqfile].is_a?(String)
|
# UploadedFile which can be returned unaltered.
|
||||||
params[:qqfile]
|
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
|
else
|
||||||
######################## dealing with local files #############
|
######################## dealing with local files #############
|
||||||
# get file name
|
# get file name
|
||||||
file_name = params[:qqfile]
|
file_name = params[:qqfile]
|
||||||
# get file content type
|
# 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
|
# 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
|
# 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
|
# create several required methods for this temporal file
|
||||||
Tempfile.send(:define_method, "content_type") {return att_content_type}
|
Tempfile.send(:define_method, "content_type") { return att_content_type }
|
||||||
Tempfile.send(:define_method, "original_filename") {return file_name}
|
Tempfile.send(:define_method, "original_filename") { return file_name }
|
||||||
file
|
file
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -149,36 +155,35 @@ class PhotosController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
if photo_params[:set_profile_photo]
|
if photo_params[:set_profile_photo]
|
||||||
profile_params = {:image_url => @photo.url(:thumb_large),
|
profile_params = {image_url: @photo.url(:thumb_large),
|
||||||
:image_url_medium => @photo.url(:thumb_medium),
|
image_url_medium: @photo.url(:thumb_medium),
|
||||||
:image_url_small => @photo.url(:thumb_small)}
|
image_url_small: @photo.url(:thumb_small)}
|
||||||
current_user.update_profile(profile_params)
|
current_user.update_profile(profile_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json{ 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 )}
|
format.html { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
respond_with @photo, :location => photos_path, :error => message
|
respond_with @photo, location: photos_path, error: message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def rescuing_photo_errors
|
def rescuing_photo_errors
|
||||||
begin
|
yield
|
||||||
yield
|
rescue TypeError
|
||||||
rescue TypeError
|
return_photo_error I18n.t "photos.create.type_error"
|
||||||
message = I18n.t 'photos.create.type_error'
|
rescue CarrierWave::IntegrityError
|
||||||
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 CarrierWave::IntegrityError
|
def return_photo_error(message)
|
||||||
message = I18n.t 'photos.create.integrity_error'
|
respond_to do |format|
|
||||||
respond_with @photo, :location => photos_path, :error => message
|
format.json { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
|
||||||
|
format.html { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
|
||||||
rescue RuntimeError => e
|
|
||||||
message = I18n.t 'photos.create.runtime_error'
|
|
||||||
respond_with @photo, :location => photos_path, :error => message
|
|
||||||
raise e
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
-# the COPYRIGHT file.
|
-# the COPYRIGHT file.
|
||||||
|
|
||||||
.profile-photo-upload#profile_photo_upload
|
.profile-photo-upload#profile_photo_upload
|
||||||
= owner_image_tag(:thumb_large)
|
.crop-container
|
||||||
|
= owner_image_tag(:thumb_large)
|
||||||
.small-horizontal-spacer
|
.small-horizontal-spacer
|
||||||
.clearfix
|
.clearfix
|
||||||
.text-center
|
.text-center
|
||||||
#file-upload.btn.btn-primary
|
#file-upload.btn.btn-primary
|
||||||
=t('.upload')
|
=t('.upload')
|
||||||
|
|
||||||
= image_tag('mobile-spinner.gif', :class => 'hidden', :style => "z-index:-1", :id => 'file-upload-spinner')
|
.spinner.hidden#file-upload-spinner
|
||||||
|
|
||||||
%p
|
%p
|
||||||
#fileInfo
|
#fileInfo
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,31 @@ Feature: editing your profile
|
||||||
And I press "update_profile"
|
And I press "update_profile"
|
||||||
Then I should see "#kamino" within "ul#as-selections-tags"
|
Then I should see "#kamino" within "ul#as-selections-tags"
|
||||||
And I should see "#starwars" 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
|
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"
|
When I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload"
|
||||||
Then I should see a flash message indicating failure
|
Then I should see a flash message indicating failure
|
||||||
|
|
||||||
When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
|
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"
|
Then I should see "button.png completed"
|
||||||
And I should see a "img" within "#profile_photo_upload"
|
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
|
||||||
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|
|
Then /^(?:|I )should not see a "([^\"]*)"(?: within "([^\"]*)")?$/ do |selector, scope_selector|
|
||||||
with_scope(scope_selector) do
|
with_scope(scope_selector) do
|
||||||
current_scope.should have_no_css(selector, :visible => true)
|
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)
|
attach_file(field, Rails.root.join(path).to_s)
|
||||||
end
|
end
|
||||||
# wait for the image to be ready
|
# 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
|
end
|
||||||
|
|
||||||
Then /^(?:|I )should see (\".+?\"[\s]*)(?:[\s]+within[\s]* "([^"]*)")?$/ do |vars, selector|
|
Then /^(?:|I )should see (\".+?\"[\s]*)(?:[\s]+within[\s]* "([^"]*)")?$/ do |vars, selector|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue