Skip to content

Commit

Permalink
Feature/enforce cropping (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielztolnai authored Mar 24, 2021
1 parent 6901f54 commit 31f826c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 22 deletions.
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
"slug": "^0.9.1",
"vue": "^2.5.17",
"vuedraggable": "^2.16.0",
"vuejs-clipper": "^0.2.11"
"vuejs-clipper": "^1.1.4"
}
}
5 changes: 5 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ Images::make('Gallery')->croppingConfigs(['ratio' => 4/3]);
```
Available cropping configuration, see https://github.com/timtnleeProject/vuejs-clipper#clipper-basic.

It is possible to enforce cropping on upload, for example to ensure the image has the set aspect ratio:
```php
Images::make('Gallery')->mustCrop();
```

## Custom properties

![Custom properties](https://raw.githubusercontent.com/ebess/advanced-nova-media-library/master/docs/custom-properties.gif)
Expand Down
39 changes: 34 additions & 5 deletions resources/js/components/Cropper.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<template>
<transition name="fade">
<modal v-if="image" @modal-close="$emit('close')" class="modal-cropper">
<card class="text-center clipping-container m-2 bg-white rounded-lg shadow-lg overflow-hidden">
<modal v-if="image" @modal-close="onCancel" class="modal-cropper">
<card class="text-center clipping-container max-w-view bg-white rounded-lg shadow-lg overflow-hidden">
<div class="p-4">
<clipper-basic class="clipper" ref="clipper" bg-color="rgba(0, 0, 0, 0)" :rotate.number="rotate" :src="imageUrl" v-bind="configs"/>
</div>

<div class="bg-30 px-6 py-3 footer rounded-lg">
<button type="button" class="btn btn-link text-80 font-normal h-9 px-3" @click="$emit('close')">{{__('Cancel')}}</button>
<button v-if="!cropAnyway" type="button" class="btn btn-link text-80 font-normal h-9 px-3" @click="onCancel">{{__('Cancel')}}</button>

<input class="input-range ml-4 mr-4" type="range" min="0" max="360" step="30" v-model="rotate">

<button type="button" class="btn btn-default btn-primary" @click="onSave">{{__('Update')}}</button>
<button type="button" class="btn btn-default btn-primary" @click="onSave" ref="updateButton">{{__('Update')}}</button>
</div>
</card>
</modal>
Expand All @@ -28,6 +28,10 @@
type: Object,
default: () => ({}),
},
mustCrop: {
type: Boolean,
default: false
}
},
data: () => ({
rotate: 0,
Expand All @@ -41,9 +45,17 @@
imageUrl() {
return this.image ? this.image.__media_urls__.__original__ : null;
},
cropAnyway() {
return (this.image.mustCrop === true) && this.mustCrop;
},
},
watch: {
image() {
image: function (newValue) {
if (newValue) {
this.$nextTick(() => {
this.$refs.updateButton.focus();
})
}
this.reset();
},
},
Expand All @@ -68,6 +80,13 @@
this.$emit('crop-completed', fileData);
this.$emit('close');
},
onCancel() {
if (this.cropAnyway) {
this.onSave();
} else {
this.$emit('close');
}
},
},
};
</script>
Expand All @@ -87,6 +106,16 @@
z-index: 400;
}
.max-w-view {
max-width: calc(100vw - 6.5rem);
}
@media (min-aspect-ratio: 4/3) {
.max-w-view {
max-width: 60vw;
}
}
.fade-enter-active, .fade-leave-active {
transition: opacity .3s;
}
Expand Down
58 changes: 51 additions & 7 deletions resources/js/components/Gallery.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="gallery" :class="{editable}" @mouseover="mouseOver = true" @mouseout="mouseOver = false">
<cropper v-if="field.type === 'media' && editable" :image="cropImage" @close="cropImage = null" @crop-completed="onCroppedImage" :configs="field.croppingConfigs"/>
<cropper v-if="field.type === 'media' && editable" :image="cropImage" :must-crop="field.mustCrop" @close="onCloseCroppedImage" @crop-completed="onCroppedImage" :configs="field.croppingConfigs"/>

<component :is="draggable ? 'draggable' : 'div'" v-if="images.length > 0" v-model="images"
class="gallery-list clearfix">
Expand All @@ -9,7 +9,7 @@
:key="index" :image="image" :field="field" :editable="editable" :removable="removable || editable" @remove="remove(index)"
:is-custom-properties-editable="customProperties && customPropertiesFields.length > 0"
@edit-custom-properties="customPropertiesImageIndex = index"
@crop-start="cropImage = $event"
@crop-start="cropImageQueue.push($event)"
/>

<CustomProperties
Expand Down Expand Up @@ -65,13 +65,16 @@
data() {
return {
mouseOver: false,
cropImage: null,
cropImageQueue: [],
images: this.value,
customPropertiesImageIndex: null,
singleComponent: this.field.type === 'media' ? SingleMedia : SingleFile,
};
},
computed: {
cropImage() {
return this.cropImageQueue.length ? this.cropImageQueue[this.cropImageQueue.length - 1] : null
},
draggable() {
return this.editable && this.multiple;
},
Expand All @@ -86,13 +89,18 @@
}
return this.__(`Upload New ${type}`);
},
mustCrop() {
return ('mustCrop' in this.field && this.field.mustCrop);
}
},
watch: {
images() {
images(value, old) {
this.queueNewImages(value, old)
this.$emit('input', this.images);
},
value(value) {
value(value, old) {
this.queueNewImages(value, old)
this.images = value;
},
},
Expand Down Expand Up @@ -129,17 +137,22 @@
},
name: file.name,
file_name: file.name,
...(this.mustCrop && {mustCrop: true}),
};
if (!this.validateFile(fileData.file)) {
return;
}
// Copy to trigger watcher to recognize differnece between new and old values
// https://github.com/vuejs/vue/issues/2164
let copiedArray = this.images.slice(0)
if (this.multiple) {
this.images.push(fileData);
copiedArray.push(fileData);
} else {
this.images = [fileData];
copiedArray = [fileData];
}
this.images = copiedArray
};
},
retrieveImageFromClipboardAsBlob(pasteEvent, callback) {
Expand Down Expand Up @@ -193,6 +206,37 @@
));
return false;
},
onCloseCroppedImage() {
this.cropImageQueue.pop()
},
/**
* Compares new and old images and will queue anything that needs cropping (if mustCrop)
*
* @param value
* @param old
*/
queueNewImages(value, old) {
let aThis = this
if (this.mustCrop) {
// For each of the new values (one)
// If it's not in the old value (two)
// And it's not already queued (three)
let toCrop = value.filter(function (one) {
return !(old.filter(function (two) {
return one === two
}).length) && !(aThis.cropImageQueue.filter(function (three) {
return one === three
}).length)
})
// Added them to the queue
for (let i in toCrop) {
this.cropImageQueue.push(toCrop[i])
}
}
}
},
mounted: function () {
this.$nextTick(() => {
Expand Down
9 changes: 7 additions & 2 deletions resources/js/components/fields/FormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,16 @@
},
addExistingItem(item) {
// Copy to trigger watcher to recognize differnece between new and old values
// https://github.com/vuejs/vue/issues/2164
let copiedArray = this.value.slice(0)
if (!this.field.multiple) {
this.value.splice(0, 1);
copiedArray.splice(0, 1);
}
this.value.push(item);
copiedArray.push(item);
this.value = copiedArray
}
},
};
Expand Down
5 changes: 5 additions & 0 deletions src/Fields/Images.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ public function showDimensions(bool $showDimensions = true): self
{
return $this->showStatistics();
}

public function mustCrop(bool $mustCrop = true): self
{
return $this->withMeta(['mustCrop' => $mustCrop]);
}
}
18 changes: 12 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2002,6 +2002,11 @@ core-js-compat@^3.6.2:
browserslist "^4.8.5"
semver "7.0.0"

core-js@^3.6.4:
version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==

core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
Expand Down Expand Up @@ -5678,7 +5683,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"

rxjs@^6.3.2:
rxjs@^6.5.2:
version "6.6.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
Expand Down Expand Up @@ -6634,13 +6639,14 @@ vuedraggable@^2.16.0:
dependencies:
sortablejs "^1.10.1"

vuejs-clipper@^0.2.11:
version "0.2.13"
resolved "https://registry.yarnpkg.com/vuejs-clipper/-/vuejs-clipper-0.2.13.tgz#52bf507bdf2ab04766f69fd8a09501dc28261c69"
integrity sha512-6Rp1+dP9XQTnFHmg8NhrGSNteSHKZ8YDnQiBFhJPoVNDSQIPjnjWNt37bqP2EuAElMoPHB+kLE+DQ7h+jjG/uQ==
vuejs-clipper@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/vuejs-clipper/-/vuejs-clipper-1.1.8.tgz#54bc55c96d95c06c4411a6e0a7059d75cf0d69ed"
integrity sha512-+UxwmO7xWfDH9s1tjh1u5dpvWHs3OSUgijH8nhrid4LpdcEt173NveTsPxEkkrK7bMVbKmL28sK8uX0OVGLcQg==
dependencies:
core-js "^3.6.4"
exif-js "^2.3.0"
rxjs "^6.3.2"
rxjs "^6.5.2"
vue-rx "^6.0.1"

watchpack-chokidar2@^2.0.0:
Expand Down

0 comments on commit 31f826c

Please sign in to comment.