Refactor Wagtail Images to srcset

This commit is contained in:
Lorenz Padberg 2024-04-30 13:55:32 +02:00
parent d69b32cba1
commit 0452389951
10 changed files with 65 additions and 90 deletions

View File

@ -1,6 +1,7 @@
<template> <template>
<wagtail-image <wagtail-image
:src="value.src" :src="value.src"
:srcset="value.srcset"
alt="" alt=""
:original-height="value.height" :original-height="value.height"
:original-width="value.width" :original-width="value.width"
@ -17,7 +18,7 @@ export default {
components: { WagtailImage }, components: { WagtailImage },
methods: { methods: {
openFullscreen() { openFullscreen() {
this.$store.dispatch('showFullscreenImage', this.value.url); this.$store.dispatch('showFullscreenImage', this.value.src);
}, },
}, },
}; };

View File

@ -20,7 +20,7 @@ export default {
'-/resize/800/ 800w,' '-/resize/800/ 800w,'
); );
} }
return ''; return this.value.url;
}, },
}, },
methods: { methods: {

View File

@ -31,9 +31,12 @@
<wagtail-image <wagtail-image
class="module__hero-image" class="module__hero-image"
:src="module.heroImage.src" :src="module.heroImage.src"
:srcset="module.heroImage.srcset"
:original-width="module.heroImage.width" :original-width="module.heroImage.width"
:original-height="module.heroImage.height" :original-height="module.heroImage.height"
></wagtail-image> ></wagtail-image>
<h5 <h5
class="module__hero-source" class="module__hero-source"
v-if="module.heroSource" v-if="module.heroSource"

View File

@ -6,6 +6,7 @@
<wagtail-image <wagtail-image
class="module-teaser__image" class="module-teaser__image"
:src="heroImage.src" :src="heroImage.src"
:srcset="heroImage.srcset"
:original-height="heroImage.height" :original-height="heroImage.height"
:original-width="heroImage.width" :original-width="heroImage.width"
></wagtail-image> ></wagtail-image>

View File

@ -1,9 +1,11 @@
<template> <template>
<div class="wagtail-image"> <div class="wagtail-image">
<img <img
:src="modifiedUrl" :src="props.src"
:srcset="props.srcset"
:alt="alt" :alt="alt"
:class="['wagtail-image__image', { loaded: loaded }]" :class="['wagtail-image__image', { loaded: loaded }]"
:sizes="computedSizes"
loading="eager" loading="eager"
v-show="loaded" v-show="loaded"
ref="imgElement" ref="imgElement"
@ -16,15 +18,15 @@
></div> ></div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({ const props = defineProps({
src: String, src: String,
alt: String(''), alt: String(''),
originalWidth: Number, originalWidth: Number,
originalHeight: Number, originalHeight: Number,
srcset: String,
}); });
const imgElement = ref(null); const imgElement = ref(null);
@ -32,19 +34,6 @@ const width = ref(0);
const height = ref(0); const height = ref(0);
const loaded = ref(false); const loaded = ref(false);
const modifiedUrl = computed(() => {
const density = isHighDensity() ? 2 : 1;
if (width.value) {
return props.src.replace('original', 'width-' + width.value * density);
}
if (height.value) {
return props.src.replace('original', 'height-' + height.value * density);
}
return props.src;
});
const updateDimensions = () => { const updateDimensions = () => {
if (imgElement.value && imgElement.value.parentElement) { if (imgElement.value && imgElement.value.parentElement) {
const { clientWidth, clientHeight } = imgElement.value.parentElement; const { clientWidth, clientHeight } = imgElement.value.parentElement;
@ -57,6 +46,23 @@ const handleLoad = () => {
loaded.value = true; // Set loaded to true when the image loads loaded.value = true; // Set loaded to true when the image loads
}; };
const computedSizes = computed(() => {
// the default set of image sizes is [160px, 320px, 800px, 1600px]
let size;
if (width.value <= 300) {
size = '160px';
} else if (300 < width.value && width.value <= 600) {
size = '320px';
} else if (600 < width.value && width.value <= 1200) {
size = '800px';
} else {
size = '100vw';
}
console.log(size, width.value);
return size;
});
const placeholderStyle = computed(() => { const placeholderStyle = computed(() => {
const styles = { const styles = {
width: '100%', width: '100%',
@ -77,20 +83,15 @@ const placeholderStyle = computed(() => {
return styles; return styles;
}); });
const isHighDensity = () => { onMounted(() => {
return ( console.log('src set', props.srcset);
(window.matchMedia && updateDimensions();
(window.matchMedia( window.addEventListener('resize', updateDimensions);
'only screen and (min-resolution: 124dpi), only screen and (min-resolution: 1.3dppx), only screen and (min-resolution: 48.8dpcm)' });
).matches ||
window.matchMedia(
'only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 2.6/2), only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3)'
).matches)) ||
(window.devicePixelRatio && window.devicePixelRatio > 1.3)
);
};
onMounted(updateDimensions); onBeforeUnmount(() => {
window.removeEventListener('resize', updateDimensions);
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -5,4 +5,5 @@ fragment WagtailImageParts on WagtailImageNode {
width width
height height
title title
srcset
} }

View File

@ -6,7 +6,6 @@ from graphene.types import Scalar
from graphene_django.converter import convert_django_field from graphene_django.converter import convert_django_field
from graphql_relay import to_global_id from graphql_relay import to_global_id
from wagtail.fields import StreamField from wagtail.fields import StreamField
from wagtail.documents.models import Document
from wagtail.images.models import Image from wagtail.images.models import Image
from assignments.models import Assignment from assignments.models import Assignment
@ -14,6 +13,7 @@ from basicknowledge.models import BasicKnowledge
from books.models import CustomDocument from books.models import CustomDocument
from surveys.models import Survey from surveys.models import Survey
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,17 +43,19 @@ def get_document_json(document_id):
def get_wagtail_image_dict(image_id: int) -> dict | None: def get_wagtail_image_dict(image_id: int) -> dict | None:
"""""" from books.schema.nodes import get_srcset, get_src
try: try:
image = Image.objects.get(id=image_id) image = Image.objects.get(id=image_id)
value = { value = {
'value': image_id, 'value': image_id,
'id': image.id, 'id': image.id,
'src': generate_image_url(image, 'original'), 'src': get_src(image),
'alt': image.title, 'alt': image.title,
'title': image.title, 'title': image.title,
'width': image.width, 'width': image.width,
'height': image.height, 'height': image.height,
'srcset': get_srcset(image),
} }
return value return value
except Image.DoesNotExist: except Image.DoesNotExist:

View File

@ -7,8 +7,20 @@ from wagtail.images.models import Image
from wagtail.images.views.serve import generate_image_url from wagtail.images.views.serve import generate_image_url
class WagtailImageNode(DjangoObjectType): def get_srcset(image: Image) -> str:
return (
f"{generate_image_url(image, 'width-160')} 160w, "
f"{generate_image_url(image, 'width-320')} 320w, "
f"{generate_image_url(image, 'width-800')} 1280w, "
f"{generate_image_url(image, 'width-1600')} 2560w"
)
def get_src(image: Image) -> str:
return generate_image_url(image, f'width-{min(3840, image.width)}')
class WagtailImageNode(DjangoObjectType):
class Meta: class Meta:
model = Image model = Image
fields = [ fields = [
@ -20,10 +32,13 @@ class WagtailImageNode(DjangoObjectType):
src = graphene.String() src = graphene.String()
alt = graphene.String() alt = graphene.String()
srcset = graphene.String()
def resolve_src(self, info): def resolve_src(self, info):
return generate_image_url(self, 'original') return get_src(self)
def resolve_alt(self, info): def resolve_alt(self, info):
return "" return self.title
def resolve_srcset(self, info):
return get_srcset(self)

View File

@ -6,8 +6,7 @@ from django.views.generic import RedirectView
from wagtail.admin import urls as wagtailadmin_urls from wagtail.admin import urls as wagtailadmin_urls
from wagtail import urls as wagtail_urls from wagtail import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls from wagtail.documents import urls as wagtaildocs_urls
#from wagtail.images import urls as wagtailimages_urls from wagtail.images.views.serve import ServeView
from core.views import CustomImageServeView
from wagtailautocomplete.urls.admin import urlpatterns as autocomplete_admin_urls from wagtailautocomplete.urls.admin import urlpatterns as autocomplete_admin_urls
@ -19,14 +18,13 @@ urlpatterns = [
re_path(r"^guru/", admin.site.urls), re_path(r"^guru/", admin.site.urls),
re_path(r"^statistics/", include("statistics.urls", namespace="statistics")), re_path(r"^statistics/", include("statistics.urls", namespace="statistics")),
# wagtail # wagtail
re_path(r'^api/images/([^/]*)/(\d*)/([^/]*)/[^/]*$', CustomImageServeView.as_view(action='redirect'), name='wagtailimages_serve'), re_path(r'^api/images/([^/]*)/(\d*)/([^/]*)/[^/]*$', ServeView.as_view(action='redirect'), name='wagtailimages_serve'),
re_path(r"^cms/autocomplete/", include(autocomplete_admin_urls)), re_path(r"^cms/autocomplete/", include(autocomplete_admin_urls)),
re_path(r"^cms/pages/(\d+)/$", override_wagtailadmin_explore_default_ordering), re_path(r"^cms/pages/(\d+)/$", override_wagtailadmin_explore_default_ordering),
re_path(r"^cms/", include(wagtailadmin_urls)), re_path(r"^cms/", include(wagtailadmin_urls)),
re_path(r"^documents/", include(wagtaildocs_urls)), re_path(r"^documents/", include(wagtaildocs_urls)),
#re_path(r"^images/", include(wagtailimages_urls)),
# graphql backend # graphql backend
re_path(r"^api/", include("api.urls", namespace="api")), re_path(r"^api/", include("api.urls", namespace="api")),

View File

@ -1,29 +1,15 @@
import imghdr
from wsgiref.util import FileWrapper
import requests import requests
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.http import HttpResponse
from django.http import HttpResponse, StreamingHttpResponse
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import render from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from graphene_django.views import GraphQLView from graphene_django.views import GraphQLView
from graphql import get_operation_ast, parse from graphql import get_operation_ast, parse
from sentry_sdk.api import start_transaction from sentry_sdk.api import start_transaction
from wagtail.admin.views.pages import listing from wagtail.admin.views.pages import listing
from wagtail.images import get_image_model
from wagtail.images.exceptions import InvalidFilterSpecError
from wagtail.images.models import Image
from wagtail.images.models import SourceImageIOError
from wagtail.images.utils import verify_signature
from wagtail.images.views.serve import ServeView
from core.logger import get_logger from core.logger import get_logger
@ -85,36 +71,3 @@ def override_wagtailadmin_explore_default_ordering(request, parent_page_id):
return listing.IndexView.as_view()(request, parent_page_id) return listing.IndexView.as_view()(request, parent_page_id)
class CustomImageServeView(ServeView):
''' Taken from wagtail.images.views.serve.ServeView the only change is that we use original for the filter_spec_for_signature'''
model = get_image_model()
action = "serve"
key = None
@method_decorator(cache_control(max_age=3600, public=True))
def get(self, request, signature, image_id, filter_spec, filename=None):
# This variable is the only change to the wagtail implementation
filter_spec_for_signature = 'original'
if not verify_signature(
signature.encode(), image_id, filter_spec_for_signature, key=self.key
):
raise PermissionDenied
image = get_object_or_404(self.model, id=image_id)
# Get/generate the rendition
try:
rendition = image.get_rendition(filter_spec)
except SourceImageIOError:
return HttpResponse(
"Source image file not found", content_type="text/plain", status=410
)
except InvalidFilterSpecError:
return HttpResponse(
"Invalid filter spec: " + filter_spec,
content_type="text/plain",
status=400,
)
return getattr(self, self.action)(rendition)