Merge pull request #732 from Human-Connection/688-hover-effect-user-image-upload

Hover effect user image upload
This commit is contained in:
Robert Schäfer 2019-06-06 15:54:50 +02:00 committed by GitHub
commit ab719eca3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 96 deletions

View File

@ -12,14 +12,16 @@ Then('I should be able to change my profile picture', () => {
cy.fixture(avatarUpload, 'base64').then(fileContent => { cy.fixture(avatarUpload, 'base64').then(fileContent => {
cy.get('#customdropzone').upload( cy.get('#customdropzone').upload(
{ fileContent, fileName: avatarUpload, mimeType: 'image/png' }, { fileContent, fileName: avatarUpload, mimeType: 'image/png' },
{ subjectType: 'drag-n-drop' }, { subjectType: 'drag-n-drop' }
) )
}) })
cy.get('#customdropzone') cy.get('.profile-avatar img')
.should('have.attr', 'style') .should('have.attr', 'src')
.and('contains', 'onourjourney') .and('contains', 'onourjourney')
cy.contains('.iziToast-message', 'Upload successful') cy.contains('.iziToast-message', 'Upload successful').should(
.should('have.length', 1) 'have.length',
1
)
}) })
When("I visit another user's profile page", () => { When("I visit another user's profile page", () => {

View File

@ -4,21 +4,30 @@
id="customdropzone" id="customdropzone"
:key="user.avatar" :key="user.avatar"
ref="el" ref="el"
:use-custom-slot="true"
:options="dropzoneOptions" :options="dropzoneOptions"
:include-styling="false"
:style="backgroundImage"
@vdropzone-thumbnail="thumbnail"
@vdropzone-error="verror" @vdropzone-error="verror"
/> >
<div class="dz-message" @mouseover="hover = true" @mouseleave="hover = false">
<hc-avatar :user="user" class="profile-avatar" size="x-large"></hc-avatar>
<div class="hc-attachments-upload-area">
<div class="hc-drag-marker">
<ds-icon v-if="hover" name="image" size="xxx-large" />
</div>
</div>
</div>
</vue-dropzone>
</div> </div>
</template> </template>
<script> <script>
import vueDropzone from 'nuxt-dropzone' import vueDropzone from 'nuxt-dropzone'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import HcAvatar from '~/components/Avatar/Avatar.vue'
export default { export default {
components: { components: {
vueDropzone, vueDropzone,
HcAvatar,
}, },
props: { props: {
user: { type: Object, default: null }, user: { type: Object, default: null },
@ -29,22 +38,11 @@ export default {
url: this.vddrop, url: this.vddrop,
maxFilesize: 5.0, maxFilesize: 5.0,
previewTemplate: this.template(), previewTemplate: this.template(),
dictDefaultMessage: '',
}, },
error: false, error: false,
hover: false,
} }
}, },
computed: {
backgroundImage() {
const avatar =
this.user.avatar ||
'https://human-connection.org/wp-content/uploads/2019/03/human-connection-logo.svg'
const userAvatar = avatar.startsWith('/') ? avatar.replace('/', '/api/') : avatar
return {
backgroundImage: `url(${userAvatar})`,
}
},
},
watch: { watch: {
error() { error() {
let that = this let that = this
@ -62,20 +60,6 @@ export default {
</div> </div>
` `
}, },
thumbnail(file, dataUrl) {
let j, len, ref, thumbnailElement
if (file.previewElement) {
this.$refs.el.$el.style.backgroundImage = ''
file.previewElement.classList.remove('dz-file-preview')
ref = file.previewElement.querySelectorAll('[data-dz-thumbnail-bg]')
for (j = 0, len = ref.length; j < len; j++) {
thumbnailElement = ref[j]
thumbnailElement.alt = file.name
thumbnailElement.style.backgroundImage = 'url("' + dataUrl + '")'
}
file.previewElement.classList.add('dz-image-preview')
}
},
vddrop(file) { vddrop(file) {
const avatarUpload = file[0] const avatarUpload = file[0]
this.$apollo this.$apollo
@ -107,25 +91,7 @@ export default {
}, },
} }
</script> </script>
<style> <style lang="scss">
#customdropzone {
margin: -60px auto auto;
width: 122px;
min-height: 122px;
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
font-family: 'Arial', sans-serif;
letter-spacing: 0.2px;
color: #777;
transition: background-color 0.2s linear;
padding: 40px;
}
#customdropzone:hover {
cursor: pointer;
}
#customdropzone .dz-preview { #customdropzone .dz-preview {
transition: all 0.2s ease-out; transition: all 0.2s ease-out;
width: 160px; width: 160px;
@ -150,15 +116,59 @@ export default {
width: 100%; width: 100%;
} }
#customdropzone .dz-preview .dz-details { .hc-attachments-upload-area {
color: white; position: relative;
transition: opacity 0.2s linear; display: flex;
text-align: center; align-items: center;
justify-content: center;
cursor: pointer;
} }
#customdropzone .dz-success-mark, .hc-attachments-upload-button {
.dz-error-mark, pointer-events: none;
.dz-remove { }
display: none;
.hc-drag-marker {
position: relative;
width: 122px;
height: 122px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
color: hsl(0, 0%, 25%);
transition: all 0.2s ease-out;
font-size: 60px;
margin: -120px auto 5px;
background-color: rgba(255, 255, 255, 0.3);
opacity: 0.1;
&:before {
position: absolute;
content: '';
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 100%;
border: 20px solid rgba(255, 255, 255, 0.4);
visibility: hidden;
}
&:after {
position: absolute;
content: '';
top: 10px;
left: 10px;
bottom: 10px;
right: 10px;
border-radius: 100%;
border: 1px dashed hsl(0, 0%, 25%);
}
.hc-attachments-upload-area:hover & {
opacity: 1;
}
} }
</style> </style>

View File

@ -35,26 +35,6 @@ describe('Upload', () => {
}, },
} }
const fileSuccess = {
filename: 'avatar.jpg',
previewElement: {
classList: {
remove: jest.fn(),
add: jest.fn(),
},
querySelectorAll: jest.fn().mockReturnValue([
{
alt: '',
style: {
'background-image': '/api/generic.jpg',
},
},
]),
},
}
const dataUrl = 'avatar.jpg'
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers() jest.useFakeTimers()
wrapper = shallowMount(Upload, { localVue, propsData, mocks }) wrapper = shallowMount(Upload, { localVue, propsData, mocks })
@ -69,11 +49,6 @@ describe('Upload', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
}) })
it('thumbnail', () => {
wrapper.vm.thumbnail(fileSuccess, dataUrl)
expect(fileSuccess.previewElement.classList.add).toHaveBeenCalledTimes(1)
})
describe('error handling', () => { describe('error handling', () => {
const message = 'File upload failed' const message = 'File upload failed'
const fileError = { status: 'error' } const fileError = { status: 'error' }
@ -93,5 +68,15 @@ describe('Upload', () => {
jest.runAllTimers() jest.runAllTimers()
expect(wrapper.vm.error).toEqual(false) expect(wrapper.vm.error).toEqual(false)
}) })
it('shows an error toaster when the apollo mutation rejects', async () => {
// calls vddrop twice because of how mockResolvedValueOnce works in jest
// the first time the mock function is called it will resolve, calling it a
// second time will cause it to fail(with this implementation)
// https://jestjs.io/docs/en/mock-function-api.html#mockfnmockresolvedvalueoncevalue
await wrapper.vm.vddrop([{ filename: 'avatar.jpg' }])
await wrapper.vm.vddrop([{ filename: 'avatar.jpg' }])
expect(mocks.$toast.error).toHaveBeenCalledTimes(1)
})
}) })
}) })

View File

@ -75,7 +75,6 @@
"vue-count-to": "~1.0.13", "vue-count-to": "~1.0.13",
"vue-izitoast": "1.1.2", "vue-izitoast": "1.1.2",
"vue-sweetalert-icons": "~3.2.0", "vue-sweetalert-icons": "~3.2.0",
"vue2-dropzone": "^3.5.9",
"vuex-i18n": "~1.11.0", "vuex-i18n": "~1.11.0",
"zxcvbn": "^4.4.2" "zxcvbn": "^4.4.2"
}, },

View File

@ -11318,13 +11318,6 @@ vue2-dropzone@3.5.8:
dependencies: dependencies:
dropzone "^5.5.1" dropzone "^5.5.1"
vue2-dropzone@^3.5.9:
version "3.5.9"
resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.5.9.tgz#a63999a45a7aad24d4c21e3d35be409b4e6bdce8"
integrity sha512-nJz6teulVKlZIAeKgvPU7wBI/gzfIgqDOrEp1okSkQIkdprDVCoM0U7XWM0NOM4AAVX+4XGRtMoocYWdTYb3bQ==
dependencies:
dropzone "^5.5.1"
vue@^2.6.10, vue@^2.6.6: vue@^2.6.10, vue@^2.6.6:
version "2.6.10" version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"