diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5531a8c3..eaf571ef 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,11 +10,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
- name: Install Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
- node-version: 20.x
+ node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
@@ -25,7 +27,7 @@ jobs:
- name: Build standalone
run: npm run build-standalone-prod
- name: Upload standalone artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b
with:
name: standalone
path: dist/standalone.html
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index a9d815c5..65088344 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -5,11 +5,6 @@ on:
push:
branches: [master]
-permissions:
- contents: read
- pages: write
- id-token: write
-
concurrency:
group: "deploy"
cancel-in-progress: true
@@ -19,14 +14,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
- name: Setup GitHub Pages
id: pages
- uses: actions/configure-pages@v4
+ uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b
- name: Install Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
- node-version: 20.x
+ node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
@@ -44,7 +41,7 @@ jobs:
npm run build-standalone-prod
cp dist/standalone.html web
- name: Upload artifact
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa
with:
path: ./web/
@@ -52,9 +49,12 @@ jobs:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
+ permissions:
+ pages: write
+ id-token: write
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
+ uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e
diff --git a/.github/workflows/generate-electron-binaries.yml b/.github/workflows/generate-electron-binaries.yml
index 8e53d39b..7dc5dc7b 100644
--- a/.github/workflows/generate-electron-binaries.yml
+++ b/.github/workflows/generate-electron-binaries.yml
@@ -15,10 +15,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
- node-version: 20.x
+ persist-credentials: false
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
+ with:
+ node-version: 22
- name: Install dependencies
run: |
cd electron-bin
@@ -31,7 +33,7 @@ jobs:
node generate-macos.js
- name: Upload macOS
if: runner.os == 'macOS'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b
with:
name: electron-macos
path: electron-bin/temp/macos/*.zip
@@ -46,13 +48,13 @@ jobs:
node generate-windows.js
- name: Upload Windows
if: runner.os == 'Windows'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b
with:
name: electron-windows
path: electron-bin/temp/windows/*.zip
- name: Upload Windows Crossbuild
if: runner.os == 'Linux'
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b
with:
name: electron-windows-crossbuild
path: electron-bin/temp/windows/*.zip
diff --git a/.github/workflows/wkwebview-build.yml b/.github/workflows/wkwebview-build.yml
index 6afe635b..66641e38 100644
--- a/.github/workflows/wkwebview-build.yml
+++ b/.github/workflows/wkwebview-build.yml
@@ -11,7 +11,9 @@ jobs:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
- name: Build
run: |
cd wkwebview
diff --git a/electron-bin/package-lock.json b/electron-bin/package-lock.json
index 6d6fbda8..5e4f627a 100644
--- a/electron-bin/package-lock.json
+++ b/electron-bin/package-lock.json
@@ -474,9 +474,9 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -1839,9 +1839,9 @@
}
},
"cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
diff --git a/package-lock.json b/package-lock.json
index e6bab656..be253b22 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,17 +9,17 @@
"version": "3.0.0",
"license": "MPL-2.0",
"dependencies": {
- "@fiahfy/icns": "0.0.7",
+ "@fiahfy/icns": "^0.0.7",
"@turbowarp/json": "^0.1.1",
"@turbowarp/jszip": "^3.11.0",
- "@turbowarp/sbdl": "^4.0.1",
- "cross-fetch": "^4.0.0",
+ "@turbowarp/sbdl": "^5.0.1",
+ "cross-fetch": "^4.1.0",
"sha.js": "^2.4.11"
},
"devDependencies": {
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.16.5",
- "@turbowarp/scratch-storage": "^0.0.202403251715",
+ "@turbowarp/scratch-storage": "^0.0.202502192258",
"@turbowarp/scratch-svg-renderer": "^1.0.0-202401111326-62c0f26",
"babel-jest": "^27.4.5",
"babel-loader": "^8.2.3",
@@ -2375,32 +2375,23 @@
}
},
"node_modules/@turbowarp/sbdl": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@turbowarp/sbdl/-/sbdl-4.0.1.tgz",
- "integrity": "sha512-S4pCot6YqdgEAh3cjIXxPE4cTSOVHZC+Ru7CkdDrdXn32j9Nr0lhGUEeHejnnO6C2anPtR+n5PMPhl2Hza0t/g==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@turbowarp/sbdl/-/sbdl-5.0.1.tgz",
+ "integrity": "sha512-tcWfKJNbAHMW6iIYzDo+Fb0AV3dwdCVWgkfwJ5cQbyAm+r7gMRJHzk5gRQhJsMYRpqBFu5TKP10cEQs049oDTg==",
"license": "MIT",
"dependencies": {
"@turbowarp/json": "^0.1.2",
- "cross-fetch": "^3.1.5",
+ "cross-fetch": "^4.1.0",
"jszip": "^3.10.1"
},
"bin": {
"sbdl": "src/cli.js"
}
},
- "node_modules/@turbowarp/sbdl/node_modules/cross-fetch": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
- "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
- "license": "MIT",
- "dependencies": {
- "node-fetch": "^2.6.12"
- }
- },
"node_modules/@turbowarp/scratch-storage": {
- "version": "0.0.202403251715",
- "resolved": "https://registry.npmjs.org/@turbowarp/scratch-storage/-/scratch-storage-0.0.202403251715.tgz",
- "integrity": "sha512-thb0e1MlgddSLJ63WRUNb4QhDhA7gE8t1Fmoi2NbbERxdbJ5yMyRa95JqkJUG2UqzqBnNnhiXYCKKRdVzpiizA==",
+ "version": "0.0.202502192258",
+ "resolved": "https://registry.npmjs.org/@turbowarp/scratch-storage/-/scratch-storage-0.0.202502192258.tgz",
+ "integrity": "sha512-74NnOz0THySIaZLydya3M6zuIedjUcMlbxY8xNc/yH+6bGGmP5S7WyhwHd/8LAgOslT+R6EaIXppWb0eyktUqg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -3730,17 +3721,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
"node_modules/bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@@ -4942,12 +4922,12 @@
}
},
"node_modules/cross-fetch": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
- "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
+ "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
"dependencies": {
- "node-fetch": "^2.6.12"
+ "node-fetch": "^2.7.0"
}
},
"node_modules/cross-spawn": {
@@ -6810,14 +6790,6 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -7102,21 +7074,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -10248,14 +10205,6 @@
"duplexer2": "^0.1.2"
}
},
- "node_modules/nan": {
- "version": "2.22.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
- "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@@ -14882,26 +14831,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/watchpack-chokidar2/node_modules/fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
"node_modules/watchpack-chokidar2/node_modules/glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -15547,26 +15476,6 @@
"node": ">=6"
}
},
- "node_modules/webpack-dev-server/node_modules/fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
"node_modules/webpack-dev-server/node_modules/glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
diff --git a/package.json b/package.json
index 5d21dbc5..afb5041c 100644
--- a/package.json
+++ b/package.json
@@ -34,17 +34,17 @@
"funding": "https://github.com/sponsors/GarboMuffin",
"license": "MPL-2.0",
"dependencies": {
- "@fiahfy/icns": "0.0.7",
+ "@fiahfy/icns": "^0.0.7",
"@turbowarp/json": "^0.1.1",
"@turbowarp/jszip": "^3.11.0",
- "@turbowarp/sbdl": "^4.0.1",
- "cross-fetch": "^4.0.0",
+ "@turbowarp/sbdl": "^5.0.1",
+ "cross-fetch": "^4.1.0",
"sha.js": "^2.4.11"
},
"devDependencies": {
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.16.5",
- "@turbowarp/scratch-storage": "^0.0.202403251715",
+ "@turbowarp/scratch-storage": "^0.0.202502192258",
"@turbowarp/scratch-svg-renderer": "^1.0.0-202401111326-62c0f26",
"babel-jest": "^27.4.5",
"babel-loader": "^8.2.3",
diff --git a/src/addons/pause.js b/src/addons/pause.js
index 0f017dc0..8d03804f 100644
--- a/src/addons/pause.js
+++ b/src/addons/pause.js
@@ -36,7 +36,8 @@ export default function ({ scaffolding }) {
// Immediately emit project stop
// Scratch will do this automatically, but there may be a slight delay.
- vm.runtime.emit("PROJECT_RUN_STOP");
+ vm.runtime.emit('PROJECT_RUN_STOP');
+ vm.runtime.emit('RUNTIME_PAUSED');
} else {
audioContextStateChange = audioContextStateChange.then(() => {
return vm.runtime.audioEngine.audioContext.resume();
@@ -53,6 +54,9 @@ export default function ({ scaffolding }) {
stackFrame.executionContext.timer.startTime += dt;
}
// Compiler state is stored differently
+ if (thread.compatibilityStackFrame && thread.compatibilityStackFrame.timer) {
+ thread.compatibilityStackFrame.timer.startTime += now - pauseState.pauseTime;
+ }
if (thread.timer) {
const dt = now - pauseState.pauseTime;
thread.timer.startTime += dt;
@@ -61,9 +65,9 @@ export default function ({ scaffolding }) {
}
}
pausedThreadState = new WeakMap();
- }
- vm.emit('P4_PAUSE', paused);
+ vm.runtime.emit('RUNTIME_UNPAUSED');
+ }
};
const ensurePausedThreadIsStillPaused = (thread) => {
diff --git a/src/locales/sl.json b/src/locales/sl.json
index 4796ab50..5d72b884 100644
--- a/src/locales/sl.json
+++ b/src/locales/sl.json
@@ -16,7 +16,13 @@
"accentColor": "Barva poudarkov (aktivni gumbi, polje za vnos odgovora, kontekstni meniji)",
"advancedOptions": "Napredne možnosti",
"advancedSummary": "Teh verjetno nočete spremeniti. (Kliknite, da odprete)",
+ "application-linux-arm32": "Aplikacija {type} za Linux (ARM, 32-bitna)",
+ "application-linux-arm64": "Aplikacija {type} za Linux (ARM, 64-bitna)",
+ "application-linux64": "Aplikacija {type} za Linux (64-bitna)",
"application-mac": "Aplikacija {type} za macOS",
+ "application-win-arm": "Aplikacija {type} za Windows (ARM)",
+ "application-win32": "Aplikacija {type} za Windows (32-bitna)",
+ "application-win64": "Aplikacija {type} za Windows (64-bitna)",
"applicationSettings": "Nastavitve aplikacije",
"automaticallyCenter": "Samodejno določi sredino",
"autoplay": "Samodejno začni namesto prikazovanja velike zelene zastavice",
@@ -55,6 +61,7 @@
"icon": "Ikona strani",
"import": "Uvozi nastavitve",
"infiniteClones": "Neskončno klonov",
+ "initalWindowSize": "Začetna velikost okna",
"interaction": "Vhod",
"interpolation": "Interpolacija",
"learnMore": "Več informacij",
@@ -96,12 +103,15 @@
"startFullscreen": "Začni v celozaslonskem načinu",
"startMaximized": "Začni v razširjenem oknu",
"startWindow": "Začni v oknu",
+ "steamworksError": "Pokaži napako in zapri",
+ "steamworksWarning": "Pokaži opozorilo in nadaljuj",
"storedWarning": "Opomba: V tem projektu so shranjene nastavitve, ki lahko povozijo te nastavitve.",
"stretch": "Raztegni oder, da napolni zaslon, brez spreminjanja dejanske velikosti (poskusno)",
"turbo": "Turbo način",
"username": "Uporabniško ime (vsak \"#\" bo zamenjan z naključno številko)",
"variableColor": "Barva spremenljivk",
"version": "Različica",
+ "versionHelp": "Nastavitev različice ni nujna in ne vpliva na delovanje projekta. Odvisno od okolja je lahko prikazana na različnih mestih, kot so okna z lastnostmi. Različica naj bo v obliki X.Y.Z.",
"zip": "Zip (priporočeno za spletne strani)",
"zip-one-asset": "Stisnjena mapa, vse slike in zvoki v eni datoteki (ni priporočeno)"
},
@@ -120,6 +130,7 @@
"error": "Napaka",
"errorMessage": "Sporočilo: {error}",
"feedback": "Povratne informacije",
+ "importingInterface": "Nalaganje možnosti...",
"networkError": "Prenašanje {url} ni uspelo. Prepričajte se, da ste povezani z internetom, in poskusite izključiti vse razširitve brskalnika.",
"outdated": "Ta različica Packagerja je zastarela. Prosimo, da ponovno naložite stran in poskusite znova.",
"privacy": "Politika zasebnosti",
@@ -133,6 +144,7 @@
},
"progress": {
"compressingProject": "Stiskanje projekta",
+ "downloadingExtensions": "Prenašanje razširitev po meri",
"loadingAssets": "Prenašanje videzov in zvokov ({complete}/{total})",
"loadingLargeAsset": "Prenašam {thing}",
"loadingProjectData": "Prenašanje podatkov projekta",
diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json
index ceca6b7d..6b60b625 100644
--- a/src/locales/zh-cn.json
+++ b/src/locales/zh-cn.json
@@ -120,7 +120,7 @@
"startFullscreen": "以全屏模式启动",
"startMaximized": "以最大化窗口模式启动",
"startWindow": "以窗口模式启动",
- "steamworksAvailable": "此作品使用了 Steamworks 扩展。您可以在 Steamworks 找到您游戏的 App ID,或者使用 {n} 来通过 Steamworks demo 测试。",
+ "steamworksAvailable": "此作品使用了 Steamworks 扩展。您可以在 Steamworks 找到您游戏的 App ID,或者使用 {n} 来测试 Steamworks 测试版游戏。",
"steamworksDocumentation": "查看扩展文档以了解详情",
"steamworksError": "展示错误并退出",
"steamworksExtension": "Steamworks 扩展",
diff --git a/src/packager/download-project.js b/src/packager/download-project.js
index 70635c96..0e4db2cb 100644
--- a/src/packager/download-project.js
+++ b/src/packager/download-project.js
@@ -66,7 +66,33 @@ const mutateScratch3InPlace = (projectData) => {
}
};
+ const disableNonsenseCloudVariables = (projectData) => {
+ const DISABLE_CLOUD_VARIABLES = [
+ // The "original" Sprunki project includes a cloud variable presumably used to detect who
+ // clicked on the report button. That seems like a Scratch community guidelines violation but
+ // that's not our job to enforce. This affects us because these games are very popular and
+ // create thousands of unnecessary concurrent cloud variable connections for a feature that
+ // can't work because there is no report button to click on.
+ '☁ potential reporters'
+ ];
+
+ // I want a more general solution here that automatically disables all unused cloud variables,
+ // but making that work in the presence of various unknown extensions seems non-trivial.
+
+ const stage = projectData.targets.find((i) => i.isStage);
+ if (stage) {
+ for (const variable of Object.values(stage.variables)) {
+ // variable is [name, value, isCloud]
+ if (variable[2] && DISABLE_CLOUD_VARIABLES.includes(variable[0])) {
+ variable[2] = false;
+ }
+ }
+ }
+ };
+
+ // Order matters -- check for implied cloud variables before disabling some of them.
makeImpliedCloudVariables(projectData);
+ disableNonsenseCloudVariables(projectData);
optimizeSb3Json(projectData);
};
diff --git a/src/packager/large-assets.js b/src/packager/large-assets.js
index f9bf51fe..6b736b39 100644
--- a/src/packager/large-assets.js
+++ b/src/packager/large-assets.js
@@ -77,9 +77,9 @@ export default {
estimatedSize: 93932512
},
'webview-mac': {
- src: externalFile('WebView-macos-5.zip'),
- sha256: 'b5636571cd9be2aae2f6dac1ab090fdf829c8fdfe91f462cc2feb2d324705f9f',
- estimatedSize: 3425601
+ src: externalFile('WebView-macos-7.zip'),
+ sha256: 'fef0603a17df6dd976eb2aeb704aaec6d2666455089fbf3398becfaf5b29448b',
+ estimatedSize: 3530149
},
'steamworks.js': {
src: externalFile('steamworks.js-0.3.2.zip'),
diff --git a/src/packager/packager.js b/src/packager/packager.js
index 606034da..f603bdee 100644
--- a/src/packager/packager.js
+++ b/src/packager/packager.js
@@ -147,11 +147,17 @@ const CFBundleShortVersionString = 'CFBundleShortVersionString';
// https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype
const LSApplicationCategoryType = 'LSApplicationCategoryType';
-const generateMacReadme = (options) => `When you try to double click on the app to run it, you will probably see this warning:
-"${options.app.packageName} cannot be opened because the developer cannot be verified."
-This is normal. Press cancel.
+const generateMacReadme = (options) => `Due to macOS restrictions, running this app requires a few manual steps.
-To run the app:
+To run the app on macOS 15 and later:
+1) Double click on the app file (${options.app.packageName} in the same folder as this document), then press "Done" when the warning appears
+2) Open macOS System Settings
+3) Go to the "Privacy & Security" section
+4) Scroll to the bottom
+5) By "${options.app.packageName} was blocked to protect your Mac", press "Open Anyway"
+6) In the prompt that appears, press "Open Anyway"
+
+To run the app on macOS 14 and earlier:
1) Control+click on the app file (${options.app.packageName} in the same folder as this document) and select "Open".
2) If a warning appears, select "Open" if it's an option.
3) If a warning appears but "Open" isn't an option, press "Cancel" and repeat from step 1.
@@ -1079,7 +1085,7 @@ cd "$(dirname "$0")"
};
xhr.onerror = () => {
if (location.protocol === 'file:') {
- reject(new Error('Zip environment must be used from a website, not from a file URL.'));
+ reject(new Error('Zip environment must be used on a website, not on a local file. To fix this error, use the "Plain HTML" environment instead.'));
} else {
reject(new Error('Request to load project data failed.'));
}
@@ -1495,15 +1501,21 @@ cd "$(dirname "$0")"
pauseButton.addEventListener('click', () => {
vm.setPaused(!isPaused);
});
- const updatePause = (_isPaused) => {
- isPaused = _isPaused;
+ const updatePause = () => {
if (isPaused) {
pauseButton.src = 'data:image/svg+xml,' + encodeURIComponent('');
} else {
pauseButton.src = 'data:image/svg+xml,' + encodeURIComponent('');
}
- }
- vm.on('P4_PAUSE', updatePause);
+ };
+ vm.runtime.on('RUNTIME_PAUSED', () => {
+ isPaused = true;
+ updatePause();
+ });
+ vm.runtime.on('RUNTIME_UNPAUSED', () => {
+ isPaused = false;
+ updatePause();
+ });
updatePause();
scaffolding.addControlButton({
element: pauseButton,
diff --git a/src/scaffolding/monitor.js b/src/scaffolding/monitor.js
index b32f3480..74cb588c 100644
--- a/src/scaffolding/monitor.js
+++ b/src/scaffolding/monitor.js
@@ -211,12 +211,14 @@ class VariableMonitor extends Monitor {
const ROW_HEIGHT = 24;
class Row {
+ /** @param {ListMonitor} monitor */
constructor (monitor) {
+ /** @type {ListMonitor} */
this.monitor = monitor;
this.index = -1;
this.value = '';
- this.locked = false;
+ this.isFocused = false;
this.root = document.createElement('label');
this.root.className = styles.monitorRowRoot;
@@ -230,14 +232,15 @@ class Row {
this.editable = this.monitor.editable;
if (this.editable) {
this.valueInner = document.createElement('input');
- this.valueInner.tabIndex = -1;
this.valueInner.className = styles.monitorRowValueInner;
- this.valueInner.readOnly = true;
- this.valueInner.addEventListener('click', this._onclickinput.bind(this));
- this.valueInner.addEventListener('blur', this._onblurinput.bind(this));
- this.valueInner.addEventListener('keypress', this._onkeypressinput.bind(this));
- this.valueInner.addEventListener('keydown', this._onkeypressdown.bind(this));
- this.valueInner.addEventListener('contextmenu', this._oncontextmenu.bind(this));
+ // We can't mark the input as readonly by default and then allow editing only
+ // when clicking on it as iOS will not show the keyboard. Thus we need the input
+ // to be always editable, so we need to handle focus events as generally as
+ // possible.
+ this.valueInner.addEventListener('focusin', this._onfocusin.bind(this));
+ this.valueInner.addEventListener('focusout', this._onfocusout.bind(this));
+ this.valueInner.addEventListener('keypress', this._onkeypress.bind(this));
+ this.valueInner.addEventListener('keydown', this._onkeydown.bind(this));
this.valueInner.addEventListener('input', this._oninput.bind(this));
this.valueOuter.appendChild(this.valueInner);
@@ -250,21 +253,25 @@ class Row {
this.valueInner = document.createElement('div');
this.valueInner.className = styles.monitorRowValueInner;
this.valueOuter.appendChild(this.valueInner);
- this.valueInner.addEventListener('contextmenu', this._oncontextmenuuneditable.bind(this));
}
+ this.valueInner.addEventListener('contextmenu', this._oncontextmenu.bind(this));
+
this.root.appendChild(this.indexEl);
this.root.appendChild(this.valueOuter);
}
- _onclickinput () {
- this.valueInner.focus();
- if (this.locked) {
+ isLocked () {
+ return this.isFocused;
+ }
+
+ _onfocusin () {
+ if (this.isFocused) {
return;
}
+
+ this.isFocused = true;
this.valueInner.select();
- this.valueInner.readOnly = false;
- this.locked = true;
this.root.classList.add(styles.monitorRowValueEditing);
this.addNewValue = false;
@@ -272,21 +279,27 @@ class Row {
this.valueWasChanged = false;
}
- _onblurinput () {
- if (!this.locked) {
+ _onfocusout () {
+ if (!this.isFocused) {
return;
}
- this.unfocus();
+ // Store our new cached value, otherwise if we get reused for a different row with
+ // the same value that the old row had before editing, setValue() will keep
+ // displaying the incorrect edited value instead of the actual value.
+ this.value = this.valueInner.value;
+
+ this.isFocused = false;
+ this.root.classList.remove(styles.monitorRowValueEditing);
if (this.deleteValue) {
const value = [...this.monitor.value];
value.splice(this.index, 1);
this.monitor.setValue(value);
- this.monitor.tryToFocusRow(Math.min(value.length - 1, this.index))
+ this.monitor.tryToFocusRow(Math.min(value.length - 1, this.index));
} else if (this.valueWasChanged || this.addNewValue) {
const value = [...this.monitor.value];
- value[this.index] = this.valueInner.value;
+ value[this.index] = this.value;
if (this.addNewValue) {
value.splice(this.index + 1, 0, '');
}
@@ -301,14 +314,14 @@ class Row {
this.valueWasChanged = true;
}
- _onkeypressinput (e) {
+ _onkeypress (e) {
if (e.key === 'Enter') {
this.addNewValue = true;
this.valueInner.blur();
}
}
- _onkeypressdown (e) {
+ _onkeydown (e) {
if (e.key === 'Escape') {
this.valueInner.blur();
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Tab') {
@@ -332,20 +345,16 @@ class Row {
}
_oncontextmenu (e) {
- if (this.locked) {
- // Open native context menu instead of custom list one when editing
+ if (this.editable) {
+ // Show the native text editing context menu instead of our custom one.
e.stopPropagation();
} else {
- // Right clicking should not focus and highlight input
- e.preventDefault();
- }
- }
-
- _oncontextmenuuneditable (e) {
- // When row has been highlighted, eg. by triple click, open native context menu instead of custom
- const selection = getSelection();
- if (this.valueInner.contains(selection.anchorNode) && !selection.isCollapsed) {
- e.stopPropagation();
+ // When row has been highlighted, eg. by triple click, open native context menu instead of custom
+ // This allows people to copy and paste without needing to know ctrl+c.
+ const selection = getSelection();
+ if (this.valueInner.contains(selection.anchorNode) && !selection.isCollapsed) {
+ e.stopPropagation();
+ }
}
}
@@ -359,7 +368,7 @@ class Row {
}
setValue (value) {
- if (this.value !== value && !this.locked) {
+ if (this.value !== value && !this.isFocused) {
this.value = value;
if (this.editable) {
this.valueInner.value = value;
@@ -370,17 +379,14 @@ class Row {
}
focus () {
- this.valueInner.click();
- if (document.activeElement !== this.valueInner) {
- setTimeout(() => this.valueInner.click());
+ if (!this.isFocused) {
+ this.valueInner.focus();
}
}
unfocus () {
- if (this.locked) {
- this.locked = false;
- this.valueInner.readOnly = true;
- this.root.classList.remove(styles.monitorRowValueEditing);
+ if (this.isFocused) {
+ this.valueInner.blur();
}
}
}
@@ -390,7 +396,9 @@ class ListMonitor extends Monitor {
super(parent, monitor);
this.editable = parent.editableLists;
+ /** @type {Map} */
this.rows = new Map();
+ /** @type {Row[]} */
this.cachedRows = [];
this.scrollTop = 0;
this.oldLength = -1;
@@ -586,7 +594,7 @@ class ListMonitor extends Monitor {
for (const index of this.rows.keys()) {
if (index < startIndex || index > endIndex) {
const row = this.rows.get(index);
- if (!row.locked || index >= value.length) {
+ if (!row.isLocked() || index >= value.length) {
row.unfocus();
row.root.remove();
this.rows.delete(index);
diff --git a/wkwebview/WebView/ViewController.swift b/wkwebview/WebView/ViewController.swift
index 6b362297..c33f1fa7 100644
--- a/wkwebview/WebView/ViewController.swift
+++ b/wkwebview/WebView/ViewController.swift
@@ -1,5 +1,5 @@
import Cocoa
-import WebKit
+@preconcurrency import WebKit
class ViewController: NSViewController, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {
@IBOutlet var webView: WKWebView!
diff --git a/wkwebview/WebView/WindowController.swift b/wkwebview/WebView/WindowController.swift
index b6890175..1a373402 100644
--- a/wkwebview/WebView/WindowController.swift
+++ b/wkwebview/WebView/WindowController.swift
@@ -5,4 +5,8 @@ class WindowController : NSWindowController {
super.init(coder: coder)
shouldCascadeWindows = true
}
+
+ override func keyDown(with event: NSEvent) {
+// Don't call super.keyDown so that it won't play the beeping noise.
+ }
}
diff --git a/wkwebview/WebView/index.html b/wkwebview/WebView/index.html
index 27bbb3dd..e8a63dd1 100644
--- a/wkwebview/WebView/index.html
+++ b/wkwebview/WebView/index.html
@@ -5,15 +5,25 @@
+
It works!
+
+
+