Skip to content

Commit 5cf8b58

Browse files
authored
Merge pull request #643 from coopdevs/develop
v3.19.0
2 parents aced8bc + 1e8fb13 commit 5cf8b58

32 files changed

+603
-51
lines changed

.env.example

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# wont be uploaded to the repo.
1010

1111

12-
# database setup:
12+
# database setup
1313
DATABASE_USER=postgres
1414
DATABASE_NAME=timeoverflow_development
1515

@@ -19,3 +19,9 @@ MAIL_LINK_PROTO=http
1919

2020
# a list of emails for superadmin users
2121
ADMINS="admin@timeoverflow.org"
22+
23+
# AWS settings
24+
AWS_ACCESS_KEY_ID=XXXXXXXX
25+
AWS_SECRET_ACCESS_KEY=XXXXXXXX
26+
AWS_BUCKET=timeoverflow_development
27+
AWS_REGION=us-east-1

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ tags
2626
.sass-cache
2727
capybara-*.html
2828
/vendor/bundle
29+
/storage/
2930
/coverage/
3031
/spec/tmp/*
3132
.byebug_history

Gemfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ gem 'rollbar', '~> 2.22.1'
2121
gem 'prawn', '~> 2.4.0'
2222
gem 'prawn-table', '~> 0.2.2'
2323
gem 'pg_search', '~> 2.3.5'
24-
gem 'skylight', '~> 4.3.2'
24+
gem 'skylight', '~> 5.0'
2525
gem 'sidekiq', '~> 6.1.2'
2626
gem 'sidekiq-cron', '~> 1.2.0'
27+
gem 'aws-sdk-s3', '~> 1.94', require: false
28+
gem 'image_processing', '~> 1.2'
2729

2830
# Assets
2931
gem 'jquery-rails', '~> 4.3.5'

Gemfile.lock

+29-6
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ GEM
7979
ast (2.4.2)
8080
autoprefixer-rails (10.2.4.0)
8181
execjs
82+
aws-eventstream (1.1.1)
83+
aws-partitions (1.451.0)
84+
aws-sdk-core (3.114.0)
85+
aws-eventstream (~> 1, >= 1.0.2)
86+
aws-partitions (~> 1, >= 1.239.0)
87+
aws-sigv4 (~> 1.1)
88+
jmespath (~> 1.0)
89+
aws-sdk-kms (1.43.0)
90+
aws-sdk-core (~> 3, >= 3.112.0)
91+
aws-sigv4 (~> 1.1)
92+
aws-sdk-s3 (1.94.1)
93+
aws-sdk-core (~> 3, >= 3.112.0)
94+
aws-sdk-kms (~> 1)
95+
aws-sigv4 (~> 1.1)
96+
aws-sigv4 (1.2.3)
97+
aws-eventstream (~> 1, >= 1.0.2)
8298
bcrypt (3.1.16)
8399
bindex (0.8.1)
84100
bootsnap (1.7.4)
@@ -154,11 +170,15 @@ GEM
154170
http_accept_language (2.1.1)
155171
i18n (1.8.10)
156172
concurrent-ruby (~> 1.0)
173+
image_processing (1.12.1)
174+
mini_magick (>= 4.9.5, < 5)
175+
ruby-vips (>= 2.0.17, < 3)
157176
inherited_resources (1.12.0)
158177
actionpack (>= 5.2, < 6.2)
159178
has_scope (~> 0.6)
160179
railties (>= 5.2, < 6.2)
161180
responders (>= 2, < 4)
181+
jmespath (1.4.0)
162182
jquery-rails (4.3.5)
163183
rails-dom-testing (>= 1, < 3)
164184
railties (>= 4.2.0)
@@ -201,6 +221,7 @@ GEM
201221
mime-types (3.3.1)
202222
mime-types-data (~> 3.2015)
203223
mime-types-data (3.2021.0225)
224+
mini_magick (4.11.0)
204225
mini_mime (1.0.3)
205226
mini_portile2 (2.5.1)
206227
minitest (5.14.4)
@@ -210,7 +231,7 @@ GEM
210231
net-ssh (6.1.0)
211232
netrc (0.11.0)
212233
nio4r (2.5.7)
213-
nokogiri (1.11.3)
234+
nokogiri (1.11.4)
214235
mini_portile2 (~> 2.5.0)
215236
racc (~> 1.4)
216237
orm_adapter (0.5.0)
@@ -324,6 +345,8 @@ GEM
324345
rack (>= 1.1)
325346
rubocop (>= 0.90.0, < 2.0)
326347
ruby-progressbar (1.11.0)
348+
ruby-vips (2.1.0)
349+
ffi (~> 1.12)
327350
ruby2_keywords (0.0.4)
328351
rubyzip (2.3.0)
329352
sassc (2.4.0)
@@ -356,10 +379,8 @@ GEM
356379
simplecov_json_formatter (~> 0.1)
357380
simplecov-html (0.12.3)
358381
simplecov_json_formatter (0.1.3)
359-
skylight (4.3.2)
360-
skylight-core (= 4.3.2)
361-
skylight-core (4.3.2)
362-
activesupport (>= 4.2.0)
382+
skylight (5.0.1)
383+
activesupport (>= 5.2.0)
363384
sprockets (4.0.2)
364385
concurrent-ruby (~> 1.0)
365386
rack (> 1, < 3)
@@ -407,6 +428,7 @@ PLATFORMS
407428

408429
DEPENDENCIES
409430
activeadmin (~> 2.9.0)
431+
aws-sdk-s3 (~> 1.94)
410432
bootsnap (~> 1.7.3)
411433
bootstrap-sass (~> 3.4)
412434
byebug (~> 11.0)
@@ -421,6 +443,7 @@ DEPENDENCIES
421443
faker (~> 2.15)
422444
has_scope (~> 0.7.2)
423445
http_accept_language (~> 2.1.1)
446+
image_processing (~> 1.2)
424447
jquery-rails (~> 4.3.5)
425448
json_translate (~> 4.0.0)
426449
kaminari (~> 1.2.1)
@@ -449,7 +472,7 @@ DEPENDENCIES
449472
sidekiq-cron (~> 1.2.0)
450473
simple_form (~> 5.0.2)
451474
simplecov (~> 0.17)
452-
skylight (~> 4.3.2)
475+
skylight (~> 5.0)
453476
uglifier (~> 4.2.0)
454477
unicorn (~> 5.5.1)
455478
web-console (~> 4.1.0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
$(function () {
2+
$('#avatar-js').on("change", () => {
3+
$('#dialog').modal({
4+
show: true
5+
});
6+
7+
preview_image_modal();
8+
9+
const panel = $('#crop_panel');
10+
let m_pos;
11+
let x_axis = true;
12+
13+
//add a listener to the document depending on where you tap in the box
14+
panel.on("pointerdown", function(e) {
15+
const BORDER_SIZE = 20;
16+
17+
if (e.offsetY >= (parseInt(panel.css('height')) - BORDER_SIZE)) {
18+
m_pos = e.y;
19+
x_axis = false;
20+
document.addEventListener("pointermove", resize, false);
21+
} else if (e.offsetX >= (parseInt(panel.css('width')) - BORDER_SIZE)) {
22+
m_pos = e.x;
23+
x_axis = true;
24+
document.addEventListener("pointermove", resize, false);
25+
} else {
26+
pos3 = e.clientX;
27+
pos4 = e.clientY;
28+
document.addEventListener("pointermove", drag, false);
29+
}
30+
});
31+
32+
// remove listeners from the document when you stop pressing
33+
document.addEventListener("pointerup", function () {
34+
document.removeEventListener("pointermove", resize, false);
35+
document.removeEventListener("pointermove", drag, false);
36+
document.removeEventListener("pointermove", resize, false);
37+
}, false);
38+
39+
// on submit take the parameters of the box to crop the avatar
40+
$('#form_photo').on("submit", () => {
41+
let total_width = parseInt(getComputedStyle(document.getElementById("containerCrop")).width);
42+
let photo_width = parseInt(getComputedStyle(document.getElementById("foto")).width);
43+
let left_displacement = total_width - photo_width;
44+
45+
$('#height_offset').val(parseInt(panel.css('top')) - 15);
46+
$('#width_offset').val(parseInt(panel.css('left')) - 15 - (left_displacement/2));
47+
$('#height_width').val(parseInt(panel.css('width')));
48+
$('#original_width').val($('#foto').width());
49+
});
50+
51+
function resize(e) {
52+
e = e || window.event;
53+
e.preventDefault();
54+
55+
let mov = x_axis ? e.x : e.y;
56+
const dx = m_pos - mov;
57+
m_pos = mov;
58+
59+
if (can_change(panel, dx, false)) {
60+
panel.width(panel.width() - dx);
61+
panel.height(panel.width() - dx);
62+
}
63+
}
64+
65+
function drag(e) {
66+
e = e || window.event;
67+
e.preventDefault();
68+
69+
pos1 = pos3 - e.x;
70+
pos2 = pos4 - e.y;
71+
pos3 = e.x;
72+
pos4 = e.y;
73+
74+
if (can_change(panel, pos1 + pos2, true)) {
75+
panel.offset({
76+
top: (panel.offset().top - pos2),
77+
left: (panel.offset().left - pos1)
78+
});
79+
}
80+
}
81+
82+
function can_change(el, mov, dragging) {
83+
let canChange = true;
84+
let pos = dragging ? [el.css('top'), el.css('left'), el.css('bottom'), el.css('right')] : [el.css('bottom'), el.css('right')];
85+
86+
pos.forEach((el, ix) => {
87+
let next = dragging && (ix == 0 || ix == 1) ? parseInt(el) - mov : parseInt(el) + mov;
88+
89+
if (next < 14) canChange = false;
90+
});
91+
92+
return canChange;
93+
}
94+
95+
function preview_image_modal() {
96+
var preview = document.querySelector('#foto');
97+
var file = document.querySelector('#avatar-js').files[0];
98+
var reader = new FileReader();
99+
100+
reader.onloadend = function () {
101+
preview.src = reader.result;
102+
}
103+
104+
if (file)
105+
reader.readAsDataURL(file);
106+
else
107+
preview.src = "";
108+
}
109+
});
110+
});

app/assets/stylesheets/application.scss

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@import "bootstrap-sprockets";
44
@import "application/bootstrap-custom";
55
@import "application/member-card";
6+
@import "application/avatar";
67
@import "application/footer";
78

89
html {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#form_photo {
2+
border: 0px;
3+
padding: 0px;
4+
margin-bottom: 0px;
5+
}
6+
7+
#crop_panel {
8+
position: absolute;
9+
width: 140px;
10+
height: 140px;
11+
border-left: 4px dashed black;
12+
cursor: move;
13+
touch-action: none;
14+
border-top: 4px dashed black;
15+
}
16+
17+
#crop_panel::before {
18+
content: "";
19+
border-right: 4px dashed black;
20+
position: absolute;
21+
right: 0;
22+
width: 16px;
23+
height: 100%;
24+
cursor: w-resize;
25+
}
26+
27+
#crop_panel::after {
28+
content: "";
29+
border-bottom: 4px dashed black;
30+
position: absolute;
31+
left: 0;
32+
bottom: 0;
33+
width: 100%;
34+
height: 16px;
35+
cursor: n-resize;
36+
}

app/controllers/users_controller.rb

+38
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ def update
6565
end
6666
end
6767

68+
def update_avatar
69+
avatar = params[:avatar]
70+
71+
if avatar && content_type_permitted(avatar.content_type)
72+
current_user.avatar.purge if current_user.avatar.attached?
73+
74+
crop_image_and_save(current_user, avatar)
75+
else
76+
flash[:error] = t 'users.show.invalid_format'
77+
end
78+
79+
redirect_to current_user
80+
end
81+
6882
private
6983

7084
def search_and_load_members(members_scope, default_search_params)
@@ -116,4 +130,28 @@ def redirect_to_after_create
116130
name: @user.username)
117131
end
118132
end
133+
134+
def crop_image_and_save(user, avatar)
135+
orig_width = params[:original_width].to_i
136+
width = params[:height_width].to_i
137+
left = params[:width_offset].to_i
138+
top = params[:height_offset].to_i
139+
140+
image_processed = ImageProcessing::MiniMagick.
141+
source(avatar.tempfile).
142+
resize_to_fit(orig_width, nil).
143+
crop("#{width}x#{width}+#{left}+#{top}!").
144+
convert("png").
145+
call
146+
147+
user.avatar.attach(
148+
io: image_processed,
149+
filename: user.username,
150+
content_type: avatar.content_type
151+
)
152+
end
153+
154+
def content_type_permitted(avatar_content_type)
155+
%w[image/jpeg image/pjpeg image/png image/x-png].include? avatar_content_type
156+
end
119157
end

app/decorators/member_decorator.rb

-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ def mail_to
2323
view.mail_to(email) if email && !email.end_with?('example.com')
2424
end
2525

26-
def avatar_img(size=32)
27-
view.image_tag(view.avatar_url(user, size), width: size, height: size)
28-
end
29-
3026
def account_balance
3127
view.seconds_to_hm(object.account.try(:balance))
3228
end

app/helpers/application_helper.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ def page_title
66
current_organization || 'TimeOverflow'
77
end
88

9-
# from gravatar
109
def avatar_url(user, size = 32)
10+
user.avatar.attached? ?
11+
user.avatar.variant(resize: "#{size}x#{size}") :
12+
gravatar_url(user, size)
13+
end
14+
15+
def gravatar_url(user, size = 32)
1116
gravatar_id = Digest::MD5::hexdigest(user.email).downcase
1217
gravatar_options = {
1318
set: "set1",

app/models/user.rb

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class User < ApplicationRecord
1818

1919
attr_accessor :empty_email
2020

21+
has_one_attached :avatar
2122
has_many :members, dependent: :destroy
2223
has_many :organizations, through: :members
2324
has_many :accounts, through: :members

0 commit comments

Comments
 (0)