Skip to content

Commit cbb5248

Browse files
authored
Merge pull request #906 from rgantzos/thumbnail-setter
Thumbnail setter
2 parents 9db919a + ac2af72 commit cbb5248

File tree

6 files changed

+208
-0
lines changed

6 files changed

+208
-0
lines changed

api/modals.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ ScratchTools.modals = {
3232
var code = document.createElement("code");
3333
code.textContent = component.content;
3434
modal.appendChild(code);
35+
} else if (component.type === "html") {
36+
modal.appendChild(component.content);
3537
}
3638
});
3739

@@ -45,5 +47,11 @@ ScratchTools.modals = {
4547
div.appendChild(modal);
4648
modal.prepend(orangeBar);
4749
document.body.appendChild(div);
50+
51+
return {
52+
close: function () {
53+
div.remove();
54+
},
55+
};
4856
},
4957
};

features/features.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
[
2+
{
3+
"version": 2,
4+
"id": "upload-thumbnail",
5+
"versionAdded": "v4.0.0"
6+
},
27
{
38
"version": 2,
49
"id": "paint-align",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"title": "Set Thumbnail",
3+
"description": "Allows you to upload an image or GIF as a project thumbnail, or set the thumbnail to the current stage.",
4+
"credits": [
5+
{
6+
"username": "Sanjang_Beta",
7+
"url": "https://scratch.mit.edu/users/Sanjang_Beta/"
8+
},
9+
{ "username": "rgantzos", "url": "https://scratch.mit.edu/users/rgantzos/" }
10+
],
11+
"type": ["Website"],
12+
"tags": ["New", "Recommended"],
13+
"scripts": [{ "file": "script.js", "runOn": "/projects/*" }],
14+
"styles": [{ "file": "style.css", "runOn": "/projects/*" }],
15+
"resources": [{ "name": "thumbnail-btn", "path": "/thumbnail-btn.svg" }],
16+
"dynamic": true
17+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
export default async function ({ feature, console }) {
2+
ScratchTools.waitForElements(
3+
".preview .inner .flex-row.action-buttons",
4+
async function (row) {
5+
if (feature.redux.getState()?.preview.projectInfo.author.username !== feature.redux.getState()?.session?.session?.user?.username) return;
6+
7+
if (row.querySelector(".ste-thumbnail")) return;
8+
let button = document.createElement("button");
9+
button.className = "button action-button ste-thumbnail";
10+
button.textContent = "Set Thumbnail";
11+
feature.self.hideOnDisable(button);
12+
13+
let input = document.createElement("input");
14+
input.className = "ste-thumbnail-input";
15+
input.style.display = "none";
16+
input.type = "file";
17+
input.accept = "image/*";
18+
input.addEventListener("input", onThumbInput);
19+
document.body.appendChild(input);
20+
21+
function onThumbInput() {
22+
if (input.files?.[0]) {
23+
setThumbnail(input.files[0]);
24+
}
25+
}
26+
27+
button.addEventListener("click", async function () {
28+
let upload = document.createElement("button");
29+
upload.textContent = "Upload Image or GIF";
30+
upload.style.marginRight = ".5rem";
31+
upload.addEventListener("click", function () {
32+
input.click();
33+
});
34+
35+
async function getStage() {
36+
return new Promise((resolve) => {
37+
feature.traps.vm.postIOData("video", {
38+
forceTransparentPreview: true,
39+
});
40+
feature.traps.vm.renderer.requestSnapshot((dataURL) => {
41+
feature.traps.vm.postIOData("video", {
42+
forceTransparentPreview: false,
43+
});
44+
resolve(dataURL);
45+
});
46+
});
47+
}
48+
49+
let useStage = document.createElement("button");
50+
useStage.textContent = "Use Stage";
51+
useStage.className = "ste-thumbnail-stage";
52+
useStage.addEventListener("click", async function () {
53+
function dataURLtoBlob(dataurl) {
54+
let arr = dataurl.split(",");
55+
let mime = arr[0].match(/:(.*?);/)[1];
56+
let bstr = atob(arr[1]);
57+
let n = bstr.length;
58+
let u8arr = new Uint8Array(n);
59+
while (n--) {
60+
u8arr[n] = bstr.charCodeAt(n);
61+
}
62+
return new Blob([u8arr], { type: mime });
63+
}
64+
65+
let url = await getStage()
66+
console.log(url)
67+
let blob = dataURLtoBlob(url);
68+
69+
let file = new File([blob], "image.png", { type: "image/png" });
70+
71+
let dataTransfer = new DataTransfer();
72+
dataTransfer.items.add(file);
73+
74+
input.files = dataTransfer.files;
75+
76+
onThumbInput();
77+
});
78+
79+
if (!feature.traps.gui().vmStatus.started) {
80+
useStage.setAttribute("disabled", "");
81+
}
82+
83+
let modal = ScratchTools.modals.create({
84+
title: "Set Thumbnail",
85+
description:
86+
"You can set the thumbnail to an image you upload or you can set it to what is currently on the stage. The project needs to have been started already in order to upload from the stage.",
87+
components: [
88+
{
89+
type: "html",
90+
content: upload,
91+
},
92+
{
93+
type: "html",
94+
content: useStage,
95+
},
96+
{
97+
type: "html",
98+
content: document.createElement("br"),
99+
},
100+
],
101+
});
102+
103+
useStage.addEventListener("click", function () {
104+
modal.close();
105+
});
106+
107+
upload.addEventListener("click", function () {
108+
modal.close();
109+
});
110+
});
111+
row.appendChild(button);
112+
}
113+
);
114+
115+
async function setThumbnail(file) {
116+
let options = {
117+
body: file,
118+
headers: {
119+
accept: "*/*",
120+
"content-type": file.type,
121+
"x-csrftoken": feature.auth.csrf(),
122+
"x-requested-with": "XMLHttpRequest",
123+
},
124+
referrer: window.location.href,
125+
referrerPolicy: "strict-origin-when-cross-origin",
126+
method: "POST",
127+
mode: "cors",
128+
credentials: "include",
129+
};
130+
131+
let response = await fetch(
132+
`https://scratch.mit.edu/internalapi/project/thumbnail/${
133+
window.location.pathname.split("/")[2]
134+
}/set/`,
135+
options
136+
);
137+
138+
if (response.ok) {
139+
ScratchTools.modals.create({
140+
title: "Successfully Set Thumbnail",
141+
description: "This project's thumbnail has been updated.",
142+
components: [],
143+
});
144+
} else {
145+
ScratchTools.modals.create({
146+
title: "Failed to Set Thumbnail",
147+
description: "This project's thumbnail was not able to be updated.",
148+
components: [],
149+
});
150+
}
151+
}
152+
}
153+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.ste-thumbnail::before {
2+
background-image: var(--scratchtoolsresource-thumbnail-btn);
3+
display: inline-block;
4+
margin-right: 0.25rem;
5+
background-repeat: no-repeat;
6+
background-position: center center;
7+
background-size: contain;
8+
width: 0.875rem;
9+
height: 0.875rem;
10+
vertical-align: bottom;
11+
content: "";
12+
transform: scale(1.3);
13+
}
14+
15+
16+
.ste-thumbnail-stage:disabled {
17+
opacity: .5;
18+
cursor: not-allowed !important;
19+
background: #b5b5b5 !important;
20+
}
21+
22+
.ste-thumbnail-stage:disabled:hover {
23+
top: 0px !important;
24+
}
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)