User:Monster Domosed/common.js: Difference between revisions

No edit summary
No edit summary
 
(30 intermediate revisions by the same user not shown)
Line 2: Line 2:
     'use strict';
     'use strict';


     const LQ_ICON = '/images/d/d8/LQ_icon.png';
     const urlCache = new Map();
    const HQ_ICON = '/images/1/10/HQ_icon.png';


     function getSize(wrapper) {
     function getSize(element) {
         const match = wrapper.className.match(/model_size_(\d+)/);
         const match = element.className.match(/model_size_(\d+)/);
         return match ? parseInt(match[1], 10) : 500;
         return match ? parseInt(match[1], 10) : 500;
     }
     }


     function updateViewerSize(viewer, wrapper) {
     function getFileUrl(fileName) {
         const size = getSize(wrapper);
         if (urlCache.has(fileName)) {
 
            return urlCache.get(fileName);
        viewer.style.width = size + 'px';
         }
        viewer.style.height = size + 'px';
    }
 
    function updateQualityButtonSize(button, wrapper) {
        const size = getSize(wrapper);
 
         const buttonSize = Math.max(28, Math.min(56, Math.round(size * 0.14)));


        button.style.width = buttonSize + 'px';
        button.style.height = buttonSize + 'px';
    }
    function getFileUrl(fileName) {
         const apiUrl =
         const apiUrl =
             mw.config.get('wgScriptPath') +
             mw.config.get('wgScriptPath') +
Line 35: Line 22:
             '&format=json';
             '&format=json';


         return fetch(apiUrl)
         const promise = fetch(apiUrl)
             .then(function (response) {
             .then(function (response) {
                if (!response.ok) {
                    throw new Error('API request failed');
                }
                 return response.json();
                 return response.json();
             })
             })
             .then(function (data) {
             .then(function (data) {
                 const page = Object.values(data.query.pages)[0];
                 const page = Object.values(data.query.pages)[0];
                return page && page.imageinfo ? page.imageinfo[0].url : null;
            })
            .catch(function () {
                return null;
            });


                if (!page.imageinfo) {
        urlCache.set(fileName, promise);
                    return null;
        return promise;
                 }
    }
    function buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay) {
        const bar = document.createElement('div');
        bar.className = 'model-viewer-controls';
 
        const playButton = document.createElement('button');
        playButton.className = 'model-anim-button';
        playButton.type = 'button';
 
        const select = document.createElement('select');
        select.className = 'model-anim-select';
 
        const slider = document.createElement('input');
        slider.className = 'model-anim-slider';
        slider.type = 'range';
        slider.min = '0';
        slider.max = '1000';
        slider.value = '0';
        slider.step = '1';
 
        let paused = !autoplay;
        let rafId = null;
        let duration = 0;
 
        function updateSlider() {
            if (duration > 0) {
                 slider.value = String(Math.round((viewer.currentTime / duration) * 1000));
            }
            rafId = requestAnimationFrame(updateSlider);
        }


                return page.imageinfo[0].url;
        function startLoop() {
             });
            cancelAnimationFrame(rafId);
    }
             rafId = requestAnimationFrame(updateSlider);
        }


    function addQualityButton(wrapper, viewer, lqUrl, hqUrl) {
        function setPlaying(playing) {
        if (!hqUrl || lqUrl === hqUrl) {
            paused = !playing;
             return;
            playButton.textContent = playing ? '❚❚' : '▶';
            if (playing) {
                viewer.play();
             } else {
                viewer.pause();
            }
         }
         }


         const button = document.createElement('button');
         playButton.addEventListener('click', function () {
         button.className = 'mv-quality-toggle';
            setPlaying(paused);
         button.type = 'button';
        });
 
         slider.addEventListener('input', function () {
            if (duration > 0) {
                viewer.currentTime = (parseFloat(slider.value) / 1000) * duration;
            }
        });
 
         viewer.addEventListener('load', function () {
            const animations = viewer.availableAnimations || [];


        updateQualityButtonSize(button, wrapper);
            if (animations.length) {
                animations.forEach(function (animName) {
                    const option = document.createElement('option');
                    option.value = animName;
                    option.textContent = animName;
                    select.appendChild(option);
                });


        const icon = document.createElement('img');
                if (defaultAnimation && animations.indexOf(defaultAnimation) !== -1) {
        icon.src = LQ_ICON;
                    select.value = defaultAnimation;
        icon.alt = '';
                }


        button.appendChild(icon);
                viewer.animationName = select.value;
                duration = viewer.duration || 0;


        button.addEventListener('click', function () {
                select.addEventListener('change', function () {
            const isLQ = viewer.src === lqUrl;
                    viewer.animationName = select.value;
                    duration = viewer.duration || 0;
                    viewer.currentTime = 0;
                    if (paused) {
                        setPlaying(true);
                    }
                });
 
                if (paused) {
                    setPlaying(false);
                }


            viewer.src = isLQ ? hqUrl : lqUrl;
                startLoop();
             icon.src = isLQ ? HQ_ICON : LQ_ICON;
                bar.classList.add('model-viewer-controls--visible');
             }
         });
         });


         wrapper.appendChild(button);
         bar.appendChild(playButton);
        bar.appendChild(select);
        bar.appendChild(slider);
        container.appendChild(bar);
     }
     }


     function loadModel(wrapper) {
 
         if (wrapper.dataset.loaded) {
 
     function loadModel(preview) {
         if (preview.dataset.loaded) {
             return;
             return;
         }
         }


         wrapper.dataset.loaded = '1';
         preview.dataset.loaded = '1';


         Promise.all([
         const fileName = preview.dataset.model;
            getFileUrl(wrapper.dataset.lq),
        const displayName = preview.dataset.name || fileName;
            getFileUrl(wrapper.dataset.hq)
        const defaultAnimation = preview.dataset.animation || '';
         ]).then(function (urls) {
         const autoplay = preview.dataset.autoplay === 'true';
        const size = getSize(preview);


             const lqUrl = urls[0] || urls[1];
        getFileUrl(fileName + '.glb')
            const hqUrl = urls[1];
             .then(function (url) {
                if (!url) {
                    return;
                }


            if (!lqUrl) {
                const wrap = document.createElement('div');
                 return;
                 wrap.className = 'model-viewer-wrap';
            }


            wrapper.innerHTML = '';
                const container = document.createElement('div');
                container.className = 'model-viewer-container';
                container.style.width = size + 'px';
                container.style.height = size + 'px';


            const viewer = document.createElement('model-viewer');
                const viewer = document.createElement('model-viewer');
                viewer.src = url;
                viewer.setAttribute('camera-controls', '');
                viewer.setAttribute('touch-action', 'pan-y');
                if (preview.dataset.autoplay === 'true') {
                    viewer.setAttribute('autoplay', '');
                }


            viewer.src = lqUrl;
                container.appendChild(viewer);
                wrap.appendChild(container);


            viewer.setAttribute('camera-controls', '');
                buildAnimationControls(wrap, container, viewer, defaultAnimation, autoplay);
            viewer.setAttribute('touch-action', 'pan-y');
            viewer.setAttribute('loading', 'lazy');


            updateViewerSize(viewer, wrapper);
                const name = document.createElement('div');
                name.className = 'model-name';
                name.textContent = displayName;


            wrapper.appendChild(viewer);
                wrap.appendChild(name);


            viewer.addEventListener('load', function () {
                preview.replaceWith(wrap);
                addQualityButton(wrapper, viewer, lqUrl, hqUrl);
             });
             });
        });
     }
     }


     function initPlaceholder(wrapper) {
     function initPreview(preview) {
         if (wrapper.dataset.initialized) {
         if (preview.dataset.initialized) {
             return;
             return;
         }
         }


         wrapper.dataset.initialized = '1';
         preview.dataset.initialized = '1';
 
        wrapper.style.position = 'relative';


         const button = document.createElement('button');
         const button = document.createElement('button');
 
        button.className = 'model-load-button';
         button.type = 'button';
         button.type = 'button';
        button.className = 'model-load-button';
button.className = 'model-load-button';
button.type = 'button';
         button.addEventListener('click', function () {
         button.addEventListener('click', function () {
             loadModel(wrapper);
             loadModel(preview);
         });
         });


         wrapper.appendChild(button);
         preview.appendChild(button);
     }
     }


Line 144: Line 209:
         document
         document
             .querySelectorAll('.model-placeholder')
             .querySelectorAll('.model-placeholder')
             .forEach(initPlaceholder);
             .forEach(initPreview);
    }
 
    let scanTimer = null;
 
    function debouncedScan() {
        clearTimeout(scanTimer);
        scanTimer = setTimeout(scan, 100);
     }
     }


     scan();
     scan();


     new MutationObserver(scan).observe(document.body, {
     const observer = new MutationObserver(debouncedScan);
    observer.observe(document.body, {
         childList: true,
         childList: true,
         subtree: true
         subtree: true
    });
    window.addEventListener('resize', function () {
        document.querySelectorAll('model-viewer').forEach(function (viewer) {
            const wrapper = viewer.closest('.model-placeholder');
            if (!wrapper) {
                return;
            }
            updateViewerSize(viewer, wrapper);
            const button = wrapper.querySelector('.mv-quality-toggle');
            if (button) {
                updateQualityButtonSize(button, wrapper);
            }
        });
     });
     });


})();
})();