Skip to content

Commit 536da2f

Browse files
committed
wip/Extract avatar upload to stimulus components
1 parent 6bf6ca5 commit 536da2f

File tree

12 files changed

+179
-13
lines changed

12 files changed

+179
-13
lines changed

app/assets/stylesheets/form-field.scss

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@import './variables.scss';
22

33
.form-field {
4-
margin-top: 20px;
4+
margin-bottom: 20px;
55

66
label {
77
display: block;
@@ -16,7 +16,7 @@
1616
border: 1px solid lightgrey;
1717
font-size: 14px;
1818
display: block;
19-
font-weight: 100;
19+
font-weight: inherit;
2020
}
2121

2222
textarea {
@@ -27,7 +27,8 @@
2727
border: 1px solid lightgrey;
2828
font-size: 14px;
2929
display: block;
30-
font-weight: 100;
30+
font-family: Helvetica, sans-serif;
31+
font-weight: inherit;
3132
}
3233

3334
&--no-margin-top {

app/controllers/users_controller.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
class UsersController < ApplicationController
2+
def edit_avatar_modal
3+
@user = User.find(params[:id])
4+
5+
render layout: false
6+
end
7+
28
def show
39
@user = User.find_by(id: params[:id])
410
@serialized_user = @user.serialize.to_json
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Controller } from 'stimulus'
2+
import 'cropperjs/dist/cropper.css';
3+
import Cropper from 'cropperjs';
4+
5+
export default class extends Controller {
6+
static targets = ['image']
7+
8+
connect() {
9+
this.populateCropper()
10+
}
11+
12+
populateCropper() {
13+
console.log(this.upload)
14+
this.imageTarget.setAttribute('src', this.upload.uploadedFile)
15+
16+
this.cropper = new Cropper(this.imageTarget, {
17+
aspectRatio: 1/ 1
18+
});
19+
}
20+
21+
handleImageCrop() {
22+
const canvas = this.cropper.getCroppedCanvas({
23+
width: 200,
24+
height: 200
25+
});
26+
27+
canvas.toBlob(blob => {
28+
console.log('b', blob)
29+
})
30+
31+
this.upload.avatarTarget.setAttribute('src', canvas.toDataURL())
32+
}
33+
34+
get upload() {
35+
return document.getElementById('upload').upload
36+
}
37+
}

app/javascript/controllers/modal_controller.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,27 @@ import { Controller } from 'stimulus';
22
import axios from 'axios';
33

44
export default class extends Controller {
5-
static targets = ["modal"];
5+
static targets = ["modal", "header", "body"];
66

77
connect() {
88
this.element[this.identifier] = this
99
}
1010

11-
present() {
11+
present(url, opts = {}) {
1212
event.preventDefault()
1313

14-
axios.get(this.url)
14+
axios.get(url)
1515
.then(res => {
16-
document.getElementById('vue-app').insertAdjacentHTML('afterbegin', res.data);
16+
this.bodyTarget.insertAdjacentHTML('afterbegin', res.data);
17+
if (opts.callback) { opts.callback() }
18+
this.modalTarget.classList.remove('hidden')
1719
})
1820
.catch(console.error)
1921
}
2022

2123
close() {
22-
this.modalTarget.remove()
24+
this.modalTarget.classList.add('hidden')
25+
this.bodyTarget.innerHTML = ''
2326
}
2427

2528
get url() {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Controller } from 'stimulus'
2+
3+
export default class extends Controller {
4+
static targets = ['fileInput', 'avatar']
5+
6+
connect() {
7+
this.element[this.identifier] = this
8+
}
9+
10+
selectNewImage() {
11+
this.fileInputTarget.value = ''
12+
this.fileInputTarget.click()
13+
}
14+
15+
handleFileSelect(event) {
16+
const input = event.target;
17+
18+
if (input.files && input.files[0]) {
19+
const self = this;
20+
const reader = new FileReader();
21+
22+
reader.onload = function(e) {
23+
self.uploadedFile = e.target.result
24+
25+
self.modal.present('/users/1/edit_avatar_modal')
26+
}
27+
28+
reader.readAsDataURL(input.files[0]);
29+
}
30+
}
31+
32+
get modal() {
33+
return document.getElementById('modal').modal
34+
}
35+
36+
set cropper(cropperObj) {
37+
return this.cropper = cropperObj
38+
}
39+
}

app/views/devise/registrations/edit.html.erb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,66 @@
1+
<div data-controller="tabs" class="card--container card--container-padding">
2+
<div class="tabs--headers-container">
3+
<div></div>
4+
<div class="tabs--headers-wrapper">
5+
<div data-target="tabs.tab" data-action="click->tabs#change" class="tabs--header tabs--header-active">
6+
<span>Account</span>
7+
</div>
8+
<div data-target="tabs.tab" data-action="click->tabs#change" class="tabs--header">
9+
<span>Security</span>
10+
</div>
11+
</div>
12+
</div>
13+
14+
<div class="tabs-details">
15+
<div data-target="tabs.panel">
16+
<div class="margin-top">
17+
<%= form_for current_user, url: modify_users_path, html: { style: "width: 100%;", method: :put } do |f| %>
18+
<div class="edit-profile--wrapper">
19+
<div class="edit-profile--fields">
20+
<div class="form-field">
21+
<%= f.label :name %>
22+
<%= f.text_field :name %>
23+
</div>
24+
25+
<div class="form-field">
26+
<%= f.label :bio %>
27+
<%= f.text_area :bio %>
28+
</div>
29+
30+
<div class="form-field">
31+
<%= f.label :location %>
32+
<%= f.text_field :location %>
33+
</div>
34+
</div>
35+
36+
<div id="upload" data-controller="upload" class="edit-avatar--wrapper">
37+
<img data-target="upload.avatar" src="<%= current_user.avatar_url %>" width="120" height="120" style="border-radius: 50%;">
38+
<div
39+
class="edit-avatar--edit-button"
40+
data-action="click->upload#selectNewImage">
41+
<img src="/icons/edit.svg" width="16">
42+
<input data-target="upload.fileInput" data-action="change->upload#handleFileSelect" name="avatar" type="file" style="visibility: hidden" />
43+
</div>
44+
</div>
45+
</div>
46+
47+
<div class="folders--options-wrapper">
48+
<%= f.submit "SAVE", class: 'button--cta-new' %>
49+
</div>
50+
<% end %>
51+
52+
<%# <div class="folders--options-wrapper">
53+
<button class="button--cta-new">SAVE</button>
54+
</div> %>
55+
</div>
56+
</div>
57+
58+
<div data-target="tabs.panel" class="hidden">
59+
sec
60+
</div>
61+
</div>
62+
</div>
63+
164
<card>
265
<tabs title="Edit Profile">
366
<tab name="Account" selected="true">

app/views/layouts/application.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<div id="vue-app" v-cloak>
3333
<% end %>
3434
<%= render partial: 'shared/toast' %>
35+
<%= render partial: 'shared/modal' %>
3536

3637
<popover></popover>
3738

app/views/shared/_modal.html.erb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
<div id="modal" data-controller="modal" data-target="modal.modal" class="modal-mask">
1+
<div id="modal" data-controller="modal" data-target="modal.modal" class="modal-mask hidden">
22
<div class="modal-wrapper">
33
<div class="card--container card--container-padding modal-container">
44
<div class="modal-header">
5-
<h3><%= header %></h3>
5+
<h3 data-target="modal.header"></h3>
66
<span data-action="click->modal#close">
77
<img class="modal-close" src="/icons/cancel.svg" width="22">
88
</span>
99
</div>
1010

11-
<div class="modal-body">
12-
<%= yield %>
13-
</div>
11+
<div data-target="modal.body" class="modal-body"></div>
1412

1513
<!-- <div class="modal-footer">
1614
<slot name="footer">
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div data-controller="cropper">
2+
<div>
3+
<img data-target="cropper.image" style="display: block; max-width: 100%;" id="new-avatar">
4+
</div>
5+
6+
<div class="action-buttons">
7+
<button data-action="click->cropper#handleImageCrop" class="button--cta-blue">CROP</button>
8+
</div>
9+
</div>

config/routes.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
post :unfollow, on: :member
4545
get :hovercard, on: :member
4646

47+
# Modals
48+
get :edit_avatar_modal, on: :member
49+
4750
resources :snippets, only: :index
4851
end
4952

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"@rails/ujs": "^6.0.0",
88
"@rails/webpacker": "4.2.2",
99
"axios": "^0.19.2",
10+
"cropperjs": "^1.5.9",
1011
"lodash": "^4.17.15",
1112
"marked": "^0.8.2",
1213
"stimulus": "^1.1.1",

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,6 +2110,11 @@ cropperjs@^1.5.6:
21102110
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.7.tgz#b65019725bae1c6285e881fb661b2141fa57025b"
21112111
integrity sha512-sGj+G/ofKh+f6A4BtXLJwtcKJgMUsXYVUubfTo9grERiDGXncttefmue/fyQFvn8wfdyoD1KhDRYLfjkJFl0yw==
21122112

2113+
cropperjs@^1.5.9:
2114+
version "1.5.9"
2115+
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.9.tgz#4ff9d31e02ad04d2fc5df0044207c2ad53d99da8"
2116+
integrity sha512-aPWlg43sLIcYN4GBXIdyvM09wNPgn1ug+vNVwV8jlb3dbgEX/B34Iw6hrjGSajkUDQBmaCi6uPOevFb7N0yUsw==
2117+
21132118
[email protected], cross-spawn@^6.0.0:
21142119
version "6.0.5"
21152120
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"

0 commit comments

Comments
 (0)