diff --git a/api/feature/index.js b/api/feature/index.js index 703f2524..7788522c 100644 --- a/api/feature/index.js +++ b/api/feature/index.js @@ -1,12 +1,14 @@ import { default as self } from "./self.js"; import { default as traps } from "./traps.js"; import { default as auth } from "./auth.js"; +import { default as server } from "./server.js"; export default function (data) { var feature = new Feature(data); feature.self = self(data.id); feature.traps = traps() feature.auth = auth() + feature.server = server() feature.page = { appendToSharedSpace: ScratchTools.appendToSharedSpace, waitForElement: ScratchTools.waitForElement, diff --git a/api/feature/server.js b/api/feature/server.js new file mode 100644 index 00000000..b1f4c507 --- /dev/null +++ b/api/feature/server.js @@ -0,0 +1,8 @@ +export default function (id) { + return { + url: "https://data.scratchtools.app", + endpoint: function(path) { + return "https://data.scratchtools.app" + path + }, + } +} \ No newline at end of file diff --git a/features/features.json b/features/features.json index bf6c47f3..36ce35c0 100644 --- a/features/features.json +++ b/features/features.json @@ -1,4 +1,9 @@ [ + { + "version": 2, + "id": "project-reactions", + "versionAdded": "v4.0.0" + }, { "version": 2, "id": "chomp-blocks", diff --git a/features/project-reactions/data.json b/features/project-reactions/data.json new file mode 100644 index 00000000..c177ebac --- /dev/null +++ b/features/project-reactions/data.json @@ -0,0 +1,46 @@ +{ + "title": "Project Reactions", + "description": "Allows you to react to projects with different emojis.", + "credits": [ + { "username": "rgantzos", "url": "https://scratch.mit.edu/users/rgantzos/" } + ], + "type": ["Website"], + "tags": ["New"], + "dynamic": true, + "scripts": [ + { + "file": "script.js", + "runOn": "/projects/*" + } + ], + "styles": [ + { + "file": "style.css", + "runOn": "/projects/*" + } + ], + "components": [ + { + "type": "info", + "content": "Users will not receive a notification when you react to their projects." + } + ], + "resources": [ + { "name": "project-reactions-beaming", "path": "/emojis/beaming.svg" }, + { "name": "project-reactions-crying", "path": "/emojis/crying.svg" }, + { "name": "project-reactions-fire", "path": "/emojis/fire.svg" }, + { "name": "project-reactions-hands", "path": "/emojis/hands.svg" }, + { + "name": "project-reactions-heart-eyes", + "path": "/emojis/heart-eyes.svg" + }, + { + "name": "project-reactions-heart-face", + "path": "/emojis/heart-face.svg" + }, + { "name": "project-reactions-hearts", "path": "/emojis/hearts.svg" }, + { "name": "project-reactions-laughing", "path": "/emojis/laughing.svg" }, + { "name": "project-reactions-popper", "path": "/emojis/popper.svg" }, + { "name": "project-reactions-thumbsup", "path": "/emojis/thumbsup.svg" } + ] +} diff --git a/features/project-reactions/emojis/beaming.svg b/features/project-reactions/emojis/beaming.svg new file mode 100644 index 00000000..b10147ae --- /dev/null +++ b/features/project-reactions/emojis/beaming.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/crying.svg b/features/project-reactions/emojis/crying.svg new file mode 100644 index 00000000..a826b814 --- /dev/null +++ b/features/project-reactions/emojis/crying.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/fire.svg b/features/project-reactions/emojis/fire.svg new file mode 100644 index 00000000..6570b839 --- /dev/null +++ b/features/project-reactions/emojis/fire.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/hands.svg b/features/project-reactions/emojis/hands.svg new file mode 100644 index 00000000..90475afc --- /dev/null +++ b/features/project-reactions/emojis/hands.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/heart-eyes.svg b/features/project-reactions/emojis/heart-eyes.svg new file mode 100644 index 00000000..76badafd --- /dev/null +++ b/features/project-reactions/emojis/heart-eyes.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/heart-face.svg b/features/project-reactions/emojis/heart-face.svg new file mode 100644 index 00000000..b1de943d --- /dev/null +++ b/features/project-reactions/emojis/heart-face.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/hearts.svg b/features/project-reactions/emojis/hearts.svg new file mode 100644 index 00000000..c61f8712 --- /dev/null +++ b/features/project-reactions/emojis/hearts.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/laughing.svg b/features/project-reactions/emojis/laughing.svg new file mode 100644 index 00000000..0a8347e3 --- /dev/null +++ b/features/project-reactions/emojis/laughing.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/popper.svg b/features/project-reactions/emojis/popper.svg new file mode 100644 index 00000000..06dc90b3 --- /dev/null +++ b/features/project-reactions/emojis/popper.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/emojis/thumbsup.svg b/features/project-reactions/emojis/thumbsup.svg new file mode 100644 index 00000000..cf598a30 --- /dev/null +++ b/features/project-reactions/emojis/thumbsup.svg @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/project-reactions/script.js b/features/project-reactions/script.js new file mode 100644 index 00000000..7e82632d --- /dev/null +++ b/features/project-reactions/script.js @@ -0,0 +1,167 @@ +export default async function ({ feature, console }) { + let username = feature.redux.getState().session?.session?.user?.username; + + let projectId = window.location.pathname.split("/")[2]; + let reactions = await ( + await fetch(feature.server.endpoint(`/reactions/${projectId}/`)) + ).json(); + + ScratchTools.waitForElements("div.flex-row.stats", function (req, res) { + makeReactions(reactions); + }); + + function makeReactions(data) { + let parent = document.querySelector("div.flex-row.stats"); + + let already = parent.querySelector(".ste-reactions"); + already?.remove(); + + let div = document.createElement("div"); + div.className = "ste-reactions"; + + let reactionsList = document.createElement("div"); + reactionsList.className = "ste-reactions-list"; + + let allEmojis = []; + for (var i in reactions) { + if (!allEmojis.includes(reactions[i].emoji)) { + allEmojis.push({ + emoji: reactions[i].emoji, + count: reactions.filter((el) => el.emoji === reactions[i].emoji) + .length, + }); + } + } + + if (allEmojis.length === 0) { + let span = document.createElement("span"); + + let img = document.createElement("img"); + img.src = feature.self.getResource("project-reactions-heart-eyes"); + span.appendChild(img); + + reactionsList.appendChild(span); + } + + for (var i in allEmojis) { + if (i < 3) { + let span = document.createElement("span"); + + let img = document.createElement("img"); + img.src = feature.self.getResource( + "project-reactions-" + allEmojis[i].emoji + ); + span.appendChild(img); + + reactionsList.appendChild(span); + } + } + + div.appendChild(reactionsList); + + let modal = document.createElement("div"); + modal.className = "ste-reactions-modal"; + div.appendChild(modal); + + let options = document.createElement("div"); + options.classList.add("ste-reactions-options"); + + function updateOptions() { + allEmojis = []; + for (var i in reactions) { + if (!allEmojis.includes(reactions[i].emoji)) { + allEmojis.push({ + emoji: reactions[i].emoji, + count: reactions.filter((el) => el.emoji === reactions[i].emoji) + .length, + }); + } + } + + options.innerHTML = ""; + + let emojis = [ + "beaming", + "crying", + "fire", + "hands", + "heart-eyes", + "heart-face", + "hearts", + "laughing", + "popper", + "thumbsup", + ]; + + for (var i in emojis) { + let img = document.createElement("img"); + img.src = feature.self.getResource("project-reactions-" + emojis[i]); + img.className = "ste-reactions-option"; + img.dataset.emoji = emojis[i]; + if ( + reactions.find((el) => el.emoji === emojis[i] && el.user === username) + ) { + img.classList.add("selected"); + } + + let span = document.createElement("span"); + span.textContent = allEmojis + .filter((el) => el.emoji === emojis[i]) + .length.toString(); + options.appendChild(span); + + img.addEventListener("click", async function () { + let emoji = this.dataset.emoji; + if (!img.className.includes("selected")) { + this.classList.add("selected"); + ScratchTools.verifyUser(async function (token) { + let data = await ( + await fetch("https://data.scratchtools.app/react/", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token, + emoji, + project: projectId, + }), + }) + ).json(); + + if (data.success) { + reactions = data.data; + updateOptions(); + } + }); + } else { + this.classList.remove("selected"); + ScratchTools.verifyUser(async function (token) { + let data = await ( + await fetch("https://data.scratchtools.app/unreact/", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ token, emoji, project: projectId }), + }) + ).json(); + + if (data.success) { + reactions = data.data; + updateOptions(); + } + }); + } + }); + options.appendChild(img); + } + } + updateOptions(); + modal.appendChild(options); + + parent.appendChild(div); + } +} diff --git a/features/project-reactions/style.css b/features/project-reactions/style.css new file mode 100644 index 00000000..e5aa265b --- /dev/null +++ b/features/project-reactions/style.css @@ -0,0 +1,119 @@ +.ste-reactions-list { + display: flex; + transform: scale(1.3); +} + +.ste-reactions-list img { + height: 1.25rem; + position: relative; + top: .15rem; +} + +.ste-reactions-list span { + height: 1.75rem; + width: 1.75rem; + background: rgb(226, 226, 226); + border-radius: calc(1.75rem / 2); + text-align: center; + z-index: 6; +} + +.ste-reactions-list span:not(:first-child) { + margin-left: -.5rem; + transition: margin-left .3s, opacity .3s; +} + +.ste-reactions-list span:nth-child(2) { + opacity: .6; + z-index: 5; +} + +.ste-reactions-list span:nth-child(3) { + opacity: .3; + z-index: 4; +} + +.ste-reactions-list { + cursor: pointer; + transition: transform .3s; +} + +.ste-reactions:hover .ste-reactions-list { + transform: scale(1.35); +} + +.ste-reactions:hover .ste-reactions-list span:not(:first-child) { + margin-left: .25rem; + opacity: 1; +} + +.ste-reactions-modal { + display: none; +} + +.ste-reactions { + position: relative; +} + +.ste-reactions:hover .ste-reactions-modal { + display: block; + position: absolute; + top: -100%; + left: -6rem; + transform: translateY(calc(-50% - 3rem)); + z-index: 99; + width: 15rem; + background: white; + padding: 1rem; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.357); + height: fit-content; + border-radius: .5rem; + padding-left: 1.5rem; + padding-top: 0px; +} + +.ste-reactions-option { + width: 100%; + height: 2rem; + opacity: .5; + object-fit: contain; + opacity: .5; + cursor: pointer; + transition: transform .3s; +} + +.ste-reactions-option:hover { + transform: scale(1.2); +} + +.ste-reactions-option.selected { + opacity: 1; +} + +.ste-reactions-options { + display: flex; + column-count: 5; + column-gap: 1rem; + display: inline-block; + vertical-align: center; +} + +.ste-reactions-options span { + position: relative; + bottom: -1rem; + left: 1rem; + background-color: #0fbd8c; + color: white; + padding: .1rem; + height: 1.8rem; + width: 1.6rem; + display: block; + margin: 0px; + text-align: center; + font-size: 1rem; + padding-top: 0px; + border-radius: .9rem; + font-weight: 600; + transform: scale(.7); + z-index: 9999; +} \ No newline at end of file