Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/ILIAS/UI/resources/js/Image/dist/image.min.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/
!function(e,n){"use strict";e.UI=e.UI||{},e.UI.image=e.UI.image||{},e.UI.image.getImageElement=e=>function(e,n){const t=e.getElementById(n);return null===t?null:t instanceof e.defaultView.HTMLImageElement?t:t.querySelector("img")}(n,e),e.UI.image.loadHighResolutionSource=function(e,n){const t=function(e,n){let t=null,l=null;return e.forEach(((e,i)=>{i<=n&&i>l&&(l=i,t=e)})),t}(n,e.width);if(null!==t){const n=e.cloneNode();n.addEventListener("load",(()=>{e.replaceWith(n)})),n.src=t}}}(il,document);
!function(e,n,t){"use strict";function i(e,n,t){if(!n.checkVisibility()){return void new e(((o,l)=>{i(e,n,t),l.unobserve(n)}),{root:null,rootMargin:"0px",threshold:.1}).observe(n)}const o=function(e,n){let t=null,i=null;return e.forEach(((e,o)=>{o<=n&&o>i&&(i=o,t=e)})),t}(t,n.width);if(null!==o){const e=n.cloneNode();e.addEventListener("load",(()=>{n.replaceWith(e)})),e.src=o}}e.UI=e.UI||{},e.UI.image=e.UI.image||{},e.UI.image.getImageElement=e=>function(e,n){const t=e.getElementById(n);return null===t?null:t instanceof e.defaultView.HTMLImageElement?t:t.querySelector("img")}(n,e),e.UI.image.loadHighResolutionSource=(e,n)=>i(t,e,n)}(il,document,IntersectionObserver);
6 changes: 4 additions & 2 deletions components/ILIAS/UI/resources/js/Image/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
*/

import terser from '@rollup/plugin-terser';
import copyright from '../../../../../../scripts/Copyright-Checker/copyright';
import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright';
import copyright from '../../../../../../scripts/Copyright-Checker/copyright.js';
import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright.js';

export default {
external: [
'il',
'document',
'IntersectionObserver',
],
input: './src/image.js',
output: {
Expand All @@ -30,6 +31,7 @@ export default {
globals: {
il: 'il',
document: 'document',
IntersectionObserver: 'IntersectionObserver',
},
plugins: [
terser({
Expand Down
7 changes: 6 additions & 1 deletion components/ILIAS/UI/resources/js/Image/src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ import il from 'il';
import document from 'document';
import getImageElement from './getImageElement';
import loadHighResolutionSource from './loadHighResolutionSource';
import IntersectionObserver from 'IntersectionObserver';

il.UI = il.UI || {};
il.UI.image = il.UI.image || {};

il.UI.image.getImageElement = (imageId) => getImageElement(document, imageId);
il.UI.image.loadHighResolutionSource = loadHighResolutionSource;
il.UI.image.loadHighResolutionSource = (imageElement, highResDefinitions) => loadHighResolutionSource(
IntersectionObserver,
imageElement,
highResDefinitions,
);
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,30 @@ function determineBestSource(highResDefinitions, imageWidth) {
/**
* Loads the best fitting high resolution source for the given image element.
*
* @param {IntersectionObserver} intersectionObserver
* @param {HTMLImageElement} imageElement
* @param {Map<number, string>} highResDefinitions min-width in px => source mapping
*/
export default function loadHighResolutionSource(imageElement, highResDefinitions) {
export default function loadHighResolutionSource(
intersectionObserver,
imageElement,
highResDefinitions,
) {
if (!imageElement.checkVisibility()) {
const visibilityObserver = new intersectionObserver(
(elements, observer) => {
loadHighResolutionSource(intersectionObserver, imageElement, highResDefinitions);
observer.unobserve(imageElement);
},
{
root: null,
rootMargin: '0px',
threshold: 0.1,
},
);
visibilityObserver.observe(imageElement);
return;
}
const optimalSource = determineBestSource(highResDefinitions, imageElement.width);
if (optimalSource !== null) {
const highResolutionImage = imageElement.cloneNode();
Expand Down
52 changes: 50 additions & 2 deletions components/ILIAS/UI/tests/Client/Image/loadHighResolutionSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @author Thibeau Fuhrer <thibeau@sr.solutions>
*/

import { describe, it } from 'node:test';
import { describe, it, mock } from 'node:test';
import { strict } from 'node:assert/strict';
import loadHighResolutionSource
from '../../../resources/js/Image/src/loadHighResolutionSource.js';
Expand Down Expand Up @@ -58,11 +58,14 @@ describe('loadHighResolutionSource', () => {
addEventListener(e, fn) {
this.loader = fn;
},
checkVisibility() {
return true;
},
};

for (let width = 0, maxWidth = 35; width <= maxWidth; width += 5) {
imageElement.width = width;
loadHighResolutionSource(imageElement, definitions);
loadHighResolutionSource(class {}, imageElement, definitions);

// we manually need to call the "load" event, since this will
// actually replace the element, which updates the src property
Expand All @@ -75,4 +78,49 @@ describe('loadHighResolutionSource', () => {
strict.equal(imageElement.src, expectedSources.get(width));
}
});
it('should initialize observer.', () => {
const initialSource = 'source_0';
const sourceAbove10 = 'source_10';
const sourceAbove20 = 'source_20';
const sourceAbove30 = 'source_30';

// note the definitions are NOT ordered by min-width
const definitions = new Map([
[10, sourceAbove10],
[30, sourceAbove30],
[20, sourceAbove20],
]);

const imageElement = {
width: 0,
loader: null,
src: initialSource,
cloneNode() {
return this;
},
replaceWith(otherImage) {
this.src = otherImage.src;
},
addEventListener(e, fn) {
this.loader = fn;
},
checkVisibility() {
return false;
},
};

const imageObserver = mock.fn(class {
initialized = false;

constructor() {
this.initialized = true;
}

observe() {}
});

loadHighResolutionSource(imageObserver, imageElement, definitions);

strict.equal(imageObserver.mock.calls.length, 1);
});
});
Loading