[Slider] Update to include mobile optimization

This commit is contained in:
2025-09-24 13:39:38 +02:00
parent d0ae1a6eb4
commit ba4203925f
5 changed files with 2661 additions and 28 deletions

View File

@@ -0,0 +1,704 @@
import vue from 'eslint-plugin-vue';
import eslint from '@eslint/js';
import globals from 'globals';
import typescript from '@typescript-eslint/eslint-plugin';
import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint';
const style = {
'plugins': {
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
},
'files': [
'**/*.ts',
'**/*.js',
'**/*.mjs',
'**/*.cjs',
'**/*.tsx',
'**/*.jsx'
],
'rules': {
// Formatting
'@stylistic/array-bracket-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/array-bracket-spacing': [
'error',
'always'
],
'@stylistic/array-element-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/arrow-parens': [
'error',
'as-needed'
],
'@stylistic/arrow-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/block-spacing': [
'error',
'always'
],
'@stylistic/brace-style': [
'error',
'1tbs'
],
'@stylistic/comma-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/comma-style': [
'error',
'last'
],
'@stylistic/dot-location': [
'error',
'property'
],
'@stylistic/eol-last': [
'error',
'always'
],
'@stylistic/function-call-spacing': [
'error',
'never'
],
'@stylistic/function-paren-newline': [
'error',
{
'minItems': 3
}
],
'@stylistic/function-call-argument-newline': [
'error',
'consistent'
],
'@stylistic/implicit-arrow-linebreak': [
'error',
'beside'
],
'@stylistic/indent': [
'error',
4
],
'@stylistic/key-spacing': [
'error',
{
'beforeColon': false,
'afterColon': true
}
],
'@stylistic/keyword-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/lines-between-class-members': [
'error',
'always'
],
'@stylistic/max-len': [
'warn',
{
'code': 120,
'comments': 140,
'ignoreComments': false,
'ignoreUrls': true,
'ignoreStrings': false
}
],
'@stylistic/new-parens': [
'error',
'always'
],
'@stylistic/newline-per-chained-call': [ 'error' ],
'@stylistic/no-extra-parens': [
'error',
'all',
{
'nestedBinaryExpressions': false,
'ternaryOperandBinaryExpressions': false,
'ignoreJSX': 'multi-line',
'nestedConditionalExpressions': false
}
],
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-floating-decimal': 'error',
'@stylistic/no-mixed-operators': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-multi-spaces': 'error',
'@stylistic/no-multiple-empty-lines': [
'error',
{
'max': 3,
'maxEOF': 2
}
],
'@stylistic/no-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/no-whitespace-before-property': 'error',
'@stylistic/object-curly-newline': [
'error',
{
'multiline': true,
'minProperties': 1
}
],
'@stylistic/object-curly-spacing': [
'error',
'always'
],
'@stylistic/object-property-newline': 'error',
'@stylistic/operator-linebreak': [
'error',
'before'
],
'@stylistic/one-var-declaration-per-line': 'error',
'@stylistic/padded-blocks': [
'error',
{
'blocks': 'never',
'classes': 'always',
'switches': 'never',
}
],
// Padding lines. The most in-depth part of this config
'@stylistic/padding-line-between-statements': [
'error',
// Variables, Constants
{
'blankLine': 'never',
'prev': 'var',
'next': 'var'
},
{
'blankLine': 'never',
'prev': 'let',
'next': 'let'
},
{
'blankLine': 'never',
'prev': 'const',
'next': 'const'
},
{
'blankLine': 'always',
'prev': 'var',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'with'
]
},
{
'blankLine': 'always',
'prev': 'let',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': 'const',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// Import
{
'blankLine': 'never',
'prev': 'import',
'next': 'import'
},
{
'blankLine': 'never',
'prev': 'cjs-import',
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': 'cjs-import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'import'
},
{
'blankLine': 'always',
'prev': 'import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// If
{
'blankLine': 'always',
'prev': '*',
'next': 'if'
},
{
'blankLine': 'always',
'prev': 'if',
'next': '*'
},
// For
{
'blankLine': 'always',
'prev': '*',
'next': 'for'
},
{
'blankLine': 'always',
'prev': 'for',
'next': '*'
},
// While
{
'blankLine': 'always',
'prev': '*',
'next': 'while'
},
{
'blankLine': 'always',
'prev': 'while',
'next': '*'
},
// Functions
{
'blankLine': 'always',
'prev': '*',
'next': 'function'
},
{
'blankLine': 'always',
'prev': 'function',
'next': '*'
},
// Block Statements
{
'blankLine': 'always',
'prev': '*',
'next': 'block-like'
},
{
'blankLine': 'always',
'prev': 'block-like',
'next': '*'
},
// Switch
{
'blankLine': 'always',
'prev': '*',
'next': 'switch'
},
{
'blankLine': 'always',
'prev': 'switch',
'next': '*'
},
// Try-Catch
{
'blankLine': 'always',
'prev': '*',
'next': 'try'
},
{
'blankLine': 'always',
'prev': 'try',
'next': '*'
},
// Throw
{
'blankLine': 'always',
'prev': '*',
'next': 'throw'
},
{
'blankLine': 'always',
'prev': 'throw',
'next': '*'
},
// Return
{
'blankLine': 'never',
'prev': 'return',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'return'
},
// Export
{
'blankLine': 'always',
'prev': '*',
'next': 'export'
},
{
'blankLine': 'always',
'prev': 'export',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'cjs-export'
},
{
'blankLine': 'always',
'prev': 'cjs-export',
'next': '*'
},
// Classes
{
'blankLine': 'always',
'prev': '*',
'next': 'class'
},
{
'blankLine': 'always',
'prev': 'class',
'next': '*'
},
],
'@stylistic/quote-props': [
'error',
'always'
],
'@stylistic/quotes': [
'error',
'single'
],
'@stylistic/rest-spread-spacing': [
'error',
'never'
],
'@stylistic/semi': [
'error',
'always'
],
'@stylistic/semi-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/semi-style': [
'error',
'last'
],
'@stylistic/space-before-blocks': [
'error',
'always'
],
'@stylistic/space-before-function-paren': [
'error',
'always'
],
'@stylistic/space-in-parens': [
'error',
'always'
],
'@stylistic/space-infix-ops': [
'error',
{
'int32Hint': false
}
],
'@stylistic/space-unary-ops': 'error',
'@stylistic/spaced-comment': [
'error',
'always'
],
'@stylistic/template-curly-spacing': [
'error',
'always'
],
'@stylistic/switch-colon-spacing': 'error',
'@stylistic/wrap-iife': [
'error',
'inside'
],
'@stylistic/wrap-regex': 'error',
'@stylistic/ts/type-annotation-spacing': 'error',
}
};
/** @type {import('eslint').Linter.Config} */
export default tseslint.config(
// Base JavaScript rules
eslint.configs.recommended,
tseslint.configs.recommended,
style,
// Vue support (including TS and JSX inside SFCs)
{
'files': [ '**/*.vue' ],
'languageOptions': {
'sourceType': 'module',
'ecmaVersion': 'latest',
'globals': globals.browser,
'parserOptions': {
'parser': tseslint.parser,
},
},
'plugins': {
'vue': vue,
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
'@typescript-eslint': typescript,
},
'extends': [
eslint.configs.recommended,
...vue.configs['flat/recommended']
],
'rules': {
...typescript.configs.recommended.rules,
...style.rules,
// Vue specific rules
'@stylistic/indent': 'off',
'vue/html-indent': [
'error',
4
],
'vue/html-comment-indent': [
'error',
4
],
'vue/script-indent': [
'error',
4,
{
'baseIndent': 1,
'switchCase': 1
}
],
'vue/html-self-closing': [
'error',
{
'html': {
'void': 'never',
'normal': 'never',
'component': 'always'
},
'svg': 'always',
'math': 'never'
}
],
'vue/max-attributes-per-line': [
'error',
{
'singleline': 3,
'multiline': 1,
}
],
},
},
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-var */
const fetchedElements = document.getElementsByClassName('slider-element'); const fetchedElements = document.getElementsByClassName('slider-element');
const sliderElements = []; const sliderElements = [];
let okToMove = true; let okToMove = true;
@@ -35,7 +36,8 @@ function sliderGoToIndex(index) {
// Determine move direction: // Determine move direction:
// true = next, false = previous // true = next, false = previous
let moveDirection = true; let moveDirection = true;
if ((index < currentSlideIndex || (index === sliderElements.length - 1 && currentSlideIndex === 0)) && !(index === 0 && currentSlideIndex === sliderElements.length - 1)) { if ((index < currentSlideIndex || (index === sliderElements.length - 1 && currentSlideIndex === 0))
&& !(index === 0 && currentSlideIndex === sliderElements.length - 1)) {
moveDirection = false; moveDirection = false;
} }
/* /*
@@ -85,7 +87,8 @@ function sliderGoToIndex(index) {
} }
} }
} }
function sliderControl(action) { // eslint-disable-next-line @typescript-eslint/no-unused-vars
var sliderControl = (action) => {
if (action === 'next') { if (action === 'next') {
sliderGoToIndex(currentSlideIndex + 1); sliderGoToIndex(currentSlideIndex + 1);
} }
@@ -93,10 +96,16 @@ function sliderControl(action) {
sliderGoToIndex(currentSlideIndex - 1); sliderGoToIndex(currentSlideIndex - 1);
} }
sliderAutoAdvance(); sliderAutoAdvance();
} };
let sliderAutoAdvanceInterval = 0; let sliderAutoAdvanceInterval = 0;
let sliderInterval = 0; let sliderInterval = 0;
function activateSlider(interval) { /**
* Set up the slider and give it an interval for auto advancing
* @param interval - The interval at which to auto advance
* @param name - The name of the platform (like desktop, used to load different images)
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
var activateSlider = (interval, name) => {
sliderAutoAdvanceInterval = interval; sliderAutoAdvanceInterval = interval;
sliderContainer.addEventListener('mouseenter', () => { sliderContainer.addEventListener('mouseenter', () => {
stopSliderAutoAdvance(); stopSliderAutoAdvance();
@@ -111,7 +120,8 @@ function activateSlider(interval) {
sliderAutoAdvance(); sliderAutoAdvance();
}); });
sliderAutoAdvance(); sliderAutoAdvance();
} loadImageType(name);
};
const sliderAutoAdvance = () => { const sliderAutoAdvance = () => {
if (sliderAutoAdvanceInterval > 0) { if (sliderAutoAdvanceInterval > 0) {
stopSliderAutoAdvance(); stopSliderAutoAdvance();
@@ -124,10 +134,38 @@ const stopSliderAutoAdvance = () => {
try { try {
clearInterval(sliderInterval); clearInterval(sliderInterval);
} }
catch (e) { } catch (e) {
; console.debug(e);
}
}; };
for (let el in fetchedElements) { const allowedExtensions = [
'webp',
'jpg',
'jpeg',
'svg',
'png'
];
/**
* Load type of image, can be used to load images for different platforms (i.e. mobile optimization)
* @param name - The name appended to the image filename
*/
var loadImageType = (name) => {
sliderElements.forEach(el => {
const baseURL = el.dataset.imageBaseURL;
const filetype = el.dataset.filetype;
// TODO: Verification (i.e. baseURL cannot contain .something in the end, filetype may only be jpg, jpeg, webp, svg or png)
if (allowedExtensions.indexOf(filetype) === -1) {
console.warn('[ SLIDER ] Invalid filetype ' + filetype + ' for image element with id ' + el.id);
return;
}
if (baseURL.lastIndexOf('.') > baseURL.lastIndexOf('/')) {
console.warn('[ SLIDER ] ImageBaseURL incorrect for image element with id ' + el.id);
return;
}
el.style.setProperty('background-image', baseURL + name + filetype);
});
};
for (const el in fetchedElements) {
if (fetchedElements[el].className) { if (fetchedElements[el].className) {
sliderElements.push(fetchedElements[el]); sliderElements.push(fetchedElements[el]);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
{ {
"dependencies": { "dependencies": {
"typescript": "^5.5.4" "typescript": "^5.5.4"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@stylistic/eslint-plugin": "^5.4.0",
"eslint-plugin-vue": "^10.5.0",
"typescript-eslint": "^8.44.1"
} }
} }

View File

@@ -1,8 +1,11 @@
/* eslint-disable no-var */
const fetchedElements = document.getElementsByClassName( 'slider-element' ); const fetchedElements = document.getElementsByClassName( 'slider-element' );
const sliderElements: HTMLDivElement[] = []; const sliderElements: HTMLDivElement[] = [];
let okToMove = true; let okToMove = true;
let currentSlideIndex = 0; let currentSlideIndex = 0;
const sliderContainer: HTMLDivElement = ( document.getElementsByClassName( 'slider-container' )[ 0 ] as HTMLDivElement );
const sliderContainer: HTMLDivElement = document.getElementsByClassName( 'slider-container' )[ 0 ] as HTMLDivElement;
function sliderGoToIndex ( index: number ) { function sliderGoToIndex ( index: number ) {
if ( okToMove ) { if ( okToMove ) {
@@ -12,6 +15,7 @@ function sliderGoToIndex ( index: number ) {
let previousElement = 0; let previousElement = 0;
let nextElement = 0; let nextElement = 0;
let beforePreviousElement = 0; let beforePreviousElement = 0;
if ( index < sliderElements.length - 1 ) { if ( index < sliderElements.length - 1 ) {
nextElement = index + 1; nextElement = index + 1;
} else { } else {
@@ -35,7 +39,11 @@ function sliderGoToIndex ( index: number ) {
// Determine move direction: // Determine move direction:
// true = next, false = previous // true = next, false = previous
let moveDirection = true; let moveDirection = true;
if ( ( index < currentSlideIndex || ( index === sliderElements.length - 1 && currentSlideIndex === 0 ) ) && !( index === 0 && currentSlideIndex === sliderElements.length - 1 ) ) {
if (
( index < currentSlideIndex || ( index === sliderElements.length - 1 && currentSlideIndex === 0 ) )
&& !( index === 0 && currentSlideIndex === sliderElements.length - 1 )
) {
moveDirection = false; moveDirection = false;
} }
@@ -55,6 +63,7 @@ function sliderGoToIndex ( index: number ) {
} else { } else {
sliderElements[ nextElement ].classList.add( 'next' ); sliderElements[ nextElement ].classList.add( 'next' );
} }
sliderElements[ nextElement ].classList.remove( 'current' ); sliderElements[ nextElement ].classList.remove( 'current' );
sliderElements[ nextElement ].classList.remove( 'past' ); sliderElements[ nextElement ].classList.remove( 'past' );
sliderElements[ nextElement ].classList.remove( 'last' ); sliderElements[ nextElement ].classList.remove( 'last' );
@@ -75,6 +84,7 @@ function sliderGoToIndex ( index: number ) {
sliderElements[ nextElement ].classList.add( 'next' ); sliderElements[ nextElement ].classList.add( 'next' );
sliderElements[ nextElement ].classList.remove( 'future' ); sliderElements[ nextElement ].classList.remove( 'future' );
} }
currentSlideIndex = index; currentSlideIndex = index;
setTimeout( () => { setTimeout( () => {
okToMove = true; okToMove = true;
@@ -90,7 +100,9 @@ function sliderGoToIndex ( index: number ) {
type SliderAction = 'next' | 'previous'; type SliderAction = 'next' | 'previous';
function sliderControl ( action: SliderAction ) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
var sliderControl = ( action: SliderAction ) => {
if ( action === 'next' ) { if ( action === 'next' ) {
sliderGoToIndex( currentSlideIndex + 1 ); sliderGoToIndex( currentSlideIndex + 1 );
} else if ( action === 'previous' ) { } else if ( action === 'previous' ) {
@@ -98,14 +110,22 @@ function sliderControl ( action: SliderAction ) {
} }
sliderAutoAdvance(); sliderAutoAdvance();
} };
let sliderAutoAdvanceInterval = 0; let sliderAutoAdvanceInterval = 0;
let sliderInterval = 0; let sliderInterval = 0;
function activateSlider ( interval: number ) {
/**
* Set up the slider and give it an interval for auto advancing
* @param interval - The interval at which to auto advance
* @param name - The name of the platform (like desktop, used to load different images)
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
var activateSlider = ( interval: number, name: string ) => {
sliderAutoAdvanceInterval = interval; sliderAutoAdvanceInterval = interval;
sliderContainer.addEventListener( 'mouseenter', () => { sliderContainer.addEventListener( 'mouseenter', () => {
stopSliderAutoAdvance() stopSliderAutoAdvance();
} ); } );
sliderContainer.addEventListener( 'mouseleave', () => { sliderContainer.addEventListener( 'mouseleave', () => {
@@ -113,7 +133,7 @@ function activateSlider ( interval: number ) {
} ); } );
document.addEventListener( 'blur', () => { document.addEventListener( 'blur', () => {
stopSliderAutoAdvance() stopSliderAutoAdvance();
} ); } );
window.addEventListener( 'focus', () => { window.addEventListener( 'focus', () => {
@@ -121,7 +141,9 @@ function activateSlider ( interval: number ) {
} ); } );
sliderAutoAdvance(); sliderAutoAdvance();
}
loadImageType( name );
};
const sliderAutoAdvance = () => { const sliderAutoAdvance = () => {
if ( sliderAutoAdvanceInterval > 0 ) { if ( sliderAutoAdvanceInterval > 0 ) {
@@ -130,17 +152,57 @@ const sliderAutoAdvance = () => {
sliderGoToIndex( currentSlideIndex + 1 ); sliderGoToIndex( currentSlideIndex + 1 );
}, sliderAutoAdvanceInterval ); }, sliderAutoAdvanceInterval );
} }
} };
const stopSliderAutoAdvance = () => { const stopSliderAutoAdvance = () => {
try { try {
clearInterval( sliderInterval ); clearInterval( sliderInterval );
} catch ( e ) {}; } catch ( e ) {
console.debug( e );
}
};
const allowedExtensions = [
'webp',
'jpg',
'jpeg',
'svg',
'png'
];
/**
* Load type of image, can be used to load images for different platforms (i.e. mobile optimization)
* @param name - The name appended to the image filename
*/
var loadImageType = ( name: string ) => {
sliderElements.forEach( el => {
const baseURL = el.dataset.imageBaseURL;
const filetype = el.dataset.filetype;
// TODO: Verification (i.e. baseURL cannot contain .something in the end, filetype may only be jpg, jpeg, webp, svg or png)
if ( allowedExtensions.indexOf( filetype ) === -1 ) {
console.warn( '[ SLIDER ] Invalid filetype ' + filetype + ' for image element with id ' + el.id );
return;
} }
for ( let el in fetchedElements ) { if ( baseURL.lastIndexOf( '.' ) > baseURL.lastIndexOf( '/' ) ) {
console.warn( '[ SLIDER ] ImageBaseURL incorrect for image element with id ' + el.id );
return;
}
el.style.setProperty( 'background-image', baseURL + name + filetype );
} );
};
for ( const el in fetchedElements ) {
if ( fetchedElements[ el ].className ) { if ( fetchedElements[ el ].className ) {
sliderElements.push( ( fetchedElements[ el ] as HTMLDivElement ) ); sliderElements.push( ( fetchedElements[ el ] as HTMLDivElement ) );
} }
} }
sliderGoToIndex( 0 ); sliderGoToIndex( 0 );