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

No edit summary
No edit summary
Line 1: Line 1:
( function () {
/**
* Rotatable Model Viewer — with 2D / 3D toggle
*/
(function () {
     'use strict';
     'use strict';


    /* ---- bootstrap one viewer element ---- */
     function boot(el) {
     function boot( el ) {
         if (el.dataset.rmReady) return;
 
         if ( el.dataset.rmReady ) return;   // already initialised
         el.dataset.rmReady = '1';
         el.dataset.rmReady = '1';


         /* --- config --- */
         /* ---- config from data-attributes ---- */
         var imageUrl    = el.dataset.image,
         var imageUrl    = el.dataset.image       || '',
            image2dUrl  = el.dataset.image2d      || '',
             fw          = +el.dataset.frameWidth  || 300,
             fw          = +el.dataset.frameWidth  || 300,
             fh          = +el.dataset.frameHeight || 400,
             fh          = +el.dataset.frameHeight || 400,
Line 16: Line 18:
             sensitivity = +el.dataset.sensitivity || 5;
             sensitivity = +el.dataset.sensitivity || 5;


         if ( !imageUrl ) return;
         var hint  = el.querySelector('.rotatable-model-hint'),
            load  = el.querySelector('.rotatable-model-loading'),
            wrap  = el.closest('.rotatable-model-wrapper');


         var cur  = startFrame,
         if (!imageUrl) {
             hint = el.querySelector( '.rotatable-model-hint' ),
             if (load) load.textContent = 'Error: no image URL';
            load = el.querySelector( '.rotatable-model-loading' );
            return;
        }


         /* --- build the <img> strip --- */
         /* ============================================================
         var img   = document.createElement( 'img' );
          STATE
          ============================================================ */
        var cur    = startFrame,
            is2D    = false,          // current mode
            dragOK  = true;            // allow drag interaction
 
        /* ============================================================
          3D SPRITE STRIP
          ============================================================ */
         var img       = new Image();
         img.className = 'rm-strip';
         img.className = 'rm-strip';
         img.alt      = '';
         img.alt      = '';
         img.draggable = false;
         img.draggable = false;
         img.style.height = fh + 'px';
         img.style.height = fh + 'px';
         img.style.width  = ( fw * count ) + 'px';
         img.style.width  = (fw * count) + 'px';


         img.onload = function () {
         img.addEventListener('load', function () {
             if ( load ) load.style.display = 'none';
            var detected = Math.round(img.naturalWidth / fw);
         };
            if (detected > 0 && detected !== count) count = detected;
         img.onerror = function () {
             if (load) load.style.display = 'none';
             if ( load ) load.textContent = 'Image failed to load';
            show(startFrame);
         };
         });
         img.addEventListener('error', function () {
             if (load) { load.textContent = 'Failed to load image'; load.style.color = '#d33'; }
         });


         img.src = imageUrl;
         img.src = imageUrl;
         el.insertBefore( img, el.firstChild );
         el.insertBefore(img, el.firstChild);
 
        if (img.complete && img.naturalWidth > 0) {
            if (load) load.style.display = 'none';
        }
 
        /* ============================================================
          2D STATIC IMAGE  (created only if URL provided)
          ============================================================ */
        var staticImg = null;
        if (image2dUrl) {
            staticImg = new Image();
            staticImg.className = 'rm-static';
            staticImg.alt      = '';
            staticImg.draggable = false;
            staticImg.src      = image2dUrl;
            el.insertBefore(staticImg, el.firstChild);
        }


         /* --- helpers --- */
         /* ============================================================
         function show( idx ) {
          FRAME DISPLAY
             cur = ( ( idx % count ) + count ) % count;
          ============================================================ */
             img.style.left = -( cur * fw ) + 'px';
         function show(idx) {
             cur = ((idx % count) + count) % count;
             img.style.left = -(cur * fw) + 'px';
         }
         }
        function nudge(d) { if (!is2D) { show(cur - d); hideHint(); } }
        function reset()  { show(startFrame); }
        function hideHint() { if (hint) hint.classList.add('rm-hidden'); }
        /* ============================================================
          2D ↔ 3D MODE SWITCHING
          ============================================================ */
        var btn2d = el.querySelector('.rm-mode-2d'),
            btn3d = el.querySelector('.rm-mode-3d');


         function nudge(d) { show(cur - d); hideHint(); }
         function setMode(mode) {
            is2D = (mode === '2d');


        function reset() { show( startFrame ); }
            if (is2D) {
                el.classList.add('rm-is-2d');
                if (wrap) wrap.classList.add('rm-is-2d');
                dragOK = false;
            } else {
                el.classList.remove('rm-is-2d');
                if (wrap) wrap.classList.remove('rm-is-2d');
                dragOK = true;
            }


        function hideHint() {
            // Toggle active button
            if ( hint ) hint.classList.add( 'rm-hidden' );
            if (btn2d && btn3d) {
                btn2d.classList.toggle('rm-active', is2D);
                btn3d.classList.toggle('rm-active', !is2D);
            }
         }
         }


         /* --- drag (mouse + touch) --- */
        if (btn2d) {
         function startDrag( x0 ) {
            btn2d.addEventListener('click', function () { setMode('2d'); });
        }
        if (btn3d) {
            btn3d.addEventListener('click', function () { setMode('3d'); });
        }
 
         /* ============================================================
          DRAG  (mouse + touch)
          ============================================================ */
         function startDrag(x0) {
            if (!dragOK) return;
             var acc = 0;
             var acc = 0;
             hideHint();
             hideHint();


             function move( cx ) {
             function move(cx) {
                 acc += cx - x0;
                 acc += cx - x0;
                 x0   = cx;
                 x0 = cx;
                 var fd = ( acc / sensitivity ) | 0;       // truncate → 0
                 var fd = (acc / sensitivity) | 0;
                 if ( fd ) {
                 if (fd) { acc -= fd * sensitivity; show(cur + fd); }
                    acc -= fd * sensitivity;
                    show( cur + fd );
                }
             }
             }


             function onMM( e ) { move( e.clientX ); e.preventDefault(); }
             function onMM(e) { move(e.clientX); e.preventDefault(); }
             function onTM( e ) {
             function onTM(e) {
                 if ( e.touches.length === 1 ) {
                 if (e.touches.length === 1) { move(e.touches[0].clientX); e.preventDefault(); }
                    move( e.touches[0].clientX );
                    e.preventDefault();
                }
             }
             }
             function stop() {
             function stop() {
                 el.classList.remove( 'rm-dragging' );
                 el.classList.remove('rm-dragging');
                 document.removeEventListener( 'mousemove', onMM );
                 document.removeEventListener('mousemove',   onMM);
                 document.removeEventListener( 'mouseup',   stop );
                 document.removeEventListener('mouseup',     stop);
                 document.removeEventListener( 'touchmove', onTM );
                 document.removeEventListener('touchmove',   onTM);
                 document.removeEventListener( 'touchend', stop );
                 document.removeEventListener('touchend',   stop);
                 document.removeEventListener( 'touchcancel', stop );
                 document.removeEventListener('touchcancel', stop);
             }
             }


             el.classList.add( 'rm-dragging' );
             el.classList.add('rm-dragging');
             document.addEventListener( 'mousemove', onMM );
             document.addEventListener('mousemove',   onMM);
             document.addEventListener( 'mouseup',   stop );
             document.addEventListener('mouseup',     stop);
             document.addEventListener( 'touchmove', onTM, { passive: false } );
             document.addEventListener('touchmove',   onTM, { passive: false });
             document.addEventListener( 'touchend', stop );
             document.addEventListener('touchend',   stop);
             document.addEventListener( 'touchcancel', stop );
             document.addEventListener('touchcancel', stop);
         }
         }


         el.addEventListener( 'mousedown', function ( e ) {
         el.addEventListener('mousedown', function (e) {
             if ( e.button === 0 ) { startDrag( e.clientX ); e.preventDefault(); }
            // Don't drag if clicking a mode button
            if (e.target.closest('.rm-mode-toggle')) return;
             if (e.button === 0 && dragOK) { startDrag(e.clientX); e.preventDefault(); }
         });
         });
         el.addEventListener( 'touchstart', function ( e ) {
         el.addEventListener('touchstart', function (e) {
             if ( e.touches.length === 1 ) { startDrag( e.touches[0].clientX ); e.preventDefault(); }
            if (e.target.closest('.rm-mode-toggle')) return;
         }, { passive: false } );
             if (e.touches.length === 1 && dragOK) {
                startDrag(e.touches[0].clientX); e.preventDefault();
            }
         }, { passive: false });


         /* --- mouse-wheel --- */
         /* ---- wheel ---- */
         el.addEventListener( 'wheel', function ( e ) {
         el.addEventListener('wheel', function (e) {
            if (!dragOK) return;
             var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
             var d = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
             if ( d ) { nudge( d ); e.preventDefault(); }
             if (d) { nudge(d); e.preventDefault(); }
         }, { passive: false } );
         }, { passive: false });


         /* --- keyboard (element must be focusable) --- */
         /* ---- keyboard ---- */
         el.setAttribute( 'tabindex', '0' );
         el.setAttribute('tabindex', '0');
         el.addEventListener( 'keydown', function ( e ) {
         el.addEventListener('keydown', function (e) {
             switch ( e.key ) {
             if (is2D) return;
                case 'ArrowLeft': nudge( -1 ); e.preventDefault(); break;
            if (e.key === 'ArrowLeft') { nudge(1); e.preventDefault(); }
                case 'ArrowRight': nudge( 1 ); e.preventDefault(); break;
            if (e.key === 'ArrowRight') { nudge(1); e.preventDefault(); }
                case 'Home':       reset();     e.preventDefault(); break;
            if (e.key === 'Home')       { reset();   e.preventDefault(); }
            }
         });
         });


         /* --- nav buttons (outside .rotatable-model, inside wrapper) --- */
         /* ---- nav buttons ---- */
        var wrap = el.closest( '.rotatable-model-wrapper' );
         if (wrap) {
         if ( wrap ) {
             var bL = wrap.querySelector('.rm-btn-left'),
             var bL = wrap.querySelector( '.rm-btn-left' ),
                 bR = wrap.querySelector('.rm-btn-right'),
                 bR = wrap.querySelector( '.rm-btn-right' ),
                 bO = wrap.querySelector('.rm-btn-reset');
                 bO = wrap.querySelector( '.rm-btn-reset' );
             if (bL) bL.addEventListener('click', function () { nudge(-1); });
 
             if (bR) bR.addEventListener('click', function () { nudge( 1); });
             if ( bL ) bL.addEventListener( 'click', function () { nudge( 1 ); } );
             if (bO) bO.addEventListener('click', reset);
             if ( bR ) bR.addEventListener( 'click', function () { nudge( -1 ); } );
             if ( bO ) bO.addEventListener( 'click', reset );
         }
         }


         /* --- initial frame --- */
         /* ---- initial frame ---- */
         show( startFrame );
         show(startFrame);
     }
     }


     /* ---- scan & initialise ---- */
     /* ---- scan & initialise ---- */
     function initAll( root ) {
     function initAll(root) {
         var els = ( root || document ).querySelectorAll(
         var els = (root || document).querySelectorAll('.rotatable-model');
                '.rotatable-model:not([data-rm-ready])' );
         for (var i = 0; i < els.length; i++) boot(els[i]);
         for ( var i = 0; i < els.length; i++ ) boot( els[i] );
     }
     }


     if ( document.readyState === 'loading' ) {
     if (document.readyState === 'loading') {
         document.addEventListener( 'DOMContentLoaded', function () { initAll(); } );
         document.addEventListener('DOMContentLoaded', function () { initAll(); });
     } else {
     } else {
         initAll();
         initAll();
     }
     }


    // re-scan when MediaWiki injects content (VE save, live-preview, etc.)
     if (typeof mw !== 'undefined' && mw.hook) {
     if ( typeof mw !== 'undefined' && mw.hook ) {
         mw.hook('wikipage.content').add(function ($c) { initAll($c[0]); });
         mw.hook( 'wikipage.content' ).add( function ( $c ) { initAll( $c[0] ); } );
     }
     }
}() );
}());