From ff8cd43165d7cd4f9895394b28890e79eba86c7f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 4 Feb 2021 16:10:44 +0100 Subject: [PATCH 1/8] image cropping is optional setup --- .../ImageUploader/ImageUploader.vue | 22 ++++++++++++++----- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/webapp/components/ImageUploader/ImageUploader.vue b/webapp/components/ImageUploader/ImageUploader.vue index d4f402e02..afcf1e507 100644 --- a/webapp/components/ImageUploader/ImageUploader.vue +++ b/webapp/components/ImageUploader/ImageUploader.vue @@ -6,7 +6,7 @@ :options="dropzoneOptions" :use-custom-slot="true" @vdropzone-error="onDropzoneError" - @vdropzone-file-added="initCropper" + @vdropzone-file-added="fileAdded" > @@ -24,6 +24,11 @@ {{ $t('contribution.teaserImage.supportedFormats') }} +
+ + {{ $t('contribution.teaserImage.cropImage') }} + +
@@ -70,6 +75,7 @@ export default { cropper: null, file: null, showCropper: false, + imageCanBeCropped: false, isLoadingImage: false, } }, @@ -81,10 +87,17 @@ export default { onUnSupportedFormat(status, message) { this.$toast.error(status, message) }, - initCropper(file) { + fileAdded(file) { + this.$emit('addImageAspectRatio', file.width / file.height || 1.0) + this.$emit('addHeroImage', file) + this.file = file + if (this.file.type === 'image/jpeg') this.imageCanBeCropped = true + this.$nextTick((this.isLoadingImage = false)) + }, + initCropper() { const supportedFormats = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'] - if (supportedFormats.indexOf(file.type) < 0) { + if (supportedFormats.indexOf(this.file.type) < 0) { this.onUnSupportedFormat( 'error', this.$t('contribution.teaserImage.errors.unSupported-file-format'), @@ -92,10 +105,9 @@ export default { return } this.showCropper = true - this.file = file const imageElement = document.querySelector('#cropping-image') - imageElement.src = URL.createObjectURL(file) + imageElement.src = URL.createObjectURL(this.file) this.cropper = new Cropper(imageElement, { zoomable: false, autoCropArea: 0.9 }) }, cropImage() { diff --git a/webapp/locales/de.json b/webapp/locales/de.json index a603e58fb..706b53177 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -213,6 +213,7 @@ "newPost": "Erstelle einen neuen Beitrag", "success": "Gespeichert!", "teaserImage": { + "cropImage": "Bild zuschneiden", "cropperConfirm": "Bestätigen", "errors": { "unSupported-file-format": "Bitte lade ein Bild in den folgenden Formaten hoch: JPG, JPEG, PNG or GIF!" diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 28ee013af..402cea016 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -213,6 +213,7 @@ "newPost": "Create a new Post", "success": "Saved!", "teaserImage": { + "cropImage": "Crop image", "cropperConfirm": "Confirm", "errors": { "unSupported-file-format": "Please upload an image of file format: JPG, JPEG, PNG or GIF!" From 4a16af10bafd55059d4738c49fc66dd8c9dc6085 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 4 Feb 2021 21:01:52 +0100 Subject: [PATCH 2/8] image property type added --- backend/src/db/factories.js | 3 ++- backend/src/models/Image.js | 1 + backend/src/schema/resolvers/images.js | 2 +- backend/src/schema/resolvers/images/images.js | 4 ++-- backend/src/schema/types/type/Image.gql | 2 ++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 7ea82f661..ca637a01c 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -49,8 +49,9 @@ Factory.define('badge') Factory.define('image') .attr('url', faker.image.unsplash.imageUrl) - .attr('aspectRatio', 1) + .attr('aspectRatio', 1.0) .attr('alt', faker.lorem.sentence) + .attr('type', 'image/jpeg') .after((buildObject, options) => { const { url: imageUrl } = buildObject if (imageUrl) buildObject.url = uniqueImageUrl(imageUrl) diff --git a/backend/src/models/Image.js b/backend/src/models/Image.js index 19824b493..b46342c18 100644 --- a/backend/src/models/Image.js +++ b/backend/src/models/Image.js @@ -3,5 +3,6 @@ export default { alt: { type: 'string' }, sensitive: { type: 'boolean', default: false }, aspectRatio: { type: 'float', default: 1.0 }, + type: { type: 'string' }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, } diff --git a/backend/src/schema/resolvers/images.js b/backend/src/schema/resolvers/images.js index 8b3f4a3e8..111f84888 100644 --- a/backend/src/schema/resolvers/images.js +++ b/backend/src/schema/resolvers/images.js @@ -2,7 +2,7 @@ import Resolver from './helpers/Resolver' export default { Image: { ...Resolver('Image', { - undefinedToNull: ['sensitive', 'alt', 'aspectRatio'], + undefinedToNull: ['sensitive', 'alt', 'aspectRatio', 'type'], }), }, } diff --git a/backend/src/schema/resolvers/images/images.js b/backend/src/schema/resolvers/images/images.js index 9b57579c4..656ae114a 100644 --- a/backend/src/schema/resolvers/images/images.js +++ b/backend/src/schema/resolvers/images/images.js @@ -53,8 +53,8 @@ export async function mergeImage(resource, relationshipType, imageInput, opts = if (!(existingImage || upload)) throw new UserInputError('Cannot find image for given resource') if (existingImage && upload) deleteImageFile(existingImage, deleteCallback) const url = await uploadImageFile(upload, uploadCallback) - const { alt, sensitive, aspectRatio } = imageInput - const image = { alt, sensitive, aspectRatio, url } + const { alt, sensitive, aspectRatio, type } = imageInput + const image = { alt, sensitive, aspectRatio, url, type } txResult = await transaction.run( ` MATCH (resource {id: $resource.id}) diff --git a/backend/src/schema/types/type/Image.gql b/backend/src/schema/types/type/Image.gql index 41cc11eef..f171a4b77 100644 --- a/backend/src/schema/types/type/Image.gql +++ b/backend/src/schema/types/type/Image.gql @@ -8,6 +8,7 @@ type Image { alt: String, sensitive: Boolean, aspectRatio: Float, + type: String, } input ImageInput { @@ -15,4 +16,5 @@ input ImageInput { upload: Upload, sensitive: Boolean, aspectRatio: Float, + type: String, } From 664d3d2c42d3e4963454620fe2db41a3c3f3a28c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 4 Feb 2021 21:05:04 +0100 Subject: [PATCH 3/8] image type in frontend --- .../components/ContributionForm/ContributionForm.vue | 12 +++++++++++- webapp/components/ImageUploader/ImageUploader.vue | 7 +++++-- webapp/graphql/Fragments.js | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue index 42ed2799e..d2cb419a4 100644 --- a/webapp/components/ContributionForm/ContributionForm.vue +++ b/webapp/components/ContributionForm/ContributionForm.vue @@ -19,6 +19,7 @@ :class="[formData.imageBlurred && '--blur-image']" @addHeroImage="addHeroImage" @addImageAspectRatio="addImageAspectRatio" + @addImageType="addImageType" />
@@ -84,7 +85,11 @@ export default { }, data() { const { title, content, image } = this.contribution - const { sensitive: imageBlurred = false, aspectRatio: imageAspectRatio = null } = image || {} + const { + sensitive: imageBlurred = false, + aspectRatio: imageAspectRatio = null, + type: imageType = null, + } = image || {} return { links, @@ -93,6 +98,7 @@ export default { content: content || '', image: image || null, imageAspectRatio, + imageType, imageBlurred, }, formSchema: { @@ -125,6 +131,7 @@ export default { if (this.imageUpload) { image.upload = this.imageUpload image.aspectRatio = this.formData.imageAspectRatio + image.type = this.formData.imageType } } this.loading = true @@ -173,6 +180,9 @@ export default { addImageAspectRatio(aspectRatio) { this.formData.imageAspectRatio = aspectRatio }, + addImageType(imageType) { + this.formData.imageType = imageType + }, }, apollo: { User: { diff --git a/webapp/components/ImageUploader/ImageUploader.vue b/webapp/components/ImageUploader/ImageUploader.vue index afcf1e507..c1492e5ba 100644 --- a/webapp/components/ImageUploader/ImageUploader.vue +++ b/webapp/components/ImageUploader/ImageUploader.vue @@ -90,6 +90,7 @@ export default { fileAdded(file) { this.$emit('addImageAspectRatio', file.width / file.height || 1.0) this.$emit('addHeroImage', file) + this.$emit('addImageType', file.type) this.file = file if (this.file.type === 'image/jpeg') this.imageCanBeCropped = true this.$nextTick((this.isLoadingImage = false)) @@ -113,9 +114,10 @@ export default { cropImage() { this.isLoadingImage = true - const onCropComplete = (aspectRatio, imageFile) => { + const onCropComplete = (aspectRatio, imageFile, imageType) => { this.$emit('addImageAspectRatio', aspectRatio) this.$emit('addHeroImage', imageFile) + this.$emit('addImageType', imageType) this.$nextTick((this.isLoadingImage = false)) this.closeCropper() } @@ -125,7 +127,7 @@ export default { canvas.toBlob((blob) => { const imageAspectRatio = canvas.width / canvas.height const croppedImageFile = new File([blob], this.file.name, { type: this.file.type }) - onCropComplete(imageAspectRatio, croppedImageFile) + onCropComplete(imageAspectRatio, croppedImageFile, 'image/jpeg') }, 'image/jpeg') } else { // TODO: use cropped file instead of original file @@ -140,6 +142,7 @@ export default { deleteImage() { this.$emit('addHeroImage', null) this.$emit('addImageAspectRatio', null) + this.$emit('addImageType', null) }, }, } diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js index 2626581e1..e4c31bd4b 100644 --- a/webapp/graphql/Fragments.js +++ b/webapp/graphql/Fragments.js @@ -51,6 +51,7 @@ export const postFragment = gql` url sensitive aspectRatio + type } author { ...user From 80e296604deeb64ca9939b016b9f2b54f91a4614 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 5 Feb 2021 14:19:47 +0100 Subject: [PATCH 4/8] height of image in post/index.vue is calculated correctly. Why is this necessary? --- webapp/pages/post/_id/_slug/index.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index a2147db41..be3fef61f 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -176,13 +176,15 @@ export default { /* Return false when image property is not present or is not a number so no unnecessary css variables are set. */ - if (!this.post.image || typeof this.post.image.aspectRatio !== 'number') return false + if (!this.post.image || typeof this.post.image.aspectRatio !== 'number') return false /* Return the aspect ratio as a css variable. Later to be used when calculating the height with respect to the width. + Why is this correction necessary? */ return { - '--hero-image-aspect-ratio': 1 / this.post.image.aspectRatio, + '--hero-image-aspect-ratio': 1.0 / this.post.image.aspectRatio, + '--hero-image-correction': 1.0 / this.post.image.aspectRatio * 48.0 + 'px', } }, }, @@ -258,8 +260,8 @@ export default { hero image aspect ratio) before the hero image loads so the autoscroll works correctly when following a comment link. */ - padding-top: calc(var(--hero-image-aspect-ratio) * 100%); + padding-top: calc(var(--hero-image-aspect-ratio) * 100% + var(--hero-image-correction)); /* Letting the image fill the container, since the container is the one determining height */ From 585d32ea706e92cf5271c8f64d34b59849fb8112 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 5 Feb 2021 14:21:13 +0100 Subject: [PATCH 5/8] aspectRation is seeded correctly for random images --- backend/src/db/factories.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index ca637a01c..8a6ca380e 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -49,7 +49,7 @@ Factory.define('badge') Factory.define('image') .attr('url', faker.image.unsplash.imageUrl) - .attr('aspectRatio', 1.0) + .attr('aspectRatio', 1.3333333333333333) .attr('alt', faker.lorem.sentence) .attr('type', 'image/jpeg') .after((buildObject, options) => { From c304b74f17cd325c08f4d92450ceb75d14943c86 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 5 Feb 2021 14:27:13 +0100 Subject: [PATCH 6/8] Image width/height is catched correctly without cropping --- .../components/ImageUploader/ImageUploader.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/webapp/components/ImageUploader/ImageUploader.vue b/webapp/components/ImageUploader/ImageUploader.vue index c1492e5ba..bce3a9837 100644 --- a/webapp/components/ImageUploader/ImageUploader.vue +++ b/webapp/components/ImageUploader/ImageUploader.vue @@ -9,7 +9,7 @@ @vdropzone-file-added="fileAdded" > - + { + const img = new Image() + img.onload = () => resolve(img) + img.onerror = reject + img.src = src + }) + }, + async fileAdded(file) { + const imageURL = URL.createObjectURL(file) + const image = await this.addImageProcess(imageURL) + this.$emit('addImageAspectRatio', image.width / image.height || 1.0) this.$emit('addHeroImage', file) this.$emit('addImageType', file.type) this.file = file From 650e83f4c250389477933a2e7d21d8245b0ce882 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 8 Feb 2021 16:10:38 +0100 Subject: [PATCH 7/8] file upload: refactored --- .../ImageUploader/ImageUploader.vue | 78 +++++++++++-------- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + webapp/pages/post/_id/_slug/index.vue | 4 +- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/webapp/components/ImageUploader/ImageUploader.vue b/webapp/components/ImageUploader/ImageUploader.vue index bce3a9837..d885782f3 100644 --- a/webapp/components/ImageUploader/ImageUploader.vue +++ b/webapp/components/ImageUploader/ImageUploader.vue @@ -1,17 +1,21 @@