diff --git a/extensions/GamerCreeper12/ShareSheet.js b/extensions/GamerCreeper12/ShareSheet.js
new file mode 100644
index 0000000000..2194f338c7
--- /dev/null
+++ b/extensions/GamerCreeper12/ShareSheet.js
@@ -0,0 +1,254 @@
+// Name: ShareSheet
+// ID: gamercreepernoobssharesheetextension
+// Description: Share data from your browser.
+// By: GamerCreeper12
+// License: MPL-2.0
+
+(function (Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Share Sheet must run unsandboxed");
+ }
+
+ const Cast = Scratch.Cast;
+
+ class GamerCreeperNoobsShareSheetExtension {
+ constructor() {
+ this.dataType = "text";
+ this.title = "";
+ this.data = null;
+ this.shareData = {};
+ this.cancelled = false;
+ this.hasError = false;
+ this.errorMsg = "";
+ }
+ getInfo() {
+ return {
+ id: "gamercreepernoobssharesheetextension",
+ name: Scratch.translate("ShareSheet"),
+ color1: "#742DEB",
+ color3: "#a176e8",
+ blocks: [
+ {
+ opcode: "issupported",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: Scratch.translate("is share sheet supported?"),
+ },
+ {
+ opcode: "settitleandtextto",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set title [TITLE]"),
+ arguments: {
+ TITLE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: Scratch.translate("Check out this file!"),
+ },
+ },
+ },
+ {
+ opcode: "setdatatotxt",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set data to text [TXT]"),
+ arguments: {
+ TXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: Scratch.translate("Hello, World!"),
+ },
+ },
+ },
+ {
+ opcode: "setdatatourl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set data to url [URL]"),
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: Scratch.translate("https://example.com"),
+ },
+ },
+ },
+ {
+ opcode: "setdatatoimageurl",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set data to image url [URL]"),
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: Scratch.translate("https://example.com/myimage/"),
+ },
+ },
+ },
+ {
+ opcode: "setdatatocostume",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set data to costume [COSTUME]"),
+ arguments: {
+ COSTUME: {
+ type: Scratch.ArgumentType.COSTUME,
+ },
+ },
+ },
+ {
+ opcode: "share",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("share data"),
+ },
+ {
+ opcode: "isdatatosharesupported",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: Scratch.translate("is last data to share supported?"),
+ },
+ {
+ opcode: "iscancelled",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: Scratch.translate("is sharing cancelled?"),
+ },
+ {
+ opcode: "gotaerrortwhilesharing",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: Scratch.translate("got a error while sharing?"),
+ },
+ {
+ opcode: "lasterrormsg",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("last error message"),
+ },
+ ],
+ };
+ }
+ // helpers
+ isShareSheetSupported() {
+ return !!(navigator.share && navigator.canShare);
+ }
+
+ isShareSheetDataSupported(shareData) {
+ if (!this.isShareSheetSupported()) return false;
+ return !!navigator.canShare(shareData);
+ }
+
+ async shareDataFunc(shareData) {
+ if (!this.isShareSheetDataSupported(shareData)) return;
+ await navigator.share(shareData);
+ }
+
+ async shareDataForShareSheet() {
+ if (!this.isShareSheetSupported()) return;
+ this.shareData = {};
+ this.cancelled = false;
+ this.hasError = false;
+ try {
+ switch (this.dataType) {
+ case "text":
+ if (this.title) this.shareData["title"] = this.title;
+ if (this.data) this.shareData["text"] = this.data;
+ await this.shareDataFunc(this.shareData);
+ break;
+
+ case "url":
+ if (this.title) this.shareData["title"] = this.title;
+ if (this.data) this.shareData["url"] = this.data;
+ await this.shareDataFunc(this.shareData);
+ break;
+
+ case "image":
+ if (this.title) this.shareData["title"] = this.title;
+ if (this.data) this.shareData["files"] = [this.data];
+ await this.shareDataFunc(this.shareData);
+ break;
+
+ default:
+ return;
+ }
+ } catch (err) {
+ if (err.name === "AbortError") {
+ this.cancelled = true;
+ } else {
+ this.hasError = true;
+ this.errorMsg = err;
+ }
+ }
+ }
+
+ // blocks
+ issupported() {
+ return Cast.toBoolean(this.isShareSheetSupported());
+ }
+
+ settitleandtextto({ TITLE: title }) {
+ this.title = Cast.toString(title) || "Share";
+ }
+
+ setdatatotxt({ TXT: text }) {
+ this.dataType = "text";
+ this.data = Cast.toString(text);
+ }
+
+ setdatatourl({ URL: url }) {
+ this.dataType = "url";
+ this.data = Cast.toString(url);
+ }
+
+ async setdatatoimageurl({ URL: url }) {
+ this.hasError = false;
+ try {
+ const response = await Scratch.fetch(url);
+ const blob = await response.blob();
+
+ const imageFile = new File([blob], "image.png", {
+ type: blob.type,
+ });
+
+ this.dataType = "image";
+ this.data = imageFile;
+ } catch (err) {
+ this.hasError = true;
+ this.errorMsg = err.message;
+ }
+ }
+
+ async setdatatocostume({ COSTUME: costumeName }, util) {
+ const target = util.target;
+ const costumeIndex = target.getCostumeIndexByName(costumeName);
+ const costume = target.sprite.costumes[costumeIndex];
+ const asset = costume.asset.encodeDataURI();
+
+ this.hasError = false;
+ try {
+ const response = await Scratch.fetch(asset);
+ const blob = await response.blob();
+
+ const imageFile = new File([blob], "image.png", {
+ type: blob.type,
+ });
+
+ this.dataType = "image";
+ this.data = imageFile;
+ } catch (err) {
+ this.hasError = true;
+ this.errorMsg = err.message;
+ }
+ }
+
+ async share() {
+ await this.shareDataForShareSheet();
+ }
+
+ isdatatosharesupported() {
+ return this.isShareSheetDataSupported(this.shareData);
+ }
+
+ iscancelled() {
+ return this.cancelled;
+ }
+
+ gotaerrortwhilesharing() {
+ return this.hasError;
+ }
+
+ lasterrormsg() {
+ return this.errorMsg;
+ }
+ }
+ Scratch.extensions.register(new GamerCreeperNoobsShareSheetExtension());
+})(Scratch);
diff --git a/extensions/extensions.json b/extensions/extensions.json
index 1c3bc949d8..1fe2e006c5 100644
--- a/extensions/extensions.json
+++ b/extensions/extensions.json
@@ -39,6 +39,7 @@
"XmerOriginals/closecontrol",
"navigator",
"battery",
+ "GamerCreeper12/ShareSheet",
"PwLDev/vibration",
"TheShovel/CustomStyles",
"TheShovel/ColorPicker",
diff --git a/images/GamerCreeper12/ShareSheet.svg b/images/GamerCreeper12/ShareSheet.svg
new file mode 100644
index 0000000000..9e823361b3
--- /dev/null
+++ b/images/GamerCreeper12/ShareSheet.svg
@@ -0,0 +1 @@
+