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

No edit summary
No edit summary
Line 1: Line 1:
/**
/**
  * Rotatable Model Viewer — with 2D / 3D toggle
  * Rotatable Model Viewer — with 2D / 3D toggle
* All <img> elements are created here in JS to bypass MediaWiki's HTML sanitizer.
  */
  */
(function () {
(function () {
     'use strict';
     'use strict';
    function makeImg(src, alt, className) {
        var i = new Image();
        i.src = src;
        i.alt = alt || '';
        i.draggable = false;
        if (className) i.className = className;
        return i;
    }


     function boot(el) {
     function boot(el) {
Line 9: Line 19:
         el.dataset.rmReady = '1';
         el.dataset.rmReady = '1';


         /* ---- config from data-attributes ---- */
         /* ---- config ---- */
         var imageUrl    = el.dataset.image        || '',
         var imageUrl    = el.dataset.image        || '',
             image2dUrl  = el.dataset.image2d      || '',
             image2dUrl  = el.dataset.image2d      || '',
            icon2dUrl  = el.dataset.icon_2d      || el.dataset.icon2d  || '',
            icon3dUrl  = el.dataset.icon_3d      || el.dataset.icon3d  || '',
            iconRotUrl  = el.dataset.icon_rotate  || el.dataset.iconRotate || '',
             fw          = +el.dataset.frameWidth  || 300,
             fw          = +el.dataset.frameWidth  || 300,
             fh          = +el.dataset.frameHeight || 400,
             fh          = +el.dataset.frameHeight || 400,
Line 18: Line 31:
             sensitivity = +el.dataset.sensitivity || 5;
             sensitivity = +el.dataset.sensitivity || 5;


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


         if (!imageUrl) {
         if (!imageUrl) {
Line 27: Line 40:
         }
         }


         /* ============================================================
         /* ---- state ---- */
          STATE
         var cur   = startFrame,
          ============================================================ */
             is2D   = false,
         var cur     = startFrame,
             dragOK = true;
             is2D   = false,           // current mode
             dragOK = true;           // allow drag interaction


         /* ============================================================
         /* ============================================================
           3D SPRITE STRIP
           3D SPRITE STRIP (<img> created in JS)
           ============================================================ */
           ============================================================ */
         var img      = new Image();
         var img      = makeImg(imageUrl, '', 'rm-strip');
        img.className = 'rm-strip';
        img.alt      = '';
        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';
Line 54: Line 62:
         });
         });


        img.src = imageUrl;
         el.insertBefore(img, el.firstChild);
         el.insertBefore(img, el.firstChild);


Line 62: Line 69:


         /* ============================================================
         /* ============================================================
           2D STATIC IMAGE  (created only if URL provided)
           2D STATIC IMAGE  (<img> created in JS)
           ============================================================ */
           ============================================================ */
         var staticImg = null;
         var staticImg = null;
         if (image2dUrl) {
         if (image2dUrl) {
             staticImg = new Image();
             staticImg = makeImg(image2dUrl, '', 'rm-static');
            staticImg.className = 'rm-static';
            staticImg.alt      = '';
            staticImg.draggable = false;
            staticImg.src      = image2dUrl;
             el.insertBefore(staticImg, el.firstChild);
             el.insertBefore(staticImg, el.firstChild);
        }
        /* ============================================================
          MODE TOGGLE  (top-right, built in JS)
          ============================================================ */
        var btn2d = null, btn3d = null;
        if (image2dUrl) {
            var toggle = document.createElement('div');
            toggle.className = 'rm-mode-toggle';
            btn2d = document.createElement('span');
            btn2d.className = 'rm-mode-btn rm-mode-2d';
            btn2d.setAttribute('role', 'button');
            btn2d.setAttribute('tabindex', '0');
            btn2d.title = '2D View';
            if (icon2dUrl) {
                btn2d.appendChild(makeImg(icon2dUrl, '2D', 'rm-mode-icon'));
            } else {
                btn2d.textContent = '2D';
            }
            btn3d = document.createElement('span');
            btn3d.className = 'rm-mode-btn rm-mode-3d rm-active';
            btn3d.setAttribute('role', 'button');
            btn3d.setAttribute('tabindex', '0');
            btn3d.title = '3D View';
            if (icon3dUrl) {
                btn3d.appendChild(makeImg(icon3dUrl, '3D', 'rm-mode-icon'));
            } else {
                btn3d.textContent = '3D';
            }
            toggle.appendChild(btn2d);
            toggle.appendChild(btn3d);
            el.appendChild(toggle);
        }
        /* ============================================================
          ROTATION ICON  (bottom-right, built in JS)
          ============================================================ */
        if (iconRotUrl) {
            var rotDiv = document.createElement('div');
            rotDiv.className = 'rm-rotate-icon';
            rotDiv.appendChild(makeImg(iconRotUrl, 'Drag to rotate', ''));
            el.appendChild(rotDiv);
         }
         }


Line 86: Line 135:


         /* ============================================================
         /* ============================================================
           2D ↔ 3D MODE SWITCHING
           2D ↔ 3D SWITCHING
           ============================================================ */
           ============================================================ */
        var btn2d = el.querySelector('.rm-mode-2d'),
            btn3d = el.querySelector('.rm-mode-3d');
         function setMode(mode) {
         function setMode(mode) {
             is2D = (mode === '2d');
             is2D = (mode === '2d');
Line 104: Line 150:
             }
             }


            // Toggle active button
             if (btn2d && btn3d) {
             if (btn2d && btn3d) {
                 btn2d.classList.toggle('rm-active', is2D);
                 btn2d.classList.toggle('rm-active', is2D);
Line 111: Line 156:
         }
         }


         if (btn2d) {
         if (btn2d) btn2d.addEventListener('click', function () { setMode('2d'); });
            btn2d.addEventListener('click', function () { setMode('2d'); });
         if (btn3d) btn3d.addEventListener('click', function () { setMode('3d'); });
        }
         if (btn3d) {
            btn3d.addEventListener('click', function () { setMode('3d'); });
        }


         /* ============================================================
         /* ============================================================
Line 155: Line 196:


         el.addEventListener('mousedown', function (e) {
         el.addEventListener('mousedown', function (e) {
            // Don't drag if clicking a mode button
             if (e.target.closest('.rm-mode-toggle')) return;
             if (e.target.closest('.rm-mode-toggle')) return;
             if (e.button === 0 && dragOK) { startDrag(e.clientX); e.preventDefault(); }
             if (e.button === 0 && dragOK) { startDrag(e.clientX); e.preventDefault(); }
Line 177: Line 217:
         el.addEventListener('keydown', function (e) {
         el.addEventListener('keydown', function (e) {
             if (is2D) return;
             if (is2D) return;
             if (e.key === 'ArrowLeft')  { nudge(1); e.preventDefault(); }
             if (e.key === 'ArrowLeft')  { nudge(-1); e.preventDefault(); }
             if (e.key === 'ArrowRight') { nudge(1); e.preventDefault(); }
             if (e.key === 'ArrowRight') { nudge( 1); e.preventDefault(); }
             if (e.key === 'Home')      { reset();  e.preventDefault(); }
             if (e.key === 'Home')      { reset();  e.preventDefault(); }
         });
         });
Line 187: Line 227:
                 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 (bL) bL.addEventListener('click', function () { nudge(1); });
             if (bR) bR.addEventListener('click', function () { nudge( 1); });
             if (bR) bR.addEventListener('click', function () { nudge(-1); });
             if (bO) bO.addEventListener('click', reset);
             if (bO) bO.addEventListener('click', reset);
         }
         }
Line 196: Line 236:
     }
     }


     /* ---- scan & initialise ---- */
     /* ---- scan & init ---- */
     function initAll(root) {
     function initAll(root) {
         var els = (root || document).querySelectorAll('.rotatable-model');
         var els = (root || document).querySelectorAll('.rotatable-model');