diff --git a/.idea/studio.iml b/.idea/studio.iml index 9cca4fcfac..1cdcb17b68 100644 --- a/.idea/studio.iml +++ b/.idea/studio.iml @@ -3,6 +3,7 @@ + diff --git a/contentcuration/contentcuration/static/img/kolibri_placeholder.png b/contentcuration/contentcuration/static/img/kolibri_placeholder.png index 5b7dd837bf..c12008541e 100644 Binary files a/contentcuration/contentcuration/static/img/kolibri_placeholder.png and b/contentcuration/contentcuration/static/img/kolibri_placeholder.png differ diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelEditor.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelEditor.vue index 7e49a1b2c0..2be971280e 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelEditor.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelEditor.vue @@ -271,14 +271,12 @@ padding: 0 20px 40px; .channel-thumbnail { - width: @channel-thumbnail-size; + width: @channel-thumbnail-width; + height: @channel-thumbnail-height; margin-top: 35px; /deep/ .image_dropzone { - width: @channel-thumbnail-size; - img { - width: @channel-thumbnail-size; - height: @channel-thumbnail-size; - } + width: @channel-thumbnail-width; + height: @channel-thumbnail-height; } } diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue index 4dda35dba5..3b01a266d4 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue @@ -10,7 +10,9 @@ @click="openChannel" >
- +
+ +
diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelMetadataSection.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelMetadataSection.vue index 2e49201eb4..d157923a7d 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelMetadataSection.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelMetadataSection.vue @@ -104,11 +104,11 @@ grid-template-columns: 1fr 3fr; padding: 0 20px 40px; img { - width: 130px; - height: 130px; + width: 160px; + height: 90px; margin-top: 35px; border: 2px solid @gray-500; - object-fit: cover; + object-fit: contain; } .channel-section { padding-left: 20px; diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_set/views/ChannelItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_set/views/ChannelItem.vue index 9e867119d2..851eab3033 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_set/views/ChannelItem.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_set/views/ChannelItem.vue @@ -117,9 +117,9 @@ .section { padding: 0; img { - width: 100px; - height: 100px; - object-fit: cover; + max-width: 100%; + max-height: 100%; + object-fit: contain; } } .title { diff --git a/contentcuration/contentcuration/static/js/edit_channel/details/hbtemplates/details_view.handlebars b/contentcuration/contentcuration/static/js/edit_channel/details/hbtemplates/details_view.handlebars index 9b9999f6cf..82b287421b 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/details/hbtemplates/details_view.handlebars +++ b/contentcuration/contentcuration/static/js/edit_channel/details/hbtemplates/details_view.handlebars @@ -261,8 +261,8 @@ {{#each details.original_channels}}
  • -
    -
    {{name}}
    +
    +
    {{name}}
  • {{/each}} diff --git a/contentcuration/contentcuration/static/js/edit_channel/image/views.js b/contentcuration/contentcuration/static/js/edit_channel/image/views.js index 8d8443c355..71d761c57a 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/image/views.js +++ b/contentcuration/contentcuration/static/js/edit_channel/image/views.js @@ -11,11 +11,6 @@ require('croppie/croppie.css'); var dialog = require('edit_channel/utils/dialog'); const Constants = require('edit_channel/constants/index'); -const CHANNEL_ASPECT_RATIO = { width: 130, height: 130 }; -const CHANNEL_CROP_BOUNDARY = { - width: CHANNEL_ASPECT_RATIO.width + 20, - height: CHANNEL_ASPECT_RATIO.height + 20, -}; const THUMBNAIL_ASPECT_RATIO = { width: 160, height: 90 }; const THUMBNAIL_CROP_BOUNDARY = { width: THUMBNAIL_ASPECT_RATIO.width + 10, @@ -81,8 +76,8 @@ export const ThumbnailUploadView = BaseViews.BaseView.extend({ this.upload_url = options.upload_url; this.default_url = options.default_url; this.allow_edit = options.allow_edit; - this.aspect_ratio = options.is_channel ? CHANNEL_ASPECT_RATIO : THUMBNAIL_ASPECT_RATIO; - this.boundary = options.is_channel ? CHANNEL_CROP_BOUNDARY : THUMBNAIL_CROP_BOUNDARY; + this.aspect_ratio = THUMBNAIL_ASPECT_RATIO; + this.boundary = THUMBNAIL_CROP_BOUNDARY; this.cropping = false; this.render(); this.dropzone = null; @@ -194,6 +189,7 @@ export const ThumbnailUploadView = BaseViews.BaseView.extend({ this.croppie = new Croppie(this.$(selector).get(0), { boundary: this.boundary, viewport: this.aspect_ratio, + enforceBoundary: false, showZoomer: false, customClass: 'crop-img', }); diff --git a/contentcuration/contentcuration/static/less/channel_list.less b/contentcuration/contentcuration/static/less/channel_list.less index 21e4c430f4..549dcb9381 100644 --- a/contentcuration/contentcuration/static/less/channel_list.less +++ b/contentcuration/contentcuration/static/less/channel_list.less @@ -6,7 +6,8 @@ @channel-item-min-width: 400px; @channel-container-height: 250px; @channel-profile-width: 150px; -@channel-thumbnail-size: 150px; +@channel-thumbnail-width: 170px; +@channel-thumbnail-height: 100px; // nested flexbox settings to ensure that the channels list view is vertically responsive. body { @@ -115,10 +116,16 @@ body { .profile { margin: 5px 15px 5px 5px; text-align: left; - img { - width: @channel-thumbnail-size; - height: @channel-thumbnail-size; - object-fit: cover; + .channel-pic { + width: @channel-thumbnail-width; + height: @channel-thumbnail-height; + text-align: center; + background-color: #ffffff; + img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } } span { font-size: 65pt; diff --git a/contentcuration/contentcuration/static/less/channel_settings.less b/contentcuration/contentcuration/static/less/channel_settings.less index 5bb54b0161..615b057be3 100644 --- a/contentcuration/contentcuration/static/less/channel_settings.less +++ b/contentcuration/contentcuration/static/less/channel_settings.less @@ -21,23 +21,23 @@ } .channel_section { display: inline-block; - width: 70%; + width: 65%; margin-left: 5px; } .image_dropzone { width: 120px; } .new_channel_pic { - width: 150px; - height: 100%; + width: 170px; + height: 100px; margin: 5px 15px 5px 5px; .image_dropzone { width: auto; } img { - width: 150px; - height: 150px; - object-fit: cover; + max-width: 100%; + max-height: 100%; + object-fit: contain; } } hr { diff --git a/contentcuration/contentcuration/static/less/details.less b/contentcuration/contentcuration/static/less/details.less index 2d8841bcdf..4660c2e643 100644 --- a/contentcuration/contentcuration/static/less/details.less +++ b/contentcuration/contentcuration/static/less/details.less @@ -349,7 +349,6 @@ } } img { - width: 32px; height: 32px; border: 1px solid @blue-200; object-fit: cover; diff --git a/contentcuration/contentcuration/static/less/queue.less b/contentcuration/contentcuration/static/less/queue.less index ed66e9e848..2039e486f6 100644 --- a/contentcuration/contentcuration/static/less/queue.less +++ b/contentcuration/contentcuration/static/less/queue.less @@ -333,7 +333,7 @@ } .content-item-thumbnail { display: inline-block; - width: 30px; + height: 30px; margin-top: -3px; // HACK margin-right: 10px; margin-left: 6px; diff --git a/contentcuration/contentcuration/utils/files.py b/contentcuration/contentcuration/utils/files.py index c9f3d9c079..5c1967658f 100644 --- a/contentcuration/contentcuration/utils/files.py +++ b/contentcuration/contentcuration/utils/files.py @@ -6,7 +6,6 @@ import shutil import tempfile import zipfile -from fractions import Fraction from multiprocessing.dummy import Pool import requests @@ -32,7 +31,7 @@ from contentcuration.models import generate_object_storage_name ImageFile.LOAD_TRUNCATED_IMAGES = True -THUMBNAIL_DIMENSION = 400 +THUMBNAIL_WIDTH = 400 def create_file_from_contents(contents, ext=None, node=None, preset_id=None, uploaded_by=None): @@ -233,14 +232,15 @@ def generate_thumbnail_from_channel(item, dimension=200): return get_thumbnail_encoding(item.thumbnail, dimension=dimension) -def get_thumbnail_encoding(filename, dimension=THUMBNAIL_DIMENSION): +def get_thumbnail_encoding(filename, dimension=THUMBNAIL_WIDTH): """ Generates a base64 encoding for a thumbnail Args: filename (str): thumbnail to generate encoding from (must be in storage already) - dimension (int): how big resized image should be + dimension (int, optional): desired width of thumbnail. Defaults to 400. Returns base64 encoding of resized thumbnail """ + if filename.startswith("data:image"): return filename @@ -248,6 +248,8 @@ def get_thumbnail_encoding(filename, dimension=THUMBNAIL_DIMENSION): inbuffer = StringIO.StringIO() outbuffer = StringIO.StringIO() + # make sure the aspect ratio between width and height is 16:9 + thumbnail_size = [dimension, round(dimension / 1.77)] try: if not filename.startswith(settings.STATIC_ROOT): filename = generate_object_storage_name(checksum, filename) @@ -260,22 +262,13 @@ def get_thumbnail_encoding(filename, dimension=THUMBNAIL_DIMENSION): with Image.open(inbuffer) as image: image_format = image.format - width, height = image.size - dimension = min([dimension, width, height]) - size = [dimension, dimension] - ratio = Fraction(*size) - - # Crop image the aspect ratio is different - if width > ratio * height: - x, y = (width - ratio * height) // 2, 0 - else: - x, y = 0, (height - width / ratio) // 2 - - image = image.crop((x, y, width - x, height - y)) - if image.size > size: - image.thumbnail(size, Image.ANTIALIAS) - else: - image.thumbnail((dimension, dimension), Image.ANTIALIAS) + + # Note: Image.thumbnail ensures that the image will fit in the + # specified thumbnail size, but it retains the original image's + # aspect ratio. So a square image will remain square rather + # than being distorted to a 16:9 aspect ratio. This removes + # the need to make any changes like cropping the image. + image.thumbnail(thumbnail_size, Image.ANTIALIAS) image.save(outbuffer, image_format) return "data:image/{};base64,{}".format(ext[1:], base64.b64encode(outbuffer.getvalue()))