Skip to content

Commit

Permalink
Merge pull request #886 from readthedocs/davidfischer/improve-image-u…
Browse files Browse the repository at this point in the history
…ploading

Improve image uploading
  • Loading branch information
davidfischer authored Jul 3, 2024
2 parents 1a4ff7d + 1c037bb commit d2f3d85
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 5 deletions.
14 changes: 13 additions & 1 deletion adserver/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,14 @@ def __init__(self, *args, **kwargs):
ad_type_max_lengths = [at.max_text_length for at in adtype_queryset] or [0]
maximum_text_length = min(ad_type_max_lengths)

# Get the width/height of any ad types (if applicable)
ad_type_image_width = (
[at.image_width for at in adtype_queryset if at.image_width] or [0]
)[0]
ad_type_image_height = (
[at.image_height for at in adtype_queryset if at.image_height] or [0]
)[0]

self.fields["image"].help_text = _(
"Sized according to the ad type. Need help with manipulating or resizing images? We can <a href='%s'>help</a>."
) % (reverse("support") + "?subject=Image+help")
Expand Down Expand Up @@ -1080,7 +1088,11 @@ def __init__(self, *args, **kwargs):
_("Advertisement display"),
"ad_types",
"link",
"image",
Field(
"image",
data_width=ad_type_image_width,
data_height=ad_type_image_height,
),
css_class="my-3",
),
Fieldset(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ <h5>{% trans 'Preview' %}</h5>

</div>

{% include "adserver/includes/widgets/advertisement-crop-resize-modal.html" %}

{% endblock content_container %}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ <h5>{% trans 'Preview' %}</h5>

</div>

{% include "adserver/includes/widgets/advertisement-crop-resize-modal.html" %}

{% endblock content_container %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% load i18n %}

<!-- Modal -->
<div class="modal fade" id="modal" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">{% trans 'Crop and resize image' %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{% trans 'Your image is not the right size for the ad format. You will need to crop and resize the image.' %}</p>
<p data-bind='text: imageErrors()' class='text-danger'></p>
<div class="mw-100">
<img class="mw-100" style="display: block" id="crop-resize-widget" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans 'Close' %}</button>
<button type="button" class="btn btn-primary" data-bind="click: cropAndResize">{% trans 'Crop and resize image' %}</button>
</div>
</div>
</div>
</div>
3 changes: 3 additions & 0 deletions assets/src/scss/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ $fa-font-path: "~font-awesome/fonts";
// Import ad styles from the ad client
@import "~ethical-ad-client/styles.scss";

// $cropper-image-path: '../images';
@import "~cropperjs/src/index.css";

@import "theme.scss";
@import "branding.scss";
@import "utils.scss";
105 changes: 104 additions & 1 deletion assets/src/views/advertisement-form.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import * as jquery from 'jquery';
import Cropper from 'cropperjs';
const ko = require('knockout');


function AdvertisementFormViewModel(method) {
const MAX_PREVIEW_LENGTH = 100;
const SENSIBLE_MAXIMUM_LENGTH = 1000;

const viewmodel = this;
const image = document.querySelector("#id_image");
const expected_width = parseInt(image.getAttribute("data-width"));
const expected_height = parseInt(image.getAttribute("data-height"));
const uploadInput = document.getElementById("id_image");
let cropper = null;

this.headline = ko.observable($("#id_headline").val());
this.content = ko.observable($("#id_content").val());
this.cta = ko.observable($("#id_cta").val());

this.image_width = ko.observable();
this.image_height = ko.observable();

this.getHeadlinePreview = function () {
return (this.headline() || "").slice(0, MAX_PREVIEW_LENGTH) + " ";
};
Expand All @@ -30,7 +41,6 @@ function AdvertisementFormViewModel(method) {
return length;
};


this.maxLength = function () {
// The actual max length passed from the backend form
let max_length = parseInt($("#id_maximum_text_length").attr("data-maximum-length"));
Expand All @@ -42,6 +52,99 @@ function AdvertisementFormViewModel(method) {

return max_length;
};

this.imageIsWrongSize = function () {
if (expected_width && expected_height && this.image_width() && this.image_height() &&
(expected_width != this.image_width() || expected_height != this.image_height())
) {
return true;
}

return false;
};

this.imageErrors = function () {
if (this.imageIsWrongSize()) {
return 'Expected an image sized ' + expected_width + '*' + expected_height + 'px ' +
'(it is ' + this.image_width() + '*' + this.image_height() + 'px).';
}

return '';
};

this.cropAndResize = function () {
if (cropper) {
cropper.getCroppedCanvas({width: expected_width, height: expected_height}).toBlob((blob) => {
// modified time is required for some reason
let file = new File([blob], "cropped.png", {type:"image/png", lastModified: new Date().getTime()});
let container = new DataTransfer();
container.items.add(file);
uploadInput.files = container.files;

// Trigger the change event
uploadInput.dispatchEvent(new Event('change'));
});
}
$('#modal').modal('hide');

// Update URL which sends a measurement event
const url = new URL(window.location);
url.searchParams.set("crop", "end");
window.history.pushState({}, null, url);
};

// Handle uploading images cleanly including cropping/resizing
// and showing the preview
uploadInput.addEventListener('change', e => {
if (e.target.files.length) {
for (const file of uploadInput.files) {
// Get the image width/height
let imageSrc = URL.createObjectURL(file);
let tempImage = new Image();
tempImage.onload = function(ev) {
viewmodel.image_width(ev.target.width);
viewmodel.image_height(ev.target.height);
if (viewmodel.imageIsWrongSize()) {
const image = document.getElementById('crop-resize-widget');
const aspectRatio = expected_width / expected_height;
const containerRatio = 466 / expected_width; // 466 is the modal width
image.src = imageSrc;

if (cropper) {
// Reset the cropper before creating a new one
cropper.destroy();
}

$('#modal').modal('show');

// https://github.com/fengyuanchen/cropperjs
cropper = new Cropper(image, {
// Height and Width are guaranteed to be >0 here
// since the image size was checked
aspectRatio: aspectRatio,
initialAspectRatio: aspectRatio,
viewMode: 2,
autoCropArea: 1,
rotatable: false,
minContainerWidth: expected_width * containerRatio,
minContainerHeight: expected_height * containerRatio,
});

// Update URL which sends a measurement event
const url = new URL(window.location);
url.searchParams.set("crop", "start");
window.history.pushState({}, null, url);
} else {
// Show the image in the ad preview(s)
document.querySelectorAll(".advertisement-preview img").forEach(element => {
element.src = imageSrc;
});
}
};
tempImage.src = imageSrc;
}
}
});
}

if ($('form#advertisement-update').length > 0) {
Expand Down
15 changes: 13 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"engines": {
"npm" : ">=8.0.0 <9.0.0",
"npm": ">=8.0.0 <9.0.0",
"node": ">=16.0.0 <17.0.0"
},
"scripts": {
Expand All @@ -24,6 +24,7 @@
"homepage": "https://github.com/readthedocs/ethical-ad-server#readme",
"dependencies": {
"bootstrap": "^4.6.2",
"cropperjs": "^1.6.2",
"font-awesome": "^4.7.0",
"jquery": "^3.5.1",
"knockout": "^3.5.1",
Expand Down

0 comments on commit d2f3d85

Please sign in to comment.