Dropbox image viewer in Picarto

Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages. By StevenRoy

// ==UserScript==
// @name        Dropbox image viewer in Picarto
// @namespace   http://michrev.com/
// @description Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages. By StevenRoy
// @include     https://www.dropbox.com/*
// @include     https://picarto.tv/site/referrer*
// @version     1.14
// @grant       none
// @supportURL  https://greasyfork.org/en/users/934871
// ==/UserScript==

"use strict";

//     _____________  __    __
//    / ___________/ /  \,-' /
//   / /__    ___   / /\,-/ /
//   \___ \  / __\ / /   / /
//______/ / / /   / /   / /    StevenRoy was here
//_______/ /_/   /_/   /_/     02023.02.24



// JB: issues/1092 obviously applies here.



// Sensitivity is proportional to the sum of the numbers being compared
// When a is 200, returns true when b is >=197 and <=204
// When a is 1000, returns true when b is >=981 and <=1020
// (Can also be adjusted by changing the 100 constant but I like it where it is now.)
function ratherclose(a,b) { return Math.abs(a-b)*100<(a+b); }

var img,mcx,mcy,ww,wh,dw,dh,dx,dy,isl,imgfit=0,eizs; // window and image size
function resized() {
  if (!img) { return; } // TSNH - throw()? alert()?
  var de=document.documentElement;
  if (window.innerWidth) { // Preferred in FF because it doesn't shrink when a scrollbar is present.
    ww=window.innerWidth;
    wh=window.innerHeight;
  } else {
    if (de && de.clientWidth) { // Also exists in FF; this one excludes scrollbar if present.
      ww=de.clientWidth;  // (Though that's kinda moot when we're using overflow:hidden)
      wh=de.clientHeight;
    } else {
      return false; // TSNH? Am I forgetting anything?
    }
  }
//  if (mcx===undefined && mcy===undefined) { mcx=ww>>1; mcy=wh>>1; } // until we get mouse coords
  var iw=img.w,ih=img.h; // We use these a lot.
//  var war=ww/wh; // window aspect ratio (1=square, >1=wide)
  var iar=iw/ih; // image aspect ratio (1920x800 -> 12/5 which is 2.4)
  var wscw=wh*iar; // resulting width from scaling window height to image's AR.

// image smaller than window: original size (centered), or enlarged to fit (letterboxed)
// image larger than window: original size (pan), or shrunk to fit (letterboxed)
// These are actually pretty much the same... except pan is only enabled in 1/4 cases
// But there's a trickier case: Image smaller in one dimension but larger in the other.
// I could give it two zoom states but neither is original-size ... or should I do the opposite?

// Let's just always assume three zoom-states: original (iw,ih), fit-x (ww,ww/iar), and fit-y (wh*iar,wh)
// ...And then sort 'em. But first eliminate any that are nearly identical.

  isl=[iw]; // image size list, start with original image size
  var am=ratherclose(ww,wscw); // aspect ratio match between image and window?
  if (!ratherclose(iw,ww)) { // but not actual size match.
    if (am) {
      console.log("Aspect ratio match");
//      isl.push(Math.floor(ww+wh*iar+1)>>1); // use average for near-match (This is actually bad.)
// Assuming a very-near-but-not-perfect match: Which size is further from iw? Let's use that one.
      isl.push(Math.abs(iw-ww) > Math.abs(iw-wscw) ? ww : wscw);
    }
    else { isl.push(ww); }
  }
  if (!am && !ratherclose(iw,wscw)) { isl.push(wscw); }
  if (isl.length>1) {
    if (imgfit>=isl.length) { imgfit=isl.length-1; } // for those times when a size vanishes
    isl.sort((a,b) => a-b);
    img.style.cursor=(imgfit==isl.length-1)?"zoom-out":"zoom-in"; // How to affect blank space around img? (But do we want that?)
  } else { imgfit=0; img.style.cursor="default"; }

  dw=isl[imgfit];
  dh=(dw==iw)?ih:(dw/iar); // dh=dw/(iw/ih) ... dh/ih=dw/iw
  if (eizs) { eizs.textContent=Math.round(100*dw/iw)+" %"; }

//  panned(); // setting image size is done in here too now. Except it's now part of the animation process.
}

function panned() { // Animated response to Mouse movement with requestAnimationFrame()
  if (mcx!==undefined && dw>ww) { dx=mcx*(ww-dw)/ww; } else { dx=(ww-dw)/2; }
  if (mcy!==undefined && dh>wh) { dy=mcy*(wh-dh)/wh; } else { dy=(wh-dh)/2; }
  var ics=img.style;
  ics.width=Math.round(dw)+"px"; // These were animated for awesomer zooming, but FF has problems with that!!!
  ics.height=Math.round(dh)+"px";
  ics.left=Math.round(dx)+"px"; // always negative when panning, otherwise positive and centered
  ics.top=Math.round(dy)+"px";
}

function animate() {
  panned();
//  var c=coordstoimg();
  requestAnimationFrame(animate); // Uh oh, now we're doing it!
}

// ** ** ** EVENTS

function shutup(e) {
  if (!e) { e=window.event; if (!e) { return false; } }
  e.cancelBubble = true;
  if (e.stopPropagation) e.stopPropagation(); // For when "return true" just isn't good enough?
  e.preventDefault(); // And I mean it!
  return e;
}

function smother(evt) { shutup(evt); return false; }

function mousecoords(e) { // event
  if ("pageX" in e && "pageY" in e) {
    mcx = e.pageX; mcy = e.pageY;
  } else { // We want coords relative to top (origin) of document, this should do it:
    mcx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    mcy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
//  x -= c.offsetLeft;
//  y -= c.offsetTop;
//  return [x,y];
}

function smdown(evt) {
//  var e=shutup(evt);
  imgfit++;
  if (imgfit>=isl.length) { imgfit=0; } // Select next (or first) size
  resized();
  smmove(evt); // trigger move() after coordinates change
  return false; // Does this value do anything?
}

function smmove(evt) {
  var e=shutup(evt);
  mousecoords(e);
//  panned();
  return false;
}

function makezoomableimage(i) {
  img=i;
  img.w=img.naturalWidth;
  img.h=img.naturalHeight;

  window.onresize=resized; resized();
//  i.onmousedown=smdown;
  i.onmousedown=smother;
  i.onmouseup=smother; // not used!
  window.onmousemove=smmove;
//  i.onclick=smother; // none of these used either
  i.onclick=smdown; // alternative strategy, maybe less good, maybe not?
  i.ondblclick=smother;
//  i.oncontextmenu=smother;
  animate(); // This starts the maaaaagic!
  img.style.position="fixed";
  img.style.maxWidth="unset"; // Leave it to Picarto to screw with zooms...
  return i;
}

const c64pal="000 fff c05 6ef c5d 6c5 42b fe7 c84 641 f79 555 888 cfa a9f ccc";
// That's my c64 palette. I'm going for a balance of authentic and vivid.
var waitindicator=function(){
	var waittimer,wc=4,wde,wrefc=0;
	return function(){
		if(!wde) {
			document.body.appendChild(wde=Object.assign(document.createElement("div"),{
				innerHTML:'W<span>a</span><span>i</span><span>t</span><span>.</span><span>.</span><span>.</span>',
//				id:'loading',
				style:"z-index:90999;box-sizing:border-box;font-size:20pt;line-height:18pt;font-family:" /* 'Roboto', */ +"'Andale Mono',monospace;"+
					"position:fixed;height:30px;width:160px;top:50%;left:50%;text-align:center;"+
					"margin:-15px 0 0 -80px;background:#000;color:#fff"
			}));
		}
		if (wde && !waittimer) { waittimer=setInterval(function(){
			if (!wde) { return clearInterval(waittimer); } // never underestimate the capacity of browsers to screw up
			wc=(wc+4)&60;
			var wcc=wc;
			wde.style.color="#"+c64pal.substr(wc,3); // A very specific set of colors...
			Array.prototype.forEach.call(wde.children,(e)=>{
				wcc=wcc+4&60;
				e.style.color="#"+c64pal.substr(wcc,3);
			});
		},150); }
		wrefc++;
		return function(){
			if (wrefc>0) { wrefc--; }
			setTimeout(()=>{
				if (wrefc==0 && wde) { wde.parentNode.removeChild(wde); wde=false; }
			},250); // in case of sequential busy-states, delay vanish of busy indicator
		};
	};
}();

// So much gutting of previous code because we don't
// need -any- of the canvas functions...
function loadimage(src,cb) {
	var wi=waitindicator();
	var ix=document.createElement("img");
//  ix=new Image();
	ix.onload=function() {
		wi(); cb(ix); // pass img to callback
	}
	ix.src=src;
} // that basically replaces an entire image manipulation library!
// (Although replacing <canvas> with <image> does seem to make this perform worse (and often crash) in FF52
//   if my awesome animation function is used... I just won't use it then. Problem solved.)

// Freaking dummkopfs Picarto seems to like to lazy-load all its actual page content,
// meaning things we want to replace won't exist at this function's execute-time.
// Of course there's a stupid workaround...
function itsapicartopage(addr){
// addr will look like: "https:// www.dropbox.com/s/y88o006k1ol8ryj/alkalithemorningafter.png?dl=0"
	var m=parsedbaddr(addr);
	if (m) {
		var keeplink=document.links;
		if (keeplink && keeplink[0] && keeplink[0].href==addr) {
			keeplink=keeplink[0].parentNode;

			loadimage(m,(ix)=>{
				var mb=document.getElementById("main-container"); // within div#root within body
				if (mb && mb.children) mb=mb.childNodes[0];
				console.log(mb,keeplink);
				if (mb && keeplink) {
					mb.innerHTML='';
					mb.style="position:fixed; overflow:hidden; min-height:100vh; height:100vh; width:100%; padding:0; margin:0; display:block";
					keeplink.style="position:fixed;right:0;bottom:0;margin:20px";
					eizs=document.createElement("span");
					mb.appendChild(makezoomableimage(ix));
					mb.appendChild(keeplink);
					var d1=document.createElement("div"); // additional UI element, mainly for balanced aesthetics!
					d1.className=keeplink.className; // This may vary!
					d1.style="position:fixed;left:0;bottom:0;margin:20px;text-align:center";
					keeplink.style.backgroundColor=d1.style.backgroundColor=getComputedStyle(document.body).backgroundColor;
					d1.innerHTML=img.w+" x "+img.h+"<BR />";
					mb.appendChild(d1);
					d1.appendChild(eizs);
				} else console.log("Fail");
			});
		} else { console.log("Retrying"); setTimeout(itsapicartopage,200,addr); }
	}
}

function parsedbaddr(a){
	var m=a.match(/\/\/(?:www\.)?dropbox\.com\/.+\/[^/?.]*\.(jpg|jpeg|png|gif|webp)(\?dl=0)?$/);
	if (m && m.length && m[1]) { // It's a valid address...
		if (m[2] == '?dl=0') a=a.replace(/\?dl=0/,'');
		else if (m[2]) throw ("TSNH: Weird query in db addr");
		return a+'?dl=1'; // Functioning link to the image itself
	}
	return false;
}

var l=top.location.href;
var m=l.match(/\/\/(?:www\.)?picarto\.tv\/.+?go=(http(?:s)?%3A%2F%2F[^&]+)/);
if (m && m.length && m[1]) itsapicartopage(decodeURIComponent(m[1]));

// (Also, the Dropbox site is broken in Firefox 52 but we can detect that
// and force images to appear there anyway. This one's for the WinXP users. Shrug.)
else if (m=parsedbaddr(l)) { // If we're on the dropbox site...
	var bv=navigator.userAgent.match(/Firefox\/([0-9]+)/); // but using a browser that db WON'T WORK ON
	if (bv && bv[1] && (bv[1]-0)<=52) {
		var i0=document.createElement("img"); // Just dump the image (sorry, no fancy viewer this time)
		i0.src=m;
		document.body.appendChild(i0);
	}
}