Dropbox image viewer in Picarto - by StevenRoy

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

当前为 2022-09-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Dropbox image viewer in Picarto - by StevenRoy
  3. // @namespace http://michrev.com/
  4. // @description Save a click or two by viewing Dropbox images directly within Picarto's "leaving" pages.
  5. // @include https://www.dropbox.com/*
  6. // @include https://dropbox.com/*
  7. // @include https://www.picarto.tv/site/referrer*
  8. // @include https://picarto.tv/site/referrer*
  9. // @version 1.12
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. "use strict";
  14.  
  15. // _____________ __ __
  16. // / ___________/ / \,-' /
  17. // / /__ ___ / /\,-/ /
  18. // \___ \ / __\ / / / /
  19. //______/ / / / / / / / StevenRoy was here
  20. //_______/ /_/ /_/ /_/ 02022.09.21
  21.  
  22.  
  23.  
  24. // when a is 200, returns true when b is >=197 and <=204
  25. // when a is 1000, returns true when b is >=981 and <=1020
  26. // sensitivity is proportional to the sum of the numbers being compared
  27. // (Can also be adjusted by changing the 100 constant but I like it where it is now.)
  28. function ratherclose(a,b) { return Math.abs(a-b)*100<(a+b); }
  29.  
  30. var img,mcx,mcy,ww,wh,dw,dh,dx,dy,isl,imgfit=0; // window and image size
  31. function resized() {
  32. if (!img) { return; } // TSNH - throw()? alert()?
  33. var de=document.documentElement;
  34. if (window.innerWidth) { // Preferred in FF because it doesn't shrink when a scrollbar is present.
  35. ww=window.innerWidth;
  36. wh=window.innerHeight;
  37. } else {
  38. if (de && de.clientWidth) { // Also exists in FF; this one excludes scrollbar if present.
  39. ww=de.clientWidth; // (Though that's kinda moot when we're using overflow:hidden)
  40. wh=de.clientHeight;
  41. } else {
  42. return false; // TSNH? Am I forgetting anything?
  43. }
  44. }
  45. // if (mcx===undefined && mcy===undefined) { mcx=ww>>1; mcy=wh>>1; } // until we get mouse coords
  46. var iw=img.w,ih=img.h; // We use these a lot.
  47. // var war=ww/wh; // window aspect ratio (1=square, >1=wide)
  48. var iar=iw/ih; // image aspect ratio (1920x800 -> 12/5 which is 2.4)
  49. var wscw=wh*iar; // resulting width from scaling window height to image's AR.
  50.  
  51. // image smaller than window: original size (centered), or enlarged to fit (letterboxed)
  52. // image larger than window: original size (pan), or shrunk to fit (letterboxed)
  53. // These are actually pretty much the same... except pan is only enabled in 1/4 cases
  54. // But there's a trickier case: Image smaller in one dimension but larger in the other.
  55. // I could give it two zoom states but neither is original-size ... or should I do the opposite?
  56.  
  57. // Let's just always assume three zoom-states: original (iw,ih), fit-x (ww,ww/iar), and fit-y (wh*iar,wh)
  58. // ...And then sort 'em. But first eliminate any that are nearly identical.
  59.  
  60. isl=[iw]; // image size list, start with original image size
  61. var am=ratherclose(ww,wscw); // aspect ratio match between image and window?
  62. if (!ratherclose(iw,ww)) { // but not actual size match.
  63. if (am) {
  64. console.log("Aspect ratio match");
  65. // isl.push(Math.floor(ww+wh*iar+1)>>1); // use average for near-match (This is actually bad.)
  66. // Assuming a very-near-but-not-perfect match: Which size is further from iw? Let's use that one.
  67. isl.push(Math.abs(iw-ww) > Math.abs(iw-wscw) ? ww : wscw);
  68. }
  69. else { isl.push(ww); }
  70. }
  71. if (!am && !ratherclose(iw,wscw)) { isl.push(wscw); }
  72. if (isl.length>1) {
  73. if (imgfit>=isl.length) { imgfit=isl.length-1; } // for those times when a size vanishes
  74. isl.sort((a,b) => a-b);
  75. img.style.cursor=(imgfit==isl.length-1)?"zoom-out":"zoom-in"; // How to affect blank space around img? (But do we want that?)
  76. } else { imgfit=0; img.style.cursor="default"; }
  77.  
  78. dw=isl[imgfit];
  79. dh=(dw==iw)?ih:(dw/iar); // dh=dw/(iw/ih) ... dh/ih=dw/iw
  80.  
  81. // panned(); // setting image size is done in here too now. Except it's now part of the animation process.
  82. }
  83.  
  84. function panned() { // Animated response to Mouse movement with requestAnimationFrame()
  85. if (mcx!==undefined && dw>ww) { dx=mcx*(ww-dw)/ww; } else { dx=(ww-dw)/2; }
  86. if (mcy!==undefined && dh>wh) { dy=mcy*(wh-dh)/wh; } else { dy=(wh-dh)/2; }
  87. var ics=img.style;
  88. ics.width=Math.round(dw)+"px"; // TODO: These can be animated for awesomer zooming. (Except for large images that crash FF!!!)
  89. ics.height=Math.round(dh)+"px";
  90. ics.left=Math.round(dx)+"px"; // always negative when panning, otherwise positive and centered
  91. ics.top=Math.round(dy)+"px";
  92. }
  93.  
  94. function animate() {
  95. panned();
  96. // var c=coordstoimg();
  97. requestAnimationFrame(animate); // Uh oh, now we're doing it!
  98. }
  99.  
  100. // ** ** ** EVENTS
  101.  
  102. function shutup(e) {
  103. if (!e) { e=window.event; if (!e) { return false; } }
  104. e.cancelBubble = true;
  105. if (e.stopPropagation) e.stopPropagation(); // For when "return true" just isn't good enough?
  106. e.preventDefault(); // And I mean it!
  107. return e;
  108. }
  109.  
  110. function smother(evt) { shutup(evt); return false; }
  111.  
  112. /* from AwShirt JS: (Thanks Jackbox!)
  113.  
  114. var a=o.canvas.getBoundingClientRect(),
  115. e={x:t.clientX-a.left,y:t.clientY-a.top};
  116. e.x=e.x*(o.canvas.width/parseInt(o.canvas.style.width,10)),
  117. e.y=e.y*(o.canvas.height/parseInt(o.canvas.style.height,10)),
  118. o[t.type](e)
  119.  
  120. Note: image width/height is fixed and in img.w/h ...values set in style are dw/dh
  121. and ratios are the same - just divide img.w/dw once
  122. In case we forget any of these, getBoundingClientRect() also gives us width and height (from style)
  123. NOTE: top/left values in the DOMRect are relative to the viewport and change if the document is scrolled!
  124. Also: We don't need any of this because we set all those values already! (And we don't even have scrollbars.)
  125. */
  126.  
  127. function mousecoords(e) { // event
  128. if ("pageX" in e && "pageY" in e) {
  129. mcx = e.pageX; mcy = e.pageY;
  130. } else { // We want coords relative to top (origin) of document, this should do it:
  131. mcx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  132. mcy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  133. }
  134. // x -= c.offsetLeft;
  135. // y -= c.offsetTop;
  136. // return [x,y];
  137. }
  138.  
  139. function smdown(evt) {
  140. // var e=shutup(evt);
  141. imgfit++;
  142. if (imgfit>=isl.length) { imgfit=0; } // Select next (or first) size
  143. resized();
  144. smmove(evt); // trigger move() after coordinates change
  145. return false; // Does this value do anything?
  146. }
  147.  
  148. /* ** ** Not currently used:
  149. function smup(evt) {
  150. var e=evt||window.event;
  151. // return false;
  152. }
  153. ** ** */
  154.  
  155. function smmove(evt) {
  156. var e=shutup(evt);
  157. mousecoords(e);
  158. // panned();
  159. return false;
  160. }
  161.  
  162. function makezoomableimage(i) {
  163. img=i;
  164. img.w=img.naturalWidth;
  165. img.h=img.naturalHeight;
  166.  
  167. window.onresize=resized; resized();
  168. // i.onmousedown=smdown;
  169. i.onmousedown=smother;
  170. i.onmouseup=smother; // not used!
  171. window.onmousemove=smmove;
  172. // i.onclick=smother; // none of these used either
  173. i.onclick=smdown; // alternative strategy, maybe less good, maybe not?
  174. i.ondblclick=smother;
  175. // i.oncontextmenu=smother;
  176. animate(); // This starts the maaaaagic!
  177. img.style.position="fixed";
  178. img.style.maxWidth="unset"; // Leave it to Picarto to screw with zooms...
  179. return i;
  180. }
  181.  
  182. const c64pal="000 fff c05 6ef c5d 6c5 42b fe7 c84 641 f79 555 888 cfa a9f ccc";
  183. // That's my c64 palette. I'm going for a balance of authentic and vivid.
  184. var waitindicator=function(){
  185. var waittimer,wc=4,wde,wrefc=0;
  186. return function(){
  187. if(!wde) {
  188. document.body.appendChild(wde=Object.assign(document.createElement("div"),{
  189. innerHTML:'W<span>a</span><span>i</span><span>t</span><span>.</span><span>.</span><span>.</span>',
  190. // id:'loading',
  191. style:"z-index:90999;box-sizing:border-box;font-size:20pt;line-height:18pt;font-family:" /* 'Roboto', */ +"'Andale Mono',monospace;"+
  192. "position:fixed;height:30px;width:160px;top:50%;left:50%;text-align:center;"+
  193. "margin:-15px 0 0 -80px;background:#000;color:#fff"
  194. }));
  195. }
  196. if (wde && !waittimer) { waittimer=setInterval(function(){
  197. if (!wde) { return clearInterval(waittimer); } // never underestimate the capacity of browsers to screw up
  198. wc=(wc+4)&60;
  199. var wcc=wc;
  200. wde.style.color="#"+c64pal.substr(wc,3); // A very specific set of colors...
  201. Array.prototype.forEach.call(wde.children,(e)=>{
  202. wcc=wcc+4&60;
  203. e.style.color="#"+c64pal.substr(wcc,3);
  204. });
  205. },150); }
  206. wrefc++;
  207. return function(){
  208. if (wrefc>0) { wrefc--; }
  209. setTimeout(()=>{
  210. if (wrefc==0 && wde) { wde.parentNode.removeChild(wde); wde=false; }
  211. },250); // in case of sequential busy-states, delay vanish of busy indicator
  212. };
  213. };
  214. }();
  215.  
  216. // So much gutting of previous code because we don't
  217. // need -any- of the canvas functions...
  218. function loadimage(src,cb) {
  219. var wi=waitindicator();
  220. var ix=document.createElement("img");
  221. // ix=new Image();
  222. ix.onload=function() {
  223. wi(); cb(ix); // pass img to callback
  224. }
  225. ix.src=src;
  226. } // that basically replaces an entire image manipulation library!
  227. // (Although replacing <canvas> with <image> does seem to make this perform worse in FF52
  228. // if my awesome animation function is used... I just won't use it then. Problem solved.)
  229.  
  230. // Freaking dummkopfs Picarto seems to like to lazy-load all its actual page content,
  231. // meaning things we want to replace won't exist at this function's execute-time.
  232. // Of course there's a stupid workaround...
  233. function itsapicartopage(addr){
  234. // addr will look like: "https:// www.dropbox.com/s/y88o006k1ol8ryj/alkalithemorningafter.png?dl=0"
  235. var m=parsedbaddr(addr);
  236. if (m) {
  237. var keeplink=document.links;
  238. if (keeplink && keeplink[0] && keeplink[0].href==addr) {
  239. keeplink=keeplink[0].parentNode;
  240.  
  241. loadimage(m,(ix)=>{
  242. var mb=document.getElementById("main-container"); // within div#root within body
  243. if (mb && mb.children) mb=mb.childNodes[0];
  244. console.log(mb,keeplink);
  245. if (mb && keeplink) {
  246. mb.innerHTML='';
  247. mb.style="position:fixed; overflow:hidden; min-height:100vh; height:100vh; width:100%; padding:0; margin:0; display:block";
  248. keeplink.style="position:fixed;right:0;bottom:0;margin:20px";
  249. mb.appendChild(makezoomableimage(ix));
  250. mb.appendChild(keeplink);
  251. var d1=document.createElement("div"); // additional UI element, mainly for aesthetics!
  252. d1.className=keeplink.className; // This may vary!
  253. d1.style="position:fixed;left:0;bottom:0;margin:20px";
  254. keeplink.style.backgroundColor=d1.style.backgroundColor=getComputedStyle(document.body).backgroundColor;
  255. d1.innerHTML=img.w+" x "+img.h;
  256. mb.appendChild(d1);
  257. } else console.log("Fail");
  258. });
  259. } else { console.log("Retrying"); setTimeout(itsapicartopage,200,addr); }
  260. }
  261. }
  262.  
  263. function parsedbaddr(a){
  264. var m=a.match(/\/\/(?:www\.)?dropbox\.com\/.+\/[^/?.]*\.(jpg|jpeg|png|gif|webp)(\?dl=0)?$/);
  265. if (m && m.length && m[1]) { // It's a valid address...
  266. if (m[2] == '?dl=0') a=a.replace(/\?dl=0/,'');
  267. else if (m[2]) throw ("TSNH: Weird query in db addr");
  268. return a+'?dl=1'; // Functioning link to the image itself
  269. }
  270. return false;
  271. }
  272.  
  273. var l=top.location.href;
  274. var m=l.match(/\/\/(?:www\.)?picarto\.tv\/.+?go=(http(?:s)?%3A%2F%2F[0-9A-Za-z%.]+)/);
  275. if (m && m.length && m[1]) itsapicartopage(decodeURIComponent(m[1]));
  276.  
  277. // (Also, the Dropbox site is broken in Firefox 52 but we can detect that
  278. // and force images to appear there anyway. This one's for the WinXP users. Shrug.)
  279. else if (m=parsedbaddr(l)) { // If we're on the dropbox site...
  280. var bv=navigator.userAgent.match(/Firefox\/([0-9]+)/); // but using a browser that db WON'T WORK ON
  281. if (bv && bv[1] && (bv[1]-0)<=52) {
  282. var i0=document.createElement("img"); // Just dump the image
  283. i0.src=m;
  284. document.body.appendChild(i0);
  285. }
  286. }
  287.