Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
5e1630b
Update packages
GarboMuffin May 31, 2025
331e882
Add .shortcut to executable files list (#1020)
Fluffy728 Jun 1, 2025
3175e69
Update GitHub Actions workflows
GarboMuffin Jun 4, 2025
503dd6a
Improve performance of renderer.pick() calls
GarboMuffin Jun 6, 2025
9289310
Revert "Improve performance of renderer.pick() calls"
GarboMuffin Jun 7, 2025
1110efb
build(deps): bump @turbowarp/scratch-storage (#1021)
dependabot[bot] Jun 7, 2025
31c7cdd
build(deps): bump scratch-paint from `cbd90fa` to `8093bf7` (#1023)
dependabot[bot] Jun 7, 2025
fe5bf06
build(deps): bump scratch-audio from `7f5925e` to `aba00cd` (#1022)
dependabot[bot] Jun 7, 2025
23299a9
build(deps): bump scratch-vm from `bf57216` to `df92bc2` (#1027)
dependabot[bot] Jun 7, 2025
2e4651c
build(deps): bump scratch-render from `00661a4` to `1ffefcf` (#1028)
dependabot[bot] Jun 7, 2025
d98308d
build(deps): bump scratch-vm from `df92bc2` to `6222be2` (#1029)
dependabot[bot] Jun 7, 2025
4ed79fa
build(deps): bump scratch-render from `1ffefcf` to `2f24d17` (#1030)
dependabot[bot] Jun 8, 2025
7e76bd4
Update translations
GarboMuffin Jun 12, 2025
063c399
Rebuild package-lock.json
GarboMuffin Jun 12, 2025
3e8bf27
Add more mime types to showOpenFilePicker
GarboMuffin Jul 6, 2025
08d1403
Use a wildcard MIME type in showOpenFilePicker
GarboMuffin Jul 16, 2025
e27c8e3
No longer accepting donations
GarboMuffin Jul 17, 2025
b7b3570
Update translations
GarboMuffin Jul 27, 2025
ce7c6ae
build(deps): bump scratch-render from `49f714e` to `13e1933` (#1032)
dependabot[bot] Aug 4, 2025
f2fc38e
Implement JSON support for monitors (#1018)
SharkPool-SP Aug 4, 2025
4d4d2a4
build(deps): bump scratch-blocks from `67121bf` to `6a7e7e5` (#1035)
dependabot[bot] Aug 4, 2025
fb4b567
Use asset host for loading fonts (#1036)
Sunwuyuan Aug 11, 2025
ff6bc4f
Update scratch-render
GarboMuffin Aug 14, 2025
e4b55cb
Fix matching named monitors opening 2 context menus (#1037)
SharkPool-SP Aug 15, 2025
e0afeef
Update scratch-render
GarboMuffin Aug 24, 2025
a778a8f
Update scratch-render
GarboMuffin Aug 24, 2025
220067a
Update scratch-vm
Tacodiva Sep 13, 2025
9519534
Update scratch-vm
GarboMuffin Sep 14, 2025
4935e50
Fix stage header size when stage width <480 (#1042)
fath11 Sep 15, 2025
52ec2c2
Update scratch-vm
GarboMuffin Sep 17, 2025
edc1ae4
Add a message about the new compiler
GarboMuffin Sep 20, 2025
ad595f6
Improve the news component
GarboMuffin Sep 20, 2025
639a018
Update scratch-vm
GarboMuffin Sep 20, 2025
6cfdcb6
Trust by host instead of origin, and fix persistence for ws:// and ws…
GarboMuffin Sep 20, 2025
2c0704c
Update scratch-vm
GarboMuffin Sep 21, 2025
5cdab7e
Update scratch-vm
GarboMuffin Sep 23, 2025
ce5daf1
Revert "Fix stage header size when stage width <480 (#1042)"
GarboMuffin Sep 23, 2025
97ec9b6
Update scratch-vm
GarboMuffin Sep 24, 2025
4d90950
Experiment with counting views (no displaying yet) (#1047)
GarboMuffin Sep 28, 2025
998d087
Update scratch-vm
GarboMuffin Sep 28, 2025
db3c756
Add build-time environment variable for enabling windchimes
GarboMuffin Sep 28, 2025
eae1236
Emit resize after tw-news state change completes
GarboMuffin Sep 28, 2025
254549e
Hide news
GarboMuffin Oct 3, 2025
1dc4af9
Do not submit views for project "0"
GarboMuffin Oct 4, 2025
913c3de
Fix view counting GPC check
GarboMuffin Oct 4, 2025
18f6e14
Update scratch-vm
GarboMuffin Oct 7, 2025
eb94860
update scratch-vm
GarboMuffin Oct 8, 2025
04519a2
Add a more prominent place for people to find face sensing (please gi…
GarboMuffin Oct 19, 2025
03f0d98
Update scratch-vm
GarboMuffin Oct 19, 2025
63a507a
Update translations
GarboMuffin Oct 19, 2025
edf0a1c
build(deps): bump @turbowarp/sb3fix from 0.3.6 to 0.3.7 (#1110)
dependabot[bot] Oct 27, 2025
1748d8c
Update translations
GarboMuffin Nov 2, 2025
7f20bc5
Update scratch-vm
GarboMuffin Nov 2, 2025
bc611ce
Disable show[Save|Open]FilePicker on Android
GarboMuffin Nov 9, 2025
4f50de5
Update scratch-vm, scratch-parser, sb3fix
GarboMuffin Nov 16, 2025
5542f58
Prevent delete/backspace from deleting blocks in fullscreen or when p…
GarboMuffin Nov 16, 2025
6a1b267
Suggest using sb3fix for zip corruption
GarboMuffin Nov 16, 2025
361dd7f
Suggest using sb3fix even if start signature is missing
GarboMuffin Nov 16, 2025
6669a37
Update face-sensing.svg (#1107)
Brackets-Coder Nov 18, 2025
22fceb9
Pull addons
GarboMuffin Nov 21, 2025
b6367bd
Completely rework monitor handling of Object and Arrays (#1113)
CubesterYT Nov 21, 2025
598e0a8
Ask server to respond with clear-site-data when pressing the reset ca…
GarboMuffin Nov 30, 2025
21a6360
Add news for the fake google play store listing
GarboMuffin Nov 30, 2025
b4ff9dd
rephrase
GarboMuffin Dec 1, 2025
df5838f
Update translations
GarboMuffin Dec 5, 2025
5cb812b
Rewrite #1113
GarboMuffin Dec 5, 2025
b97d194
Defer list value stringification until render
GarboMuffin Dec 5, 2025
a48734f
Use safe stringify in more spots in list monitor
GarboMuffin Dec 5, 2025
f2fc199
Don't save list row value if it didn't change
GarboMuffin Dec 5, 2025
2c3ac99
Fix displaying -0 in lists and variables
GarboMuffin Dec 5, 2025
237735f
Go back to old number rounding to match vanilla in some edge cases
GarboMuffin Dec 5, 2025
ec3dc03
build(deps): bump scratch-blocks from `6a7e7e5` to `dd1e426` (#1115)
dependabot[bot] Dec 5, 2025
1dabeae
build(deps): bump scratch-vm from `4187f55` to `fd13a98` (#1116)
dependabot[bot] Dec 5, 2025
0dae3fc
Hide news since the Android situation was resolved
GarboMuffin Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
Expand Down
19,976 changes: 9,137 additions & 10,839 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@turbowarp/jszip": "^3.11.1",
"@turbowarp/nanolog": "^0.2.0",
"@turbowarp/scratch-l10n": "^3.1001.0-202401241456-994097a5",
"@turbowarp/scratch-storage": "^0.0.202502192258",
"@turbowarp/scratch-storage": "^0.0.202505311821",
"@turbowarp/scratch-svg-renderer": "^1.0.0-202312242305-12c360b",
"@turbowarp/startaudiocontext": "^1.0.0",
"arraybuffer-loader": "^1.0.6",
Expand Down
20 changes: 17 additions & 3 deletions src/addons/addons/middle-click-popup/WorkspaceQuerier.js
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,8 @@ class TokenTypeBlock extends TokenType {
strings.push(...blockPart.toLowerCase().split(" "));
} else if (blockPart.type === BlockInputType.ENUM) {
for (const enumValue of blockPart.values) {
if (this.stringForms.length >= WorkspaceQuerier.MAX_RESULTS) return;

enumerateStringForms(
partIdx + 1,
[...strings, ...enumValue.string.toLowerCase().split(" ")],
Expand All @@ -749,6 +751,13 @@ class TokenTypeBlock extends TokenType {
};

enumerateStringForms();

if (this.stringForms.length >= WorkspaceQuerier.MAX_STRING_FORMS) {
console.warn(
"Warning: Block '" + this.block.id + "' has too many string forms. Search results may not be very good."
);
this.stringForms.length = 0;
}
}

/**
Expand Down Expand Up @@ -1164,7 +1173,12 @@ export default class WorkspaceQuerier {
/**
* The maximum number of tokens to find before giving up.
*/
static MAX_TOKENS = 10000;
static MAX_TOKENS = 100000;

/**
* The maximum number of string forms a block can have before we give up.
*/
static MAX_STRING_FORMS = 500;

/**
* Indexes a workspace in preparation for querying it.
Expand Down Expand Up @@ -1208,12 +1222,12 @@ export default class WorkspaceQuerier {
}
++query.resultCount;
if (!limited && query.resultCount >= WorkspaceQuerier.MAX_RESULTS) {
console.log("Warning: Workspace query exceeded maximum result count.");
console.warn("Warning: Workspace query exceeded maximum result count.");
limited = true;
}

if (!query.canCreateMoreTokens()) {
console.log("Warning: Workspace query exceeded maximum token count.");
console.warn("Warning: Workspace query exceeded maximum token count.");
limited = true;
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/addons/generated/upstream-meta.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"commit":"df5f51e"}
{"commit":"f41d6de"}
6 changes: 6 additions & 0 deletions src/addons/settings/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,16 @@
},
"fr": {
"addonFeedback": "Commentaires sur les addons",
"confirmResetAll": "Êtes-vous sûr de vouloir réinitialiser tous les paramètres des addons à leurs valeurs par défaut ?",
"credits": "Crédits:",
"dirty": "Rechargez les onglets pour appliquer les paramètres.",
"dirtyButton": "Recharger maintenant",
"enableDangerous": "Cet addon est dangereux et va intentionnellement DÉSACTIVER certaines fonctionnalitées. La plupart des utilisateurs NE DOIVENT PAS activer cet addon. Êtes-vous sûr de vouloir l'activer ?",
"export": "Exporter les paramètres",
"groupDanger": "Dangereux ({number})",
"groupOthers": "Autres ({number})",
"import": "Importer les paramètres",
"noCompiler": "Cet addon ne fonctionne que lorsque le compilateur est désactivé via le menu Avancé > Désactiver le compilateur ou à l'aide de l'addon « Désactiver le compilateur dans l'éditeur ».",
"noResults": "Aucun résultat.",
"presets": "Préconfigurations",
"reset": "Réinitialiser",
Expand Down Expand Up @@ -653,12 +656,15 @@
"addonFeedback": "附加元件回饋",
"confirmResetAll": "你確定要重設所有附件設定?",
"credits": "感謝:",
"dirty": "重新加載頁面以套用設定",
"dirtyButton": "重新載入",
"enableDangerous": "此插件非常危險,可能會停用功能。大部分使用者不應啟用此插件。你非常確定要開啟他嗎?",
"export": "匯出設定",
"groupDanger": "危險({number})",
"groupNew": "全新({number})",
"groupOthers": "其他({number})",
"import": "匯入設定",
"noCompiler": "此插件只有在通過\"高級 > 禁用編譯器\"的選項或者使用\"禁用編譯器\"插件才可以正常運行。",
"noResults": "沒有相符的結果。",
"presets": "預設",
"reset": "重設",
Expand Down
4 changes: 3 additions & 1 deletion src/components/gui/gui.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

.page-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}

.body-wrapper {
height: calc(100% - $menu-bar-height);
flex-grow: 1;
background-color: $ui-primary;
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx
import TWFontsModal from '../../containers/tw-fonts-modal.jsx';
import TWUnknownPlatformModal from '../../containers/tw-unknown-platform-modal.jsx';
import TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx';
import TWWindChimeSubmitter from '../../containers/tw-windchime-submitter.jsx';

import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
Expand Down Expand Up @@ -186,6 +187,7 @@ const GUIComponent = props => {
<React.Fragment>
<TWSecurityManager securityManager={securityManager} />
<TWRestorePointManager />
<TWWindChimeSubmitter isEmbedded={isEmbedded} />
{usernameModalVisible && <TWUsernameModal />}
{settingsModalVisible && <TWSettingsModal />}
{customExtensionModalVisible && <TWCustomExtensionModal />}
Expand Down
10 changes: 9 additions & 1 deletion src/components/menu-bar/menu-bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import FramerateChanger from '../../containers/tw-framerate-changer.jsx';
import ChangeUsername from '../../containers/tw-change-username.jsx';
import CloudVariablesToggler from '../../containers/tw-cloud-toggler.jsx';
import TWSaveStatus from './tw-save-status.jsx';
import TWNews from './tw-news.jsx';

import {openTipsLibrary, openSettingsModal, openRestorePointModal} from '../../reducers/modals';
import {setPlayer} from '../../reducers/mode';
Expand Down Expand Up @@ -482,7 +483,7 @@ class MenuBar extends React.Component {
);
// Show the About button only if we have a handler for it (like in the desktop app)
const aboutButton = this.buildAboutMenu(this.props.onClickAbout);
return (
const menuBar = (
<Box
className={classNames(
this.props.className,
Expand Down Expand Up @@ -1042,6 +1043,13 @@ class MenuBar extends React.Component {
{aboutButton}
</Box>
);

return (
<React.Fragment>
{menuBar}
{/* <TWNews /> */}
</React.Fragment>
);
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/components/menu-bar/tw-news.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import "../../css/colors.css";

.news {
background: $motion-tertiary;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0;
gap: 0.5rem;
}

.text {
flex-grow: 1;
color: white;
text-align: center;
}

.text a {
color: inherit;
}

.close {
flex-shrink: 0;
margin-right: 0.5rem;
}
67 changes: 67 additions & 0 deletions src/components/menu-bar/tw-news.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import {APP_NAME} from '../../lib/brand';
import {isScratchDesktop} from '../../lib/isScratchDesktop';
import CloseButton from '../close-button/close-button.jsx';
import styles from './tw-news.css';

const LOCAL_STORAGE_KEY = 'tw:closedNews';
const NEWS_ID = 'android-fraud';

const newsAppliesToUser = () => /android/i.test(navigator.userAgent);

const NewsBody = () => (
<div className={styles.text}>
{/* eslint-disable-next-line max-len */}
{`The Google Play Store app calling itself TurboWarp is illegitimate and outdated. If you were misled into installing it, leave a 1 star review, uninstall it, and report it. An official one will exist eventually.`}
</div>
);

const getIsClosedInLocalStorage = () => {
try {
return localStorage.getItem(LOCAL_STORAGE_KEY) === NEWS_ID;
} catch (e) {
return false;
}
};

const markAsClosedInLocalStorage = () => {
try {
localStorage.setItem(LOCAL_STORAGE_KEY, NEWS_ID);
} catch (e) {
// ignore
}
};

class TWNews extends React.Component {
constructor (props) {
super(props);
this.state = {
closed: getIsClosedInLocalStorage() || !newsAppliesToUser()
};
this.handleClose = this.handleClose.bind(this);
}
handleClose () {
markAsClosedInLocalStorage();
this.setState({
closed: true
}, () => {
window.dispatchEvent(new Event('resize'));
});
}
render () {
if (this.state.closed || isScratchDesktop()) {
return null;
}
return (
<div className={styles.news}>
<NewsBody />
<CloseButton
className={styles.close}
onClick={this.handleClose}
/>
</div>
);
}
}

export default TWNews;
5 changes: 4 additions & 1 deletion src/components/monitor/list-monitor-scroller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {FormattedMessage} from 'react-intl';

import styles from './monitor.css';
import {List} from 'react-virtualized';
import {safeStringify} from '../../lib/tw-safe-stringify.js';

class ListMonitorScroller extends React.Component {
constructor (props) {
Expand Down Expand Up @@ -71,7 +72,9 @@ class ListMonitorScroller extends React.Component {
</div>

) : (
<div className={styles.valueInner}>{this.props.values[index]}</div>
<div className={styles.valueInner}>
{safeStringify(this.props.values[index])}
</div>
)}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/monitor/monitor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const MonitorComponent = props => (
// TW: if export is defined, we always show it, even outside of the editor
disable={!props.draggable && !props.onExport}
holdToDisplay={props.mode === 'slider' ? -1 : 1000}
id={`monitor-${props.label}`}
id={`monitor-${props.id}`}
>
<Draggable
bounds=".monitor-overlay" // Class for monitor container
Expand Down Expand Up @@ -75,7 +75,7 @@ const MonitorComponent = props => (
// positioning conflicts between the monitors `transform: scale` and
// the context menus `position: fixed`. For more details, see
// http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/
<ContextMenu id={`monitor-${props.label}`}>
<ContextMenu id={`monitor-${props.id}`}>
{props.draggable && props.onSetModeToDefault &&
<MenuItem onClick={props.onSetModeToDefault}>
<FormattedMessage
Expand Down
38 changes: 36 additions & 2 deletions src/components/tw-invalid-project-modal/invalid-project-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const formatError = error => {
return `${message}\n\n---\n\nURL: ${location.href}\nUser-Agent: ${navigator.userAgent}`;
};

const errorMatches = (error, regex) => regex.test(`${error}`);

const isZipCorruptionWithSignatureIntact = error => (
errorMatches(error, /Corrupted zip|uncompressed data size mismatch/)
);

const isJSONValidationError = error => errorMatches(error, /validationError/);

const InvalidProjectModal = props => (
<Modal
className={styles.modalContent}
Expand All @@ -47,7 +55,33 @@ const InvalidProjectModal = props => (
value={formatError(props.error)}
/>

{formatError(props.error).includes('validationError') && (
{isZipCorruptionWithSignatureIntact(props.error) ? (
<p>
<FormattedMessage
// eslint-disable-next-line max-len
defaultMessage="This error often means that the file was corrupted, possibly due to a faulty storage device, power outage, or unplugging a USB drive without ejecting. Try {usingSb3fix} as it can fix this type of error."
// eslint-disable-next-line max-len
description="Part of modal that appears when a project could not be loaded. {usingSb3fix} becomes a link 'using sb3fix to recover your project'. sb3fix refers to https://turbowarp.github.io/sb3fix/"
id="tw.invalidProject.zipCorruption"
values={{
usingSb3fix: (
<a
href="https://turbowarp.github.io/sb3fix/?platform=turbowarp"
target="_blank"
rel="noreferrer"
>
<FormattedMessage
defaultMessage="using sb3fix to recover your project"
// eslint-disable-next-line max-len
description="Part of modal that appears when a project could not be loaded. Used in context 'Try using sb3fix to recover your project as it can fix this type of error'. sb3fix referes to https://turbowarp.github.io/sb3fix/"
id="tw.invalidProject.sb3fix"
/>
</a>
)
}}
/>
</p>
) : isJSONValidationError(props.error) ? (
<p>
<FormattedMessage
// eslint-disable-next-line max-len
Expand All @@ -73,7 +107,7 @@ const InvalidProjectModal = props => (
}}
/>
</p>
)}
) : null}

<p>
<FormattedMessage
Expand Down
1 change: 1 addition & 0 deletions src/components/tw-security-manager-modal/download.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const DEFINITELY_EXECUTABLE = [
'webloc',
'inetloc',
'lnk',
'shortcut',

// Windows scripting languages
'bat',
Expand Down
1 change: 1 addition & 0 deletions src/containers/extension-library.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class ExtensionLibrary extends React.PureComponent {
const locale = this.props.intl.locale;
library.push(
...this.state.gallery
.filter(i => i.extensionId !== 'faceSensing')
.map(i => translateGalleryItem(i, locale))
.map(toLibraryItem)
);
Expand Down
Loading