-
Notifications
You must be signed in to change notification settings - Fork 364
/
userscript.js
155 lines (134 loc) · 5.29 KB
/
userscript.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
export default async function ({ addon, console }) {
const CDN2_REGEX = /(.*\/get_image\/.*?\/[0-9]+?_)([0-9]+?)x([0-9]+?)(\.[a-z]+)/;
const UPLOADS_REGEX = /^https?:\/\/uploads\.scratch\.mit\.edu\//;
const thumbnails = [];
addon.self.addEventListener("disabled", () => {
for (const { image, src, lazy } of thumbnails) {
if (lazy) {
image.dataset.original = src;
}
if (image.src) {
image.src = src;
}
}
});
addon.self.addEventListener("reenabled", () => {
for (const { image, src, lazy, newSrc } of thumbnails) {
if (lazy) {
image.dataset.original = newSrc;
}
if (image.src) {
image.src = newSrc;
}
}
});
main: while (true) {
// Images in forum posts are ignored.
const image = await addon.tab.waitForElement("img:not(.postmsg *)", {
markAsSeen: true,
});
const lazy = image.classList.contains("lazy");
let src = lazy ? image.dataset.original : image.src;
// Don't process data: URLs, since these are never thumbnails to be upsized,
// and can be lengthy strings - which really does cause issues when it comes
// to the cdn2 regex!
if (src.startsWith("data:")) continue;
// If the image is from uploads.scratch.mit.edu, but doesn't match the
// dimensions-including cdn2 style, reformat src so it does.
if (!CDN2_REGEX.test(src) && UPLOADS_REGEX.test(src)) {
const id = src.match(/[0-9]+/);
if (src.includes("projects")) {
// Project thumbnails are always 480x360.
src = `//uploads.scratch.mit.edu/get_image/project/${id}_480x360.png`;
} else if (src.includes("users")) {
// Max user avatar size is 500x500.
src = `//uploads.scratch.mit.edu/get_image/user/${id}_500x500.png`;
} else if (src.includes("galleries")) {
// Max studio thumbnail size is 500x500.
src = `//uploads.scratch.mit.edu/get_image/gallery/${id}_500x500.png`;
}
}
let width, height, newSrc;
const cdn2 = src.match(CDN2_REGEX);
if (cdn2) {
width = cdn2[2];
height = cdn2[3];
} else {
continue main;
}
[width, height] = resizeToScreenRes(width, height, image);
[width, height] = scaleDimensions(width, height);
if (cdn2) {
newSrc = cdn2[1].replace("cdn2", "uploads") + width + "x" + height + cdn2[4];
}
reloadImageSafe(image, src, newSrc, { lazy });
}
function resizeToScreenRes(width, height, image) {
// Note this returns values in CSS pixel units, i.e. it doesn't take into
// account browser scaling or high-DPI screens. That's good, because we
// want to leave the actual resolution-increase factor completely up to
// the addon user/settings!
let { width: widthRect, height: heightRect } = image.getBoundingClientRect();
// If we've only got partial or no apparent displayed dimensions, and
// width/height HTML attributes are provided, fall back to those. For some
// reason, these attributes don't seem to always apply to the displayed
// image the instant it is added to the page DOM.
if (!widthRect || !heightRect) {
if (image.hasAttribute("width") && image.hasAttribute("height")) {
widthRect = +image.getAttribute("width");
heightRect = +image.getAttribute("height");
}
}
// It's possible that the Scratch site is deliberately loading an image at
// a greater resolution than it displays at - because with dynamic layout,
// if the screen (window) resizes, the image may later be displayed larger.
//
// If there's an arbitrarily determined "maximum" size for this image, we
// should use that as the target dimensions instead. (Unless it's
// incidentally smaller, in which case, fall back to the greater [rendered]
// dimensions.)
let widthMax, heightMax;
if (image.classList.contains("studio-project-image")) {
// Studio project tiles are largest on (somewhat) thinner screens, since
// fewer columns are displayed.
widthMax = 347;
heightMax = 260;
}
// Use greater of scales across both axes to choose the dimension which
// needs the most scaling and to maintain the ratio between the rendered and
// loaded dimensions.
let scale = Math.max(widthRect / width, heightRect / height);
// Calculate scale for "maximum" dimensions, and use it instead - but only
// if it's greater than the actual scale. Theoretically, max possible
// always be >= actual, but if Scratch CSS changes and this addon is
// outdated, it's better to prefer the actual displayed dimensions as the
// basis we apply the resolution multiplier to later.
if (widthMax) {
const scaleMax = Math.max(widthMax / width, heightMax / height);
scale = Math.max(scale, scaleMax);
}
return [width * scale, height * scale];
}
function scaleDimensions(width, height) {
const factor = 0.01 * addon.settings.get("multiplier");
width *= factor;
height *= factor;
width = Math.round(width);
height = Math.round(height);
return [width, height];
}
function reloadImageSafe(image, src, newSrc, { lazy = false } = {}) {
thumbnails.push({
image,
src,
lazy,
newSrc,
});
if (lazy) {
image.dataset.original = newSrc;
}
if (image.src) {
image.src = newSrc;
}
}
}